ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

MATLAB科研绘图必备:Hatchfill2纹理填充解决灰度图区分难题

MATLAB科研绘图必备:Hatchfill2纹理填充解决灰度图区分难题 简介Hatchfill2是面向MATLAB用户的图形增强工具解决标准fill函数纯色填充的局限提供斜线、交叉线、斑点等复杂图案填充适用于科研绘图、工程报告等场景适合有一定MATLAB绘图基础、希望提升图表表现力的开发者。压缩包共4个文件约60KB包含两个m文件核心填充函数与演示脚本、一个mat数据文件演示所需数据及一个txt许可证文本结构精简便于快速上手。已有1443人学习下载。通过演示脚本可直观掌握不同图案、角度、透明度等参数的组合用法理解填充算法实现细节数据文件与注释完整的示例代码相结合能帮助用户学会将自定义填充应用到实际图形中使数据可视化更专业、更具层次感。许可证文件则明确了使用规范方便后续合法修改与再分发。 做科研绘图的人应该都遇到过这种尴尬论文要求用灰度或黑白图可是画的时候为了区分数据组习惯性地上了红、绿、蓝三套颜色。屏幕上一看层次分明。等导成灰度图或者打印出来几组颜色全部糊成一团图例标得再清楚也救不回来。我的处理办法是直接用Hatchfill2一个在MATLAB生态里专门给图形对象叠加填充纹理的小工具。它的核心能力是在patch、bar、fill、contour这类对象上自动生成斜线、交叉线、点阵等图案让图形在完全去色之后依然能通过纹理方向、密度和样式区分不同区域。本文就围绕这个工具从原理到实操把怎么用、踩过哪些坑、以及如何配合论文和汇报场景做图完整讲一遍。1. Hatchfill2解决的核心瓶颈1.1 没有填充纹理时图表区分度有多差先还原一个最典型的崩溃现场。画三组柱状图分别用三套颜色填充RGB值大概是[0.8 0.2 0.2]、[0.2 0.8 0.2]和[0.2 0.2 0.8]。这三组颜色转到灰度后亮度值会变得非常接近某些情况下甚至只差十几个灰度级。人眼对灰度差异的敏感度本来就低于对色相和饱和度的敏感度一旦图幅变小、打印分辨率下降这种差异基本就消失了。期刊审稿人如果看到的是这种图轻则要求返修重则直接认为数据呈现不专业。Hatchfill2的思路是用“纹理”而不是“灰度深浅”来编码类别信息。斜线方向、稀疏程度、单线还是交叉线这些特征在黑白环境下依然能保留很强的辨识度。换句话说它把原本只能靠颜色表达的分类维度换成了一个对输出环境几乎不敏感的视觉通道。1.2 为什么不用MATLAB自带的绘图功能很多人会问MATLAB不是后来有FacePattern吗确实R2021b开始Bar对象的属性里加入了FacePattern相关能力可以用线条填充柱体。但问题在于这个能力目前覆盖的范围非常有限只对bar这类对象友好遇到patch、fill、等高线组、任意闭合面片它照样无能为力。而且在R2021a及更早版本里这个功能根本不存在。与此同时科研场景里大量使用自定义面片比如用patch画区域标注、用fill画置信区间、用contourf画分布图这些恰恰是最需要加纹理的对象。另一种替代方案是自己生成二值图案矩阵配合坐标映射叠加到图形上。这个方案不是不行但要做的事情很多解析对象边界、生成图案、处理缩放、保证旋转参数正确、还要避免覆盖坐标轴内容。一套代码写下来画一张图的时间比画十张图还长。Hatchfill2把这些细节全部封装好了用户只需要传入对象句柄和几个样式参数它内部自动完成坐标计算、线段生成和层级管理直接把结果画在图上。2. 环境准备与安装验证2.1 下载与路径配置Hatchfill2发布在MATLAB Central的File Exchange上搜索“hatchfill2”即可找到最新版本Github上也有同步镜像。下载之后解压里面通常包含核心函数文件、示例脚本和一段说明文档。我建议不要图省事把文件随手放到某个临时文件夹而是固定在一个有意义的路径下比如D:/MyMatlabToolbox/hatchfill2方便后期维护和换机器同步。然后在MATLAB里执行addpath(genpath(D:/MyMatlabToolbox/hatchfill2)); savepath;为什么要用genpath而不是addpath因为有些版本的工具会在子目录里存放示例数据或者辅助函数只加顶层目录会导致部分功能找不到依赖文件。装完之后验证一下which hatchfill2如果返回了完整路径说明安装成功。如果返回“not found”优先检查路径字符串有没有写错以及解压目录下是不是确实存在hatchfill2.m。2.2 不同MATLAB版本的兼容情况从R2014a到R2023a我在多个大版本上都跑过这个工具绝大多数场景下表现稳定。需要注意的一个分水岭是R2014b旧版本用的HG1图形系统和新的HG2图形系统对句柄的处理方式不同Hatchfill2在HG2下的适配明显更顺滑。如果还在使用R2014a之前的版本建议先升级否则可能出现句柄类型解析异常。另一个容易踩坑的地方出现在App Designer里。UIAxes和传统Figure的坐标区底层实现不完全一致直接在UIAxes上调用Hatchfill2有时候会提示句柄类型不支持或者图形没有反应。我的做法是需要加纹理的图形永远用传统figure绘制最后通过exportgraphics或copygraphics导出成图片再嵌入到App里展示这样既保证了功能的兼容性也避免了界面刷新带来的额外卡顿。3. 核心功能实操3.1 调用格式、风格与关键参数Hatchfill2的基本调用方式不长几行就能看懂hatchfill2(H) hatchfill2(H, single, 45) hatchfill2(H, cross, 30, 5)H是你要处理的图形对象句柄可以是patch、fill对象、bar序列或者contour组。第二个参数是填充风格常见的有single单向斜线、cross交叉线、speckle颗粒点、fill实色填充和none不加纹理。后面的数字参数分别是角度和线密度。角度参数单位是度表示线条与水平方向的夹角工程图里常用的范围是0到90度。线密度则控制纹理的稀疏程度数值越小线条越密。常用的风格和适用场景可以参考下面这个表样式名视觉效果典型适用场景single单方向平行斜线两组数据区分最常用cross两组斜线交叉成网格三组以上数据或需要强标记区域speckle随机颗粒点面积较大的区域适合低密度填充fill近似纯色填充需要替代色块颜色时使用none不做处理批量循环中临时占位3.2 控制颜色、线型和边界实际使用中纹理线条的颜色和线型是高度可定制的。默认情况下Hatchfill2会根据对象的FaceColor自动推导出线条颜色但这个推导结果不一定符合预期尤其是当FaceColor是flat或者使用了透明度时线条颜色可能和原图边界颜色不一致。为了稳定输出我一般会在调用时显式指定颜色hatchfill2(H, single, 45, 5, Color, [0 0 0]);如果想把默认实线改成虚线可以继续追加参数hatchfill2(H, single, 45, 5, Color, [0 0 0], LineStyle, --);需要注意的是函数解析参数时位置参数和Name-Value参数是按顺序处理的。如果你只想修改颜色而不想重新设置角度和密度依然要把角度、密度这两个位置参数占位补上否则解析会错位。不同版本对参数名的支持略有差异拿到工具后可以先看一眼help hatchfill2确认当前版本支持哪些属性再动手。3.3 多组数据的纹理分配同一个坐标系下面同时绘制多组数据时需要给每一组分配不同的纹理核心思路是分别拿到每个图形对象的句柄再循环调用Hatchfill2。以三组柱状图为例figure; hBar bar(1:5, rand(5,3), FaceColor, flat); hPatch findobj(hBar, Type, patch); angles [30 60 90]; for i 1:3 hatchfill2(hPatch(i), single, angles(i), 5, Color, k); end这里通过findobj找到柱状图内部的所有patch对象再按顺序给每个patch分配不同的角度。30、60、90度三种斜线组合在一起区分度非常高。如果想要更强的标记效果可以在循环里混合使用不同样式比如第一组用single、第二组用cross、第三组用speckle这种组合在复杂图形中比单纯换角度更容易辨认。4. 实测案例与代码示例4.1 柱状图加单斜线纹理用一组模拟数据完整跑一遍。三列数据放在同一个bar图上然后给前两组加不同角度的斜线第三组不加用来模拟“对照组”data [2.5 3.8 4.2; 3.1 2.9 4.6; 2.8 3.5 4.0]; figure; hBar bar(data); hPatch findobj(hBar, Type, patch); for i 1:3 ang 30 (i-1) * 30; hatchfill2(hPatch(i), single, ang, 4, Color, k); end运行下来三个组的柱体分别出现了30度、60度、90度的黑色斜线纹理密度足够均匀没有出现断裂或偏移。这里线间距取了4是对默认尺寸figure比较合适的一个密度值。如果图像幅面特别大比如为了论文做了全页宽图建议把密度调到6到8避免线条密到变成一团黑。反过来如果是小图密度可以降到2到3确保纹理肉眼可见。4.2 等高线区域加交叉填充处理contourf绘制的等高线图时直接对整个Contour对象调用Hatchfill2并不总是理想因为等高线对象内部包含大量线段和边界信息函数解析产生的线条数量会急剧增加图形容易变得杂乱运行速度也会明显下降。更稳妥的做法是只对你要强调的特定区域手动构建patch再对这个patch加纹理。我常用的一种做法是先用contour获取目标高度的轮廓坐标再把这些坐标组成的闭合区域填充成patch然后调用交叉纹理。比如要标注等高线中的零值区[x, y] meshgrid(-3:0.1:3); z peaks(x, y); figure; contourf(x, y, z, 20); hold on; [c, h] contour(x, y, z, [0 0]);这里h是等高线对象但零值线通常不是单一闭合区域直接取坐标、填充成patch需要额外处理连通性。我的经验是先用contourc获取坐标数据再用patch手动勾画感兴趣的区域拿到patch句柄后再加纹理C contourc(x(1,:), y(:,1), z, [0 0]); % 手动处理C矩阵中的线段分类选出目标边界XY坐标 patch(X, Y, w, EdgeColor, none); hp findobj(gca, Type, patch, FaceColor, w); hatchfill2(hp, cross, 45, 3, Color, k);这样做的好处是contourf原有的色彩层次完全保留显著性区域用交叉斜线单独标出来灰度输出时一眼就能辨认。整个过程需要对轮廓坐标做一点手工过滤代码量不多但效果非常稳。4.3 图例中正确显示纹理加了Hatchfill2之后一个高频问题是图例不显示纹理。默认情况下legend展示的是面片对象的原始色块并不会因为你手动在patch上加了斜线就自动更新。解决这个问题通常需要“造假”一个样本面片专门放进图例里hFakePatch fill(NaN, NaN, w); hatchfill2(hFakePatch, single, 45, 4, Color, k); legend([hPatch(1), hPatch(2), hFakePatch], {实验组, 对照组, 基准区域});这里用fill(NaN, NaN, w)创建一个看不见的透明面片它只服务于图例不会在坐标区显示任何内容。给这个样本面片加同样的纹理后再把它追加到legend的句柄列表里图例中就会出现和图中完全匹配的纹理样式。期刊投稿时图例纹理和图中纹理对得上这一条基本就是规范图的隐形门槛。5. 常见问题与排查技巧实录5.1 加载纹理后没有反应如果调用Hatchfill2后图形毫无变化优先排查两类原因。第一类是句柄类型不对传入的是axes或者figure这类对象不是面片函数没有处理逻辑第二类是当前对象所在坐标系处于“图片锁定”状态比如在subplot循环里没有及时hold on导致新增的Line对象被自动清除。还有一种容易被忽略的情况是对象面积太小生成的线条数量过少肉眼看不出纹理。处理办法是放大图形局部确认一下如果确实没有线条就调整线密度参数到更小的值试试。5.2 纹理被遮挡或顺序不对Hatchfill2生成的线条对象会默认放在当前坐标系的顶层但如果你后续又调用了hold on并画了新的数据新对象可能覆盖掉纹理。这种事情在画完数据后又追加标注线、误差棒时尤为常见。解决方法是把生成的Line对象手动提升到最上层hLines hatchfill2(hPatch, single, 45, 4, Color, k); uistack(hLines, top);如果线条数量太多导致图面卡顿可以把密度调低或者只在最终成图阶段加纹理避免在调试过程中反复渲染。5.3 导出图片时纹理丢失或偏移导出为PDF、EPS这类矢量格式时Hatchfill2生成的线段是普通Line对象理论上不会丢但渲染效果受输出尺寸影响很大。如果你发现导出的图片里纹理位置偏移大概率是屏幕显示和打印尺寸的缩放比例不一致导致的。建议固定figure的PaperPositionMode并在导出时指定分辨率set(gcf, PaperPositionMode, manual); exportgraphics(gcf, output.png, Resolution, 600);导出PNG时如果出现锯齿600dpi基本能解决导出PDF时如果线条偏移先检查坐标区和figure的尺寸比例是否锁定。5.4 循环绘图时纹理串号在批量处理多个子图时如果你在循环里重复使用同一个句柄变量或者上一次循环的图形对象没有清空纹理就会串到下一张图上。我的处理方式是维护独立的line句柄列表在每轮循环开始时先删除上一轮的线条for k 1:n cla(ax(k)); % 绘制新数据 hPatch findobj(ax(k), Type, patch); if ~isempty(hPatch) hatchfill2(hPatch, single, 45, 4, Color, k); end end这样虽然粗暴但能保证每个子图之间的纹理完全独立不会因为旧的Line对象残留而产生干扰。6. 扩展思路与替代方案6.1 在地图与区域图上叠加纹理除了数据图Hatchfill2在地理区域图的类别标注上也很有用。用patch绘制行政区域或样方边界时可以通过不同纹理区分土地利用类型、实验分区等信息。配合坐标转换把经纬度多边形坐标转换为平面坐标后直接填充patch再调用Hatchfill2的cross模式就能在不改变底色透明度的情况下增加一层纹理编码。我个人的习惯是把纹理线条颜色设为黑色并且透明度调低这样既不会盖住底图要素又能清晰区分区域类型。6.2 与R2021b版FacePattern功能如何取舍新版MATLAB的Bar对象确实支持FacePattern使用起来更加“官方”不需要额外下载工具包代码上也更简洁。但如果你的图形既包含柱状图又包含自定义patch区域或者需要对contourf结果做局部标注FacePattern的覆盖范围就不够了。Hatchfill2最大的优点是通用、稳定、不挑图形对象一套代码用到所有场景不用关心版本差异。我自己在实际项目中通常用FacePattern处理常规柱状图遇到复杂面片、区域图和多对象混合图时还是切回Hatchfill2。两者并不冲突按需选用就好。从刚开始觉得“加个纹理还要调参好麻烦”到现在只要涉及黑白输出都会默认把它加进绘图脚本Hatchfill2基本成了我的标配工具。回头再看它帮助最大的地方还不是省了那几行代码而是让我在做图时就强制考虑了灰度场景下的可读性避免了交付前才发现区分度不足的尴尬。如果你也在为期刊投稿的灰度图发愁不妨先下一份试试从最简单的单组柱状图开始跑通一遍之后再根据自己的数据结构做调整。最后提醒一句无论最终效果看起来多满意导出前务必在屏幕上先生成一版灰度预览确认每种纹理在去色后依然能明确区分这个小习惯能帮你省掉很多返工时间。本文还有配套的精品资源点击获取
返回列表