ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MATLAB pcolor绘图精讲:从入门到精通,掌握colormap调色与数据可视化

MATLAB pcolor绘图精讲:从入门到精通,掌握colormap调色与数据可视化 先说我自己的经历。有一段时间我整天在MATLAB里画各种二维网格数据刚开始图省事全部用imagesc一把梭。直到有一次要做一张带真实地理坐标的浓度分布图imagesc的坐标总是对不齐我怎么调XData和YData都别扭。后来换成了pcolor虽然一开始踩了不少坑但真正用顺之后才发现这个函数比我想象中要强大得多尤其是在配合colormap做数据可视化的时候能玩出的花样非常多。这篇文章我就把pcolor从入门到精通的完整心法整理出来重点会放在colormap调色上这是让一张图从“能看”变成“能用”的关键。1. pcolor在MATLAB可视化全家桶里的定位它到底解决什么问题1.1 先分清楚pcolor和imagesc、contourf、surf到底该用谁很多初学者一拿到二维数据就纠结到底用imagesc还是pcolor还是contourf其实这几个函数各自解决的场景完全不同选错了工具后边再努力也白搭。imagesc本质上是把一个二维数组当成图像显示像素位置由XData、YData决定但它并不关心你的数据网格是否均匀。也就是说如果你的X和Y是等间距的规则网格用imagesc完全没问题可一旦X和Y是不等距采样比如测站点在山坡上分布得很不均匀imagesc画出来就是扭曲的——它只会把你的数据硬塞进一个矩形区域里不能真实反映每个网格点的实际位置和面积。contourf则更适合画平滑的等值线填充图它会做插值生成一条条等值线。但如果你要严格保留每个格子的原始数值不希望被插值平滑掉contourf就不是首选了。pcolor的定位恰好在这两者之间它保留每个网格单元的原始形状和数值按面片patch方式绘制同时能真实映射到非均匀坐标。我用一个表格把这几个函数的关键差异列出来方便你选型的时候直接对比函数适用场景坐标映射方式是否能画非均匀网格是否做插值平滑输出对象类型imagesc均匀网格、图像显示像素中心对齐不支持不做image对象pcolor网格化标量场、非均匀采样区域每个面片真实坐标支持不做可选shading插值patch对象堆叠contourf平滑等值线、趋势面插值后等值线支持会contour对象surf/view(2)三维曲面俯视三维数据俯视支持可选surface对象我平时最常用的取舍逻辑是如果精度要求高、网格不规则、要出矢量图就毫不犹豫用pcolor如果只是快速看一眼数据整体趋势才偷懒用imagesc。1.2 pcolor的对象模型为什么它天生比imagesc更灵活pcolor在MATLAB底层并不是一个简单的“图像”而是一堆patch对象的组合。每一个网格单元都是一个四边形的颜色面片你可以单独控制每个面片的颜色、透明度、边缘线宽甚至可以把某个格子拿掉。这个特性在数据可视化里非常关键。比如你想把某个区域的无效数据遮住、让特定格子变得半透明、或者把某个超过阈值的单元格闪亮显示pcolor都能直接做到。而imagesc一旦画出来就是一个整体想单独操作某一小块区域麻烦得多。理解了“pcolor 面片集合”这个底层模型很多现象就说得通了。比如你第一次运行pcolor时如果不加shading interp图可能是一片黑色网格线——因为默认情况下pcolor只画边缘线颜色不填充面颜色。这个我在下一节会专门讲因为这是新手踩得最多、也最容易产生挫败感的地方。2. 第一次调用pcolor的完整流程与最容易搞反的坐标轴方向2.1 最小可运行示例先把图画出来再说先给你一个能直接跑通的最小示例后边所有进阶内容都从这里展开% 生成一组规则网格数据 x 1:10; y 1:5; [X, Y] meshgrid(x, y); C rand(5, 10); figure; pcolor(X, Y, C); shading interp; % 关键让颜色填充每个格子 colorbar;运行这段代码后你会看到一张10列5行的随机颜色分布图。对于只有几行几列的小数据pcolor画出来可能会有明显的马赛克格子感这是正常的——因为每个单元格本身就是独立面片数据分辨率低格子感就强。如果你想去掉格子边缘的黑色轮廓线更干净的做法是这样pcolor(X, Y, C); shading flat; % 每个格子内颜色均匀且默认不画边缘线区别在于shading interp会对单元格内部做线性插值让颜色过渡更平滑shading flat则是每个格子用同一个颜色填满不同格子之间颜色断裂。选哪个取决于你的数据特性如果数据本身就是离散测量值用flat更诚实如果是连续物理场用interp视觉上更自然。2.2 两个绕不开的坑维度转置与边缘/填充色我先说新手最常犯的一个错误画出来的图跟预想的翻转了90度。这不是你的数据有问题而是把X和Y的对应关系搞反了。关键要理解meshgrid的输出规则。我上面写的[X, Y] meshgrid(x, y)X的每一行都是相同的沿x方向变化Y的每一列都是相同的沿y方向变化而且X的尺寸是length(y)×length(x)C矩阵也必须和这个尺寸一致注意是“行数为y方向点数、列数为x方向点数”。我见过很多人喜欢这样写x 1:10; y 1:5; C rand(10, 5); % 直觉上觉得x是行但实际错了 pcolor(x, y, C);然后画出来的图莫名其妙转了90度。pcolor在接收向量x、y时要求C的大小必须是length(y)×length(x)。如果你把C写成length(x)×length(y)图形自然就歪了。怎么快速检查一个小经验如果图的行数/列数跟你预期不一致先看C矩阵每个维度的长度再用size(C)和length(y)、length(x)对照一遍。只要你记住“C行数对应yC列数对应x”这个规则就永远不会翻转。第二个大坑就是我前面提到的默认状态下pcolor显示的是边缘颜色不是填充色。你如果直接运行pcolor(X, Y, C);不做任何设置大概率看到的是密密麻麻的黑色线条网格以为是代码写错了。这是因为pcolor的默认属性是EdgeColor为黑色、FaceColor为flat而flat的面色需要配合shading才能显示。正确的做法是接一排shading命令让填充色显现。2.3 坐标轴方向与图像坐标系的纠缠还有个细节我用了好多年才彻底搞明白MATLAB默认坐标轴方向是axis xyx轴水平向右y轴竖直向上这个方向对大多数科学绘图都是合适的。但如果你是从图像处理转过来的习惯了图像坐标系y轴竖直向下很容易对pcolor的方向产生困惑。假设你的C矩阵是从卫星影像读出来的第1行在视觉上通常是图像的最上方但pcolor默认会把第1行画在最下方坐标y最小的地方。想让图的方向和图像一致要加上axis ij; % 翻转y轴让第1行显示在顶部或者反过来如果你想保持笛卡尔坐标系但数据本身是图像读入的那就需要先对C做flipud处理。这块没有绝对的对错完全取决于你的数据语义但你要时刻知道自己的图在哪个坐标系下否则后边叠加GIS数据、地理坐标时就会出乱子。我在实际项目里通常的做法是地理空间数据一律保持axis xy并在画图时手动将纬度维度翻转保证北方朝上。纯粹图像数据才用axis ij。这个习惯能省掉很多低级的视觉错误。3. 从土气到高级colormap调色的底层逻辑与实操配方3.1 colormap不过是一张M×3的颜色查找表现在聊标题里说的“colormap调色秘籍”。很多人用colormap只停留在切换几个预设名字比如colormap(jet)、colormap(hot)一旦想做出自己想要的配色就不知道怎么办了。本质上MATLAB的colormap就是一个M行3列的矩阵每一行代表一个颜色的RGB值三列分别对应红、绿、蓝通道数值范围在0到1之间。比如hot(5)返回的就是5行3列矩阵代表从黑到红再到黄白的一条渐变色带。渲染的时候pcolor把CData里每个数值映射到0到1之间由clim设定上下限然后在这张颜色查找表上找到对应的颜色。举个例子如果表格有256行clim是[0,100]那么CData里的数值50就会映射到第128行左右的颜色。也就是说colormap的分辨率越高颜色过渡就越细腻但本质上还是一张查找表。这个底层认知非常重要因为你一旦明白了colormap只是矩阵就能自由地编辑它、拼接它、甚至从图片里提取它。下面几种方法都是围绕这个原理展开的。3.2 自定义调色板的三种方法第一种最直接手写一个矩阵。比如要做一个红-黄-蓝三段渐变色你只需myMap [1 0 0; 1 1 0; 0 0 1]; colormap(myMap);但这样只有3个颜色渲染出来会有明显的色阶断层。更常用的是用interp1在这几个锚点之间插值生成256级平滑色带% 定义锚点颜色深蓝 - 青 - 黄 - 深红 anchors [0 0 0.6; 0 0.8 0.8; 1 1 0; 0.7 0 0]; nColors 256; t linspace(1, size(anchors, 1), nColors); smoothMap interp1(1:size(anchors,1), anchors, t); colormap(smoothMap);这段代码的原理很简单锚点是原始色标点t是目标插值位置interp1会在相邻锚点之间做线性插值生成完整的256×3矩阵。线性插值得到的色带不一定“好看”但绝对可控适合你有一个明确的色彩想法的时候用。第三种方法是从参考图里提取配色这个方法我强烈推荐。很多人看到别人论文里的配色特别高级但自己又描述不出来是什么颜色。你直接把参考图截下来用imread读入再用kmeans聚类提取主色img imread(reference_colors.png); pixels reshape(double(img), [], 3); [idx, centers] kmeans(pixels, 8); % 聚类出8个主色 map8 centers / 255; map256 interp1(1:8, map8, linspace(1, 8, 256)); colormap(map256);聚类提取出来的颜色往往比较和谐因为原始图片本身就是精心设计的。不过要注意聚类出来的颜色顺序是随机的你需要按亮度或按数值顺序手动排列一下锚点否则渐变会乱跳。3.3 选色原则按数据性质选色带这是调色是否能“专业”的核心。对于连续递增型数据比如温度、浓度、高程最好选亮度单调变化的色带。MATLAB自带的parula、turbo都是这个思路。特别是turbo它是jet的改良版色彩丰富但不会像jet那样在黄色区域产生人为的高亮“假边界”推荐优先使用。对于有正负之分的发散型数据比如异常值、偏差值传统做法是蓝-白-红三色带中间白色对应零值divMap interp1(1:3, [0 0 0.8; 1 1 1; 0.8 0 0], linspace(1, 3, 256)); colormap(divMap);这里有一个关键点发散发带一定要保证中间颜色亮度最高两端颜色饱和度高但亮度低这样人的视觉会自然把“白色中间带”理解为零值附近。另外要特别提醒红绿配色对于红绿色盲用户是完全区分不开的配色方案里尽量避免红绿同时出现。如果实在要用可以用蓝-橙组合替代这是目前在科学可视化社区比较推崇的色盲友好方案。实测下来蓝色-橙色对比在灰度打印时也能拉开层次比红绿靠谱得多。最后再给一个实用技巧配色定稿前把图转成灰度看看对比度。方法是在figure窗口用print(-deps2c)导出EPS灰度预览或者直接用rgb2gray处理你的colormap矩阵。如果转灰度后相邻颜色几乎无法分辨说明这张图在黑白打印时会丢失信息。做学术发表时这个问题很常见但也很容易避免。4. 进阶使用非均匀网格、mask遮挡与多子图配色协同4.1 非均匀网格pcolor的隐藏强项pcolor最大的隐藏优势我前面提过——支持非均匀网格。这个能力在实测数据里太常用了。举一个我项目里的实际例子沿一条断面向深部探测浅层采样密、深层采样疏x方向各测点间距也不一致。这种情况下用imagesc画出来几乎不能反映真实测点分布但pcolor可以直接把测点的x、z坐标作为X、Y传进去x [0 2 4 5 6.5 8 10]; % 非均匀横向间距 z [0 10 20 35 50 80 120]; % 非均匀纵向间距 [X, Z] meshgrid(x, z); C randn(length(z), length(x)); figure; pcolor(X, Z, C); shading flat; colorbar; axis equal; % 保持真实纵横比这样画出来的每个格子宽度和高度都是真实物理尺寸的反映不会再出现均匀像素导致的视觉失真。对做地质剖面、气象站点、海洋观测这类非规则采样数据的同学来说这个特性可以说是刚需。4.2 用NaN和透明度遮掉不想显示的区域实际数据里总会有无效点。有的测区没数据、某些网格测值明显是坏点直接画进去会把整张图的色标拉偏甚至产生假异常。pcolor处理无效数据非常方便把无效点对应的CData设为NaN对应面片就不会渲染。C(C 0 | C 100) NaN; % 超范围设为无效 pcolor(X, Y, C); shading interp;但这里有个细节设NaN后空白区域的边框线如果不处理会留下明显的网格线痕迹。建议在设为NaN之前先把面片整体画一遍作为底色或者直接对无效面片单独设置EdgeColor为none。我的习惯是h pcolor(X, Y, C); shading flat; set(h, EdgeColor, none);另外如果你想保留原始数据但只是视觉上弱化某个区域用透明度更灵活。pcolor产生的patch对象支持FaceAlpha属性但默认一个pcolor对象只能统一设置透明度不能按格区分。要实现逐格透明度需要一个循环里面逐面片赋值效率不高一般我只会对需要强调的两个区域做不会全图操作。mask的替代方案如果只是想让背景变成白色还有一个更快的思路——直接把背景区域数值设为NaN再在colormap末尾追加一个纯白颜色让NaN映射到白色上。不过这个做法在今天新版本MATLAB中已经不再推荐因为NaN会强制不渲染你看到的背景是Color属性而不是colormap所以直接设置figure背景色更简单直接set(gca, Color, [0.96 0.96 0.96]); % 雾灰背景区分有效区4.3 多子图的colormap隔离与clim统一问题多子图对比是pcolor进阶使用里最让人头疼的一块因为colormap是挂在figure级别的而不是挂在axes级别。这就意味着你在一个figure里画了4个子图只要设置一次colormap4个子图全部跟着变。这带来两个问题。第一个问题4个子图数据范围可能差很多却共用同一套颜色映射小范围数据的图颜色很淡细节全丢了。解决方法是给每个子图单独设定色标范围。新版MATLAB推荐用clim旧版用caxissubplot(2,2,1); pcolor(X, Y, C1); shading flat; clim([min(C1(:)), max(C1(:))]); % 新版本写法 subplot(2,2,2); pcolor(X, Y, C2); shading flat; clim([min(C2(:)), max(C2(:))]);第二个问题更隐蔽如果你的4个子图想用不同的colormap怎么办默认情况下做不到——colormap设置是应用到整个figure的。解决办法有两种思路一是“包装成索引值”。提前把所有颜色映射为一个统一的colormap每个子图的CData都通过这个总表映射成索引色再用imagesc实现。这种方法麻烦但颜色隔离很彻底。思路二是用新版tiledlayout的nexttile配合colormap(ax, map)。我记得从R2021b开始MATLAB的tiledlayout已经支持给每个tile单独设置colormap了。如果你的版本较新直接tiledlayout(1,2); ax1 nexttile; pcolor(ax1, X, Y, C1); shading flat; colormap(ax1, parula(256)); clim(ax1, [0 50]); ax2 nexttile; pcolor(ax2, X, Y, C2); shading flat; colormap(ax2, hot(256)); clim(ax2, [0 100]);如果版本较老又不方便升级那只能用第一种思路做一个拼接大色表。说实话大部分时候我宁愿用第一种思路因为即使在R2022a里某些渲染模式下子图的colormap隔离还是会有显示异常的小bug。5. 综合实战一幅污染浓度分布图的完整绘制套路5.1 数据准备模拟一个高斯扩散场光讲原理很空洞我用一个环境污染物浓度分布的经典场景把前边所有知识点串起来。假设我们在一个二维平面上有多个污染源每个污染源向周围扩散空间浓度符合高斯分布叠加% 定义研究区网格 x linspace(0, 20, 80); y linspace(0, 15, 60); [X, Y] meshgrid(x, y); % 设置污染源坐标和强度 sources [5 4 9; 12 10 7; 15 3 11]; % 计算每个网格点的浓度值 sigma 1.8; C zeros(size(X)); for k 1:size(sources, 1) xs sources(k, 1); ys sources(k, 2); q sources(k, 3); C C q * exp(-((X - xs).^2 (Y - ys).^2) / (2 * sigma^2)); end这个模拟不复杂但数据形态跟很多真实观测场很接近背景值低、多个局部高值中心、空间连续。用这种数据演示调色和叠加最合适。5.2 绘制主图、叠加等值线与采样点接下来用pcolor画浓度底色再叠加contour等值线和采样点位置figure; pcolor(X, Y, C); shading interp; colormap(parula(256)); clim([0 12]); hold on; % 叠加等值线并标注线值 [Cc, hc] contour(X, Y, C, [1 3 5 7 9 11], k-, LineWidth, 1); clabel(Cc, hc, FontSize, 9); % 标记污染源位置 plot(sources(:,1), sources(:,2), rv, MarkerFaceColor, r, MarkerSize, 8); % 图例与标注 colorbar; xlabel(x / km); ylabel(y / km); title(污染物浓度分布模拟); axis equal; grid off;这里有几个我反复强调的细节等值线叠加时contour和pcolor的XY必须完全来自同一套meshgrid否则会出现等高线和底色偏移的视觉误差。clabel自动标注的等值线数值默认是从C矩阵获取的要保持等值线层级设置合理不要让标注文字覆盖到关键区域必要时手动删掉几条拥挤的标注。采样点用实心三角叠加颜色和colormap主色系做区分。这里我用了红色因为parula色带本身没有正红对比度足够不会跟底色混淆。5.3 出图分辨率、字体与打印导出很多人在屏幕上看着挺好看导出一放到论文里就惨不忍睹颜色发灰、字变小、线变毛。根本原因是导出设置没做好。我的推荐配置是set(gcf, Color, w, Position, [100 100 700 500]); exportgraphics(gcf, pollution_map.png, Resolution, 300);新版exportgraphics函数比老式print好用太多它能按你figure实际大小导出分辨率设置直接生效。如果你要投稿矢量图直接导出PDF或EPSexportgraphics(gcf, pollution_map.pdf, ContentType, vector);导出的字号问题我建议在画图前就统一设置axes的FontName和FontSize而不是导出后再处理set(gca, FontName, Helvetica, FontSize, 11);这里还有一个调色与导出的连带关系colormap颜色级数太多在PDF导出时文件会很大256级可以接受如果要求文件更小可以降到128级视觉差异几乎不可感知。6. 从入门到精通常见的坑与排查清单6.1 经典问题排查对照表我把这些年遇到的高频问题整理成一个排查表碰到问题直接对照着查效率会高很多现象可能原因解决方法图全黑或全是线条没有填充颜色没有shading或FaceColor设置不当添加shading flat/interp图翻转了90度C矩阵维度与meshgrid不匹配确保size(C) [length(y), length(x)]图像上下颠倒坐标轴方向与数据语义不一致使用axis ij或flipud调整颜色色阶和数值范围对不上未设置clim默认用了数据最大最小范围按需设置clim([lower upper])NaN区域显示成黑色渲染器不支持NaN透明把无效值改成背景色或单独画一个底色patch多子图颜色互相覆盖colormap是figure级别的全局属性用clim隔离范围新版用tiledlayout或统一索引色导出后颜色变灰变淡使用了RGB颜色空间或分辨率过低exportgraphics设300dpi以上确认figure的Color为白色数据量大时运行卡顿pcolor会生成大量patch对象用imagesc插值或先聚合数据再画最右列和最下行缺失pcolor绘制的是网格之间的面片把C矩阵扩展一行一列或者改用pcolor替代方案6.2 pcolor的最后一个单元格永远“少一块”数据扩展技巧这是一个非常经典的问题当你用pcolor画一个m×n的C矩阵时画出来的图实际只有(m-1)×(n-1)个格子。因为pcolor的运作方式是把坐标网格点连接成面片每个面片由四个顶点围成所以最后一行的数据和最后一列的数据其实只作为边界顶点参与并没有形成完整的格子。很多人在画投影图、地形图时发现图形的下边界和右边界少了半条边就是被这个机制坑了。解决方法是给C矩阵额外扩展一行和一列让数据点和格点数对齐C_ext C; C_ext(end 1, :) C(end, :); % 复制最后一行 C_ext(:, end 1) C_ext(:, end); % 复制最后一列 pcolor(X, Y, C_ext); shading flat;如果你用的是contourf这类没有这个限制就没有这个问题。但pcolor本身是一个“顶点连面片”的模型必须理解这个特性才能正确出图。6.3 性能问题当网格很大时的替代方案pcolor的灵活是要付出代价的网格一大patch对象数量成千上万内存占用和渲染速度都会爆炸。我试过用pcolor画一个1200×800的网格结果MATLAB直接卡到无法旋转缩放。如果数据规模很大但仍然是均匀网格最优先的方案是imagesc因为image对象在渲染时是单对象效率远高于建一堆patch。如果一定要保留非均匀坐标那可以用插值把数据重采样到均匀网格再用imagesc。很多空间数据本身会提供griddata插值方法比如[Xg, Yg] meshgrid(linspace(min(x), max(x), 600), linspace(min(y), max(y), 400)); Cg griddata(X, Y, C, Xg, Yg, cubic); imagesc(Xg(1,:), Yg(:,1), Cg); axis xy;这样图像尺寸可控、颜色更平滑渲染也快。唯一的代价是插值会引入一些平滑效应如果你对数据的原始细节有严格要求还是需要小心使用。最后再分享一个我自己摸索出来的小技巧画大范围数据的时候先把数据按百分位裁剪一下再做可视化比如只显示2%到98%分位数之间的数据能极大改善色带的有效对比度极少数离群点不会再把整个颜色范围拉偏。这个方法配合pcolorparula或turbo几乎能应对我日常工作里九成以上的二维数据可视化需求。
返回列表