前阵子实验室一个师弟拿着一张论文截图来找我说想把里面的函数曲线数据提取出来在MATLAB里稍微改点参数重新画一张几乎同款的图放到自己的报告里。这种场景太常见了看论文、看技术博客、看老教材里的坐标曲线图觉得这个趋势画得特别好想复现甚至想修改成自己的版本结果手里只有一张“死图”没有原始数据。这里要分享的就是一套我自己长期在用的“函数图像提取 → 数据复现 → 图像修改”完整工作流以及一个注释清晰、拿来就能改的MATLAB绘图程序模板。整套流程只需要正版MATLAB校园版或者试用版够用加一个浏览器里打开的开源小工具不需要买专门的曲线数字化软件所以它确实又便宜又高效。如果你也经常对着论文插图、书里老图、别人项目截图里的坐标曲线发愁或者写报告时需要把别人的曲线风格改成自己的配色这篇内容可以直接当参考手册用。1. 先想清楚你到底是要“提取数据”还是“画出同款图”很多人一上来就急着找代码、找软件结果绕了一大圈才发现自己想要的其实不是同一个东西。所以第一步不是打开MATLAB而是先问清楚自己我对这张图的需求到底是什么1.1 三种不同需求对应三种完全不同的做法我把常见的诉求分成三类处理思路和工具差别很大。你真正想要的效果需要做的事推荐做法精度要求只想让报告里出现一张“风格类似”的示意图不需要原始数据重画一个趋势即可直接根据形状写函数表达式低想把图中的曲线数据真正拿来做对比分析必须做图像数字化把像素坐标还原成数据坐标截图定标 取点 坐标变换高想基于别人的结果修改参数生成自己的曲线先提取数据再拟合或重采样最后用绘图程序修改数字化 曲线重建 MATLAB重绘中高这个区分非常重要。如果你只是想要一张“看起来差不多的示意图”完全没必要去抠像素手动写一个函数再调调参数就够了。但如果你需要把别人的数据和自己的实验数据画在同一张图里对比那必须做数字化否则数据值全是编的审稿人或者导师一问就露馅。1.2 我日常使用的工作流总览我把完整流程固定成了五步任何一张函数曲线图拿过来都能套图像预处理把截图里无关的边框、水印、多余的图例裁掉只保留坐标轴和曲线区域。坐标定标在图上取两个已知坐标的刻度点比如x轴上的0和10y轴上的0和5建立“像素坐标 ↔ 数据坐标”的换算关系。这一步最核心也最容易被忽略。取点沿着目标曲线采点可以用工具自动识别也可以手动点实际使用中往往是“自动手动修正”的组合。数据还原与清洗根据定标公式把像素坐标换算成真实坐标再做排序、去重、平滑、重采样。MATLAB重绘与样式修改导入还原后的数据用一套注释清晰的绘图模板画出来再按自己的需要改颜色、线型、坐标范围、坐标体系。整个流程里的工具只有MATLAB和一个免费网页工具。MATLAB上课、实验室、工作单位一般都有授权学生还可以用校园版网页工具也是开源的完全不需要额外掏钱。所谓“便宜”不是让你去找乱七八糟的破解渠道而是用正版许可加上免费开源工具把原本需要几百上千块专业软件才能干的事做掉。2. 不花钱也能准确提取图像数据两种主流取点方案提取函数图像数据的方法有很多但考虑到“便宜、高效、复现容易”三个前提我实际靠两种方案吃饭一种是用MATLAB自带功能自己写取点脚本另一种是用浏览器里的开源工具先取点再导入MATLAB。2.1 方案一用MATLAB自带功能手写一个取点器这个方法的好处是全程不离开MATLAB适合不想切换软件、偶尔取一两张图的场景。核心思路就三步显示图片 → 鼠标点击已知刻度点完成定标 → 鼠标点击曲线上的点并换算成真实坐标。下面这个脚本是一个可以直接跑的简化版% extract_curve_points.m % 功能从图片中提取函数曲线数据点 % 用法运行后先看图片按提示操作 clear; clc; close all; % 1. 读取图片并显示 img imread(target_curve.png); imshow(img); % 显示原图 hold on; title(先点击X轴两个已知刻度点然后点击Y轴两个已知刻度点); % 2. 用ginput采集4个定标点 % 这里按“X轴左刻度、X轴右刻度、Y轴下刻度、Y轴上刻度”的顺序点击 [x_pix, y_pix] ginput(4); % 3. 输入这4个点对应的真实坐标根据你图片实际情况修改 % 例如X轴刻度是0和10Y轴刻度是0和5 x_real_known [0, 10]; % 对应前两个点的真实x值 y_real_known [0, 5]; % 对应后两个点的真实y值 % 4. 建立像素到真实的线性映射 x_scale (x_real_known(2) - x_real_known(1)) / (x_pix(2) - x_pix(1)); x_origin x_real_known(1) - x_scale * x_pix(1); % 注意图像坐标系中y方向是向下的所以要加负号 y_scale -(y_real_known(2) - y_real_known(1)) / (y_pix(4) - y_pix(3)); y_origin y_real_known(1) - y_scale * y_pix(3); % 5. 沿曲线取点取完后按回车结束 title(现在沿线点击曲线上的点取完后按回车结束); [xi, yi] ginput; % 6. 坐标换算 x_data x_origin x_scale * xi; y_data y_origin y_scale * yi; % 7. 保存数据 save(extracted_data.mat, x_data, y_data);这里尤其要注意y轴方向。MATLAB里imshow显示的图像坐标原点在左上角y轴向下增长。所以取点时像素y坐标越大对应的真实数据y值应该越小这就是代码里y_scale前面加负号的原因。如果忘了这一步你提取出来的曲线会是上下颠倒的很多新手在这个地方卡半天。如果你觉得一次ginput点错一个点就要全部重来太难受可以改成循环每点一个点就画一个小圆标记同时用键盘监听按键按u的时候撤销上一个点。这个逻辑不复杂核心就是维护两个动态数组我这里不展开写但强烈建议你按需加上去——实际取一条复杂曲线时手一抖点歪是常态。2.2 方案二用WebPlotDigitizer先取点再导入MATLAB如果曲线比较复杂比如有多条线重叠、有锯齿、有粗细变化手写脚本一条条点会点到怀疑人生。这种情况下我推荐先用开源工具WebPlotDigitizer浏览器直接打开官网上传图片定标自动提取导出CSV最后在MATLAB里读CSV。具体操作步骤打开WebPlotDigitizer官网在浏览器里直接使用不需要安装。上传你的函数图像建议用原图或高清截图不要用被聊天工具二次压缩过的图。选择坐标轴类型线性坐标、对数x轴semilogx、对数y轴semilogy、双对数坐标loglog。这一步一定要选对否则后面全错。在图像上依次点击坐标轴的已知刻度点输入对应的数值。比如线性坐标下x轴点击0和10输入0和10对数坐标下点1和100输入1和100。软件会自动识别曲线颜色并沿曲线取点对于重叠区域手动补充取点。导出为CSV文件会在本地生成一个表格。MATLAB这边读取非常简单% 读取WebPlotDigitizer导出的CSV data readtable(curve_data.csv); x data.x; % 第一列是x y data.y; % 第二列是y我特别建议在导出前把自动取点的数据在软件里先放大检查一遍因为自动识别容易把网格线、坐标轴边框也当作曲线。把那些明显不在目标曲线上的点删掉再导出比在MATLAB里清理轻松得多。2.3 坐标定标这一关做不对后面全白搭不管是方案一还是方案二定标都是决定精度最关键的一步。这里的原理其实就是一个二维线性映射像素坐标经过缩放和平移变成数据坐标。线性坐标的换算公式很简单假设你在x轴上取了两个已知真实值x1、x2的刻度点对应的像素横坐标是p1、p2那么任意一个像素横坐标p对应的真实值就是x_true x1 (p - p1) * (x2 - x1) / (p2 - p1)y方向同理只是要处理像素y轴方向相反的问题。如果是对数坐标事情就稍微绕一点。对数坐标下刻度值是等比递增的但像素坐标是等距的所以“像素位置”不是和“真实值”线性相关而是和“真实值的对数”线性相关。比如y轴是对数刻度你在图上取一个真实值y1参与定标计算的应该使用log10(y1)。换算得到的是对数结果最后要再用10.^还原成真实值。举一个具体例子一张semilogy图y轴刻度是0.1、1、10、100。你在图像上点击1和100两个刻度点对应的输入不是1和100而是log10(1)0和log10(100)2。定标完成后你沿曲线取点的像素y坐标代入公式得到类似1.3010这样的数值这实际对应的是10^1.3010 ≈ 20。如果你直接拿1和100做线性定标提取出来的数据会严重偏移。实操中还有几个细节定标时尽量选择跨度大的两个刻度点跨越的像素距离越大定标误差越小。不要选择图边缘的刻度因为截图时边缘常常被截掉一部分。定标完成后先做一个快速验证在图上重新点击一个已知刻度点比如x5看换算结果是否接近5。误差超过1%就要检查是不是选错点或者定标顺序错了。图像里有网格线时取点前先把网格线在视觉上排除掉或者用软件里的颜色过滤功能只保留曲线颜色。3. 复现图像不是简单把点连起来数据清洗与曲线重建从图像里取出来的原始点通常比较“脏”尤其是老书扫描图、压缩严重的网页截图、带抗锯齿的PNG。你如果把几百个原始取点直接丢进plot画出来大概率是一条锯齿横生的粗线根本没法看。所以在重绘之前数据必须经过清洗和重建。3.1 数据清洗排序、去重、去异常点取点的时候鼠标点得比较随意数据顺序可能乱也可能同一个x位置点了好几个几乎相同的点。我先固定做两步清洗% 1. 按x排序 [x, idx] sort(x); y y(idx); % 2. 去掉x重复的点保留第一个 [x, ia] unique(x); y y(ia);排序好理解unique这一步是为了防止同一个x位置有两三个y值后面做插值时会报错。接下来查看是否存在明显跳变的异常点% 查看相邻点的y差值 dy abs(diff(y)); % 找出远大于中位数的跳变位置通常是误点产生的野值 threshold 10 * median(dy); bad_idx find(dy threshold);找到野值后可以根据相邻点插值替换掉或者直接删除并重新做插值。这一步不一定要一个完美的自动函数很多时候我反而是把plot(x,y,o)画出来人眼直接观察哪些点明显飞出去了手动删掉更靠谱。对绝大多数情况来说好的人工判断胜过复杂的滤波算法。3.2 曲线重建插值重采样和函数拟合怎么选清洗完数据点之后面临两种路线插值重采样或者函数拟合。如果目标是“让图好看、密度均匀”那就用插值重采样。interp1是标配% 在统一间隔上重采样pchip保形插值比spline更不容易过冲 xq linspace(min(x), max(x), 500); yq interp1(x, y, xq, pchip);spline在某些陡峭段会产生比较大的过冲振荡pchip则更保形对函数曲线复现来说更友好。如果你只是重画一条光滑曲线pchip基本够用。如果目标是“还原这个函数的物理模型”那就得做函数拟合。比如你知道原图是一条指数衰减曲线理论上满足y a * exp(-x / tau) c可以用MATLAB的fit来做mdl fittype(a*exp(-x/tau)c, independent, x); f fit(x, y, mdl, StartPoint, [1, 10, 0]); xfit linspace(min(x), max(x), 300); yfit f(xfit);这里最容易被坑的是初值StartPoint。指数项里的tau初值如果给得差太远拟合十有八九收敛不到正确结果。我的经验是先从图上大概读出曲线衰减到一半时对应的x值把这个值当作tau的初值成功率会高很多。这背后的道理很简单非线性拟合本质上是迭代寻优初值决定你从哪个山头开始爬起点选错自然到不了最高点。如果看不出具体函数形式别硬猜表达式老老实实用插值重采样就好。强行套一个不合适的模型拟合出来的曲线反而偏离原图。3.3 对数坐标图像的还原不能直接套线性公式前面提过对数坐标的定标涉及log10数据还原阶段同样要注意。我在取semilogx、loglog这类图的时候流程是定标时把刻度真实值取对数后参与线性映射计算。取点完成后先把像素坐标代入线性公式算出“对数域的数值”。最后对计算结果统一做10.^得到真实数据值。Matlab代码大致是这样% 假设y是对数轴log_y_pix是像素y坐标 log_y_true log_y_origin log_y_scale * y_pix; y_true 10 .^ log_y_true;实际操作中我建议把“还原后的数据”立刻叠加在原图上验证一次把提取出来的(x, y)点画在坐标轴比例与原始图一致的MATLAB图上肉眼比对两条曲线是否重合。这一步能帮你迅速发现定标错误、轴类型选错等问题。数据提取不是一锤子买卖验证是必须有的环节。4. 一个“注释清晰、拿来就能改”的通用绘图程序模板数据还原只是前半场把提取到的数据变成一张好看、能改、几年后回来还能看懂的图才体现绘图程序的价值。这一节直接给一个我自己改了很多版、注释写得很细的MATLAB模板。4.1 从数据到出版级图片的基础脚本这个模板覆盖了大多数函数曲线图的绘制需求。代码本身不依赖特定数据你只要把x、y换成自己的变量就能跑% prettyPlotTemplate.m % 功能从提取数据绘制一张适合论文/报告的曲线图 % 用法替换数据后直接运行 % 维护建议保留数据文件和本脚本放同一目录 clc; close all; % 1. 加载数据也可以直接用工作区变量 % load(extracted_data.mat); x linspace(0, 10, 100); y exp(-x/3) .* sin(x) 0.2*randn(size(x)); % 示例数据替换成你自己的 % 2. 设置图形窗口大小单位是像素 figure(Position, [100, 100, 560, 420], Color, w); % 3. 绘制曲线 % 关键参数LineWidth线宽、MarkerSize标记大小、Color颜色 plot(x, y, Color, [0 0.4470 0.7410], LineWidth, 2); hold on; % 保持图形方便叠加其他曲线 % 4. 坐标轴设置 set(gca, FontName, Helvetica, FontSize, 12); set(gca, TickDir, out); % 刻度朝外出版风格的常见选择 set(gca, Box, off); % 去掉顶部和右侧框线画面更干净 xlabel(时间 (s), FontSize, 14); ylabel(幅值, FontSize, 14); % 5. 图例 legend(原始曲线, Location, northeast, Box, off); % 6. 导出图片300dpi足够印刷 exportgraphics(gcf, output_curve.png, Resolution, 300);这个脚本的注释看起来啰嗦但正是这些“废话”让半年后的你还能一眼想起来每个参数是干嘛的。我自己吃过太多亏当年画图为了省事不写注释后来导师让把某张图改个线宽我盯着代码看了十分钟不知道改哪里。exportgraphics是R2020a以后推荐的导出方式比起老式的print(-dpng,...)它能更好地处理白边、字体、分辨率问题。如果你的MATLAB版本比较老可以用print(gcf, -dpng, -r300, output_curve.png)效果相近。4.2 把绘图模板封装成可复用函数固定模板如果每次复制粘贴时间一长就会分叉出好几个版本改来改去乱成一团。更理性的做法是封装成一个函数通过参数控制细节function h prettyPlot(x, y, opts) % prettyPlot 标准化绘制单条曲线 % 输入 % x, y : 数据 % opts : 结构体可包含字段 % opts.Color, opts.LineWidth, opts.Marker, % opts.XLabel, opts.YLabel, opts.Legend % 输出 % h : 图形对象句柄 arguments x double y double opts.Color (1,3) double [0 0.4470 0.7410] opts.LineWidth double 2 opts.Marker char none opts.XLabel char opts.YLabel char opts.Legend char end fh figure(Position, [100 100 560 420], Color, w); h plot(x, y, Color, opts.Color, LineWidth, opts.LineWidth); if ~strcmp(opts.Marker, none) h.Marker opts.Marker; end set(gca, FontName, Helvetica, FontSize, 12); set(gca, TickDir, out, Box, off); xlabel(opts.XLabel, FontSize, 14); ylabel(opts.YLabel, FontSize, 14); if ~isempty(opts.Legend) legend(opts.Legend, Location, best, Box, off); end end调用的时候特别清爽opts.XLabel 频率 (Hz); opts.YLabel 幅度 (dB); opts.Legend 测量数据; prettyPlot(freq, amp, opts);封装函数的好处是以后要统一调整所有图形的风格只需要改这一个函数的内部设置不需要打开每个绘图脚本逐个改。这个习惯越早建立越受用尤其是做多个数据集对比实验的时候。4.3 样式细节决定了这张图“像不像回事”同样是画一条曲线默认plot(x, y)和精心设置的图专业感差很多。下面这些参数是我画函数图时几乎必调的参数推荐值作用LineWidth1.5 ~ 2线条太细导出后发虚太粗又显笨重MarkerSize5 ~ 7数据点过大容易遮住曲线趋势FontSize11 ~ 14图和正文的字号要协调别一个极端的大或小TickDirout期刊图常见风格刻度朝外更清爽Boxoff去掉上右框线减少视觉噪音ColorOrder自定义RGB矩阵控制多条曲线的自动配色GridLineStyle--如果开网格用虚线并降低透明度多曲线场景下颜色选择不要直接用默认的一、二、三号色硬套。我常用的一组RGB值是蓝色[0 0.4470 0.7410]橙色[0.8500 0.3250 0.0980]黄色[0.9290 0.6940 0.1250]紫色[0.4940 0.1840 0.5560]这组配色来自MATLAB的默认parula系明暗对比足够打印成灰度图后仍然能区分。5. 图像修改的三层需求改数据、改样式、改坐标体系把原始图像成功复现出来只是完成了“提取”和“复现”两个目标。实际用得更多的其实是第三个动作修改。这里的“修改”又分三层每一层的改法完全不同。5.1 第一层修改数据——把别人的曲线变成你自己的版本最常见的修改是平移、缩放、截断、叠加偏置。比如你觉得原图的曲线趋势很好但幅值范围不适合你的数据就做线性变换% 在统一x网格上重采样原始数据 xq linspace(min(x), max(x), 500); yq interp1(x, y, xq, pchip); % 平移整条曲线上移0.3 y_shift yq 0.3; % 缩放幅值缩小到原来的80% y_scaled yq * 0.8; % 截断只保留前5个单位的区间 idx xq 5; x_cut xq(idx); y_cut yq(idx);修改之后的曲线要注意重新验证边界。尤其是你做了缩放和平移之后曲线可能超出原坐标范围需要重新设定ylim或者配合新的坐标体系一起改。另外学术场景下修改他人图像数据要特别注意用于学习、复现、对比是没有问题的但如果要发表最好说明数据来源或者征得原作者同意。论文图的数据是不能随便改到面目全非的一旦被认定为篡改数据问题会很严重。5.2 第二层修改样式——黑白打印风、PPT彩色风一键切换很多人不知道“改图”很多时候根本不需要碰数据只要换一套视觉风格就行。打印场景下期刊往往是黑白印刷这时候颜色不能作为区分曲线的唯一手段。我一般给每条线分配线型和标记styles {-, --, -., :}; markers {o, s, ^, d}; for i 1:4 plot(x, y(:, i), styles{i}, Marker, markers{i}, LineWidth, 1.5); hold on; end这样在灰度打印时靠线型就能区分四条曲线。PPT演示场景则相反可以用更饱和的彩色线条同时给数据点填充和线同色的“实心标记”视觉冲击力更强。如果想高亮曲线上的某个关键点比如峰值位置不要靠肉眼找坐标手标直接用find定位[peak_y, peak_idx] max(y); peak_x x(peak_idx); plot(peak_x, peak_y, o, MarkerSize, 10, MarkerFaceColor, r); text(peak_x, peak_y * 1.05, sprintf(峰值: %.2f, peak_y));注意text的位移量别写死最好相对于当前坐标范围设置不然坐标轴一缩放文字就跑到图外面去了。5.3 第三层修改坐标体系——线性轴、对数轴、自定义刻度有些函数曲线在线性坐标下平平无奇一换成对数坐标就立刻显出幂律关系。修改坐标体系是复现和展示时很常用的手段。% 把x轴和y轴都改成对数坐标 set(gca, XScale, log, YScale, log); % 自定义刻度位置 xticks([0.1, 1, 10, 100]); xticklabels({0.1, 1, 10, 100}); % 打开次网格辅助读数 grid on; set(gca, MinorGridOn, on, GridAlpha, 0.3);改坐标体系之前务必确认数据本身没有零和负数因为对数坐标无法显示非正值。如果数据里有零或负值要么取绝对值要么做平移要么只截取正区间。很多人在loglog图上发现曲线莫名其妙“断开”其实就是因为数据里混进了零或负值。坐标范围调整时注意不要让曲线贴边。ylim([y_min*0.9, y_max*1.1])这种留白手法虽然简单但效果非常明显。6. 常见问题与排查技巧实录这个工作流我用到现在踩过的坑比看过的教程还多。下面这几个问题几乎每次帮别人弄的时候都会遇到整理成速查表分享出来。6.1 提取出来的曲线毛刺严重怎么平滑都显得乱现象常见原因对应处理曲线锯齿严重取点太密且定位不准像素抖动被放大减少取点数量或者平滑窗口取小值平滑后趋势变了窗口过大把真实峰值抹平了改用pchip插值重采样不用强力滤波局部有毛刺但整体平滑图像本身有噪点特别是扫描图用smoothdata(y, lowess, 0.05)做局部回归平滑曲线突然跳变误把网格线当成曲线取了点回到原始图片检查跳变处手动修正我的经验是先用smoothdata的movmean窗口3~7试一下如果还不够顺再考虑是不是取点本身的问题。不要一上来就上个窗口为50的大平滑那会把曲线变成一根没有特征的香肠。% 温和平滑示例 y_smooth smoothdata(y, movmean, 5);6.2 导出的图片模糊、中文乱码、字体丢失导出图片清晰度不够90%的情况是分辨率设置太低。老式写法print -dpng默认只有72dpi放大一点就糊。现在统一用exportgraphics并且明确指定分辨率exportgraphics(gcf, final_figure.png, Resolution, 300);如果需要矢量图选择SVG格式exportgraphics(gcf, final_figure.svg, ContentType, vector);矢量格式插入Office文档后无论怎么缩放都不糊。中文乱码的问题根源在于字体映射。Windows下我一般设置set(gca, FontName, Microsoft YaHei);Linux或者Mac环境注意字体名不同SimHei在Linux上未必存在。如果目标平台字体不确定最稳妥的方案是用text标注替代图中文字或者导出SVG后在插图工具里补中文。还有一种思路是图表里全部使用英文或拼音标注彻底避开中文字体问题。6.3 取点总是偏问题多半出在“定标”而不是“取点”我见过很多用户反复取点结果还是不重合最后发现是定标出了问题。这里分享一个交叉验证的方法定标时x轴选两个跨度大的刻度点y轴同样。在图上再选第三个已知刻度点用定标公式反算它的真实值。如果推算值和真实值误差在1%以内说明定标正确超过2%就要检查是不是点错了刻度线的位置、是不是坐标轴类型选错、是不是截图经过了缩放。另外有个提高精度的细节取点时尽量放大图像再取。WebPlotDigitizer和MATLAB的imshow都支持局部放大在放大视图下取点像素定位误差会显著降低。我还习惯保存“原始像素坐标”而不是只保存换算后的数据。也就是说定标点和每一条曲线的原始像素坐标都单独存一份。这样下次需要用不同的定标方式重新换算时不用重新取点。这个习惯帮我省过不少事。最后再分享一点点个人习惯做图像提取和复现这个事技术本身不太难难的是让整个过程可追溯、可修改、可复用。我自己的目录结构通常是这样的fig_repro/ ├── source/ │ └── original_curve.png # 原始图像 ├── data/ │ ├── calibration.mat # 定标点像素坐标 │ └── curve_raw_pixel.mat # 原始像素坐标 ├── scripts/ │ ├── extract_curve_points.m │ ├── clean_and_fit.m │ └── prettyPlot.m └── output/ └── final_figure.png所有脚本开头都注明日期、用途和数据来源。半年后回头重新生成一张修改版基本十分钟内就能搞定。数据提取终究是为自己的学习、复现和二次创作服务的过程中如果用到别人的图最好在笔记里留下原始出处。这套工作流我用了很久每次帮人把一张“死图”变成可控的MATLAB脚本都会觉得当初把流程固化下来的决定很值得。希望这些经验也能帮你少走点弯路。
阅读完成 · 觉得有帮助?