☰
Matlab图像保存全攻略:从DPI到exportgraphics,告别模糊与白边
2026/10/27 7:50:04 网站建设 项目流程

1. 保存前的坑,比保存本身更值得细看

做Matlab的人大概都经历过这样一幕:跑了几个小时的仿真,调了半天图,终于画出一个满意的结果,然后点了一下菜单栏的“保存”,或者在命令行敲了个saveas,以为万事大吉。第二天打开图片一看——字小了、线糊了、白边宽得能当信封,更离谱的是,论文里要求300 DPI,你存出来的图放大一丁点全是马赛克。

其实Matlab保存Figure这件事,说简单也简单,说坑也真有不少坑。尤其是现在主流期刊和报告对图片分辨率、字体嵌入、排版尺寸都有明确要求,很多人不是不会画图,而是根本没搞明白“保存”这条路有多少种走法,各自有什么脾气。这篇文章我就把这么多年在Matlab里折腾保存图像的踩坑经历完整梳理一遍,从最基础的图形句柄概念,到几种保存方式的取舍,再到科研出图、批量处理、GUI集成这些场景,尽量做到看完了你就不用再搜第二个教程。

这个内容适合谁?刚接触Matlab、每次保存都用菜单点一下然后被图片质量气得够呛的新手;已经会做图但经常被导师或期刊编辑打回来说“图不清晰”“留白太多”“字体没嵌入”的科研党;还有做工程仿真、需要批量跑参数然后自动保存结果图的效率派。涉及的代码都是可以直接复制改一改就用的,没有一行花架子。

先聊一个最容易忽略的点:在按下保存之前,你要不要先看看你的图窗本身是什么状态?

2. 图窗设置决定上限,保存命令决定下限

2.1 认识Figure的“坐标系”

Matlab里每一个Figure窗口,本质上有两套坐标在同时工作:一套是你在屏幕上看到的像素坐标,一套是系统内部给打印和导出用的“纸张坐标”。很多人的图片保存出来不对,核心原因就是这两套坐标打架了。

屏幕坐标系很好理解,你在界面上看到的图窗大小、位置,是由屏幕像素决定的。而你最终保存出来的图片,它不看你屏幕长什么样,只看后台的PaperPosition和PaperSize这两个属性。Matlab默认情况下,图窗的屏幕尺寸和纸张尺寸是不一致的——图窗默认是560 x 420像素(大概),纸张尺寸默认却是8 x 6英寸。这个错位就会导致一个经典问题:你屏幕上看到的图很漂亮,保存下来比例却变了,字符被拉长或者压缩。

解决思路很简单:画图之前,先把图窗的屏幕大小和纸张大小统一起来。最省事的方法是把PaperPositionMode设为auto,让Matlab自动把当前屏幕上的图窗内容“1:1”映射到纸张坐标中。如果你对最终出图的物理尺寸有要求(比如论文要求双栏图宽度不能超过7厘米),那就手动指定。

我平时最常用的是这样一套初始化代码:

fig = figure('Units', 'pixels', 'Position', [100 100 800 600]); set(fig, 'Color', 'white', 'PaperPositionMode', 'auto');

这一段不直接保存图片,但它决定了后面所有保存方式输出的基础质量。Position里的四个数分别是图窗左上角的横纵坐标、宽度、高度。要注意的是,这里设800像素宽,如果后面要用print -r150保存,得到的图片实际像素尺寸就是800/96*150 = 1250像素左右,而不是800像素。很多初学者在这里会误判要输出的图片大小,后面我会专门讲这个换算关系。

2.2 DPI与尺寸的换算逻辑

说到分辨率,离不开DPI这个单位。DPI是Dots Per Inch,每英寸多少个点。Matlab图窗在屏幕上默认是96 DPI——Windows系统标准。当你用print命令指定-r300时,意思是按300 DPI输出纸质图片,每个英寸对应300个像素点。

那图片最终的像素尺寸怎么算?

图片输出像素 = 图窗的英寸尺寸 × DPI

假设你图窗宽度是800像素,在96 DPI屏幕上就是800 / 96 = 8.33英寸宽。用-r300保存后,宽度像素就是8.33 × 300 = 2500像素。这个数值用在Word或者LaTeX里排版时,按实际需要的宽度缩放,清晰度就够用了。

有个技巧:如果你已经知道目标图片精确的像素尺寸(比如投稿系统要求主图宽度至少1920像素),可以先反推DPI。

DPI = 目标像素 / 图窗英寸尺寸

比如图窗在屏幕上是10英寸宽(960像素),你希望输出2400像素宽的图片,那么DPI就应该是240。然后用print(gcf, 'myplot.png', '-r240')即可。这时候你就不会被惯用的150、300这些固定值给绑住,可以灵活应对各种投稿要求。

2.3 背景色和坐标轴不可忽视

保存的时候,背景色是个很容易翻车的点。Matlab默认图窗背景是白色,但坐标区(Axes)默认背景也是白色,这没问题。问题在于,如果你在代码里用了类似set(gca, 'Color', [0.95 0.95 0.95])的背景色,或者画了半透明Patch,保存出来很可能会出现两种效果不一致:屏幕上看着是浅灰背景,保存成矢量图却变成纯白。

这不是Matlab在偷懒,而是不同渲染方式对背景透明度的支持程度不同。遇到这种情况,我用一个笨但可靠的办法:保存前统一设定背景色。

set(gcf, 'Color', 'w'); set(gca, 'Color', 'w');

坐标轴本身也有不少“隐藏设置”会影响出图质量。比如Box属性决定坐标轴是否封闭成矩形,TickDir决定刻度线朝里还是朝外——期刊排版经常要求刻度朝外,TickLength控制刻度线长度。这些细节单看没有多重要,但组合到一起,就是“这张图看起来很专业”和“这张图看起来像随便画的”之间的区别。

3. 四种主力保存方式,选对工具再动手

3.1 saveas:最方便,也最粗糙

saveas可能是Matlab用户用得最多的保存命令,原因很简单——参数少,逻辑直白:目标图形句柄加文件名就能跑。

saveas(gcf, 'result.png'); saveas(gcf, 'result.pdf'); saveas(gcf, 'result.fig');

但方便是要付出代价的。saveas底层走的是模拟用户菜单点击的逻辑,它在生成位图时用的是Matlab内部默认的渲染参数,不会读取图窗的PaperPosition设置,所以经常出现保存出来的PNG和屏幕上看到的完全不一样,边缘留白也不受你控制。saveas适合快速临时存个图、内部交流看效果,不适合最终交付。

如果项目只是写个脚本跑通逻辑,后面可以随时重跑出图,那用saveas完全没问题。但如果你要一张能直接放进论文或汇报PPT的图,别用它。

3.2 print:最灵活的老牌选手

print是Matlab里历史最悠久、参数最丰富的导出方式。它起源于Matlab还很原始的年代,最初是用来把图输出到打印机的,后来演变成通用的图像导出命令。正因为它出身打印机,所以它的很多参数都围绕“纸张”这个概念来设计。

先看基础用法:

print(gcf, 'output.png', '-dpng', '-r300'); print(gcf, 'output.pdf', '-dpdf', '-r300'); print(gcf, 'output.eps', '-depsc2', '-r600');

拆开看这些参数:-d开头的是设备识别符,png就是PNG格式,pdf是PDF格式,epsc2是彩色EPS(Level 2兼容);-r是分辨率,单位是DPI。gcf是当前图形的句柄,如果存在多个窗口,记得把它替换成具体图形句柄,比如fig1。

print一个关键优势是,它尊重图窗的PaperPosition设置。利用这一点,可以实现精确控制输出图片的内容区域。比如我只想要图中间某一小块区域存出来,不需要整个坐标轴,那可以通过调整PaperPosition来实现:

set(gcf, 'PaperPositionMode', 'manual', 'PaperUnits', 'inches'); set(gcf, 'PaperPosition', [0 0 5 3]); % 左、下、宽、高(英寸) print(gcf, 'crop.png', '-dpng', '-r300');

这里PaperPosition的前两个值是图片左下角在纸张上的起点,后两个值是宽和高。把左上角设为0、宽高设为目标尺寸,相当于把纸张“裁”成指定大小,其他区域就不输出了。效果等同于一个内置的裁剪功能,比保存完之后再去PS里抠图要省事太多。

对于不同的输出格式,print还有一些细节区别。EPS格式尤其要注意:如果你在Linux或Mac下想生成可被LaTeX使用的EPS,建议用-depsc2而不是老掉牙的-depsc。前者对色彩和线条的支持更好,编译过程中出错的概率更低。

3.3 exportgraphics:新版本的速度之王

从R2020a开始,MathWorks推出了exportgraphics函数,它的设计目标很明确:取代saveas和print的大部分使用场景,让用户用一条命令就能得到无白边、尺寸正确的结果。

exportgraphics(gcf, 'result.png', 'Resolution', 300); exportgraphics(gca, 'axes_only.png', 'Resolution', 300);

最爽的一点是,exportgraphics默认保存坐标区内容时,会自动裁掉白边,输出内容刚好从坐标轴边框边缘算起。对于那种不想留一丝多余空白的图片,这个默认行为非常省心。

另一个亮点是它支持直接对坐标区gca进行导出,而不是必须整个图窗。比如你在一张图上并排画了几个子图,想单独把左上角那个子图保存成独立图片,以前用print得手动框选区域,现在exportgraphics(gca)一句话就搞定了。

不过有个限制要提醒:exportgraphics对于某些老式渲染模式(比如OpenGL下的特定透明对象)支持得不完美。如果你画了特别复杂的透明Patch或用了特殊渲染器,保存后可能出现纹理丢失或者颜色偏移。我会在后面的常见问题里详细说这个。

3.4 getframe与imwrite:兜底的黑科技

有一种情况,上面三种方式都不好使,那就是你需要对图像结果做进一步处理(比如加个水印、拼接图),或者你要把当前图窗内容当数据用,而不是当文件存。这时候getframe加imwrite的组合就派上用场了。

frame = getframe(gcf); imwrite(frame.cdata, 'screenshot.png');

getframe的作用是把当前图窗内容抓取为一张图像数据,存到frame.cdata里。这个数据就是一个普通的三维矩阵(如果RGB图的话是宽度×高度×3),你可以任意操作它,比如翻转、加文字、拼接,最后再用imwrite写盘。

这里有个细节值得注意:getframe抓取的像素大小和屏幕显示完全一致,不会像print -r300那样按DPI放大。所以如果你想抓取高分辨率图,先用set(gcf, 'Position', ...)把窗口调大,再抓帧。

set(gcf, 'Position', [100 100 1920 1080]); drawnow; frame = getframe(gcf); imwrite(frame.cdata, 'hd_result.png');

为什么要加drawnow?因为Matlab的绘图是异步更新的,窗口位置大小改了,但画面还没重绘,你立刻抓帧会抓到一张旧尺寸的图。drawnow强制刷新图形管线,确保后续抓取的内容是最新状态。这个坑我踩过一次,当时批量出图时所有图片宽高都不对,排查半天发现就是漏了drawnow。

3.5 四种方式怎么选

把这四种方式放在一起对比一下,各自的定位就一目了然:

保存方式适用场景优点缺点
saveas临时存图、过程记录代码简单、格式齐全白边不可控、DPI无法精细设置
print论文投稿、出版级出图参数丰富、支持EPS、兼容老版本命令参数稍长,需理解纸张坐标系
exportgraphicsR2020a及以后版本日常使用自动裁剪白边、支持坐标区导出部分旧渲染效果可能丢失
getframe+imwrite图像后处理、截取当前窗口拿到图像原始数据分辨率受屏幕限制,不适合高质量出图

我的日常思路是:如果不是正式交付,随便存,saveas手上过;一旦要放进论文、报告、PPT或者交付给客户,优先考虑exportgraphics,如果格式需要EPS或老版本兼容,再退回print。这个选择逻辑在实战中基本覆盖了所有场景。

4. 进阶实战:从出版级单图到批量自动出图

4.1 出版级图片的完整模板

有经验的科研工作者基本都有一套自己固定的出图模板,每次画完图往里面一套,就能得到符合期刊要求的图。我在这里分享一套我自己用了很久的组合:

function export_figure(fig, filename) set(fig, 'Color', 'w'); set(fig, 'PaperPositionMode', 'auto'); set(fig, 'InvertHardCopy', 'off'); % 统一坐标轴风格 ax = findobj(fig, 'Type', 'axes'); for i = 1:length(ax) set(ax(i), 'LineWidth', 1.0, 'FontName', 'Helvetica'); set(ax(i), 'FontSize', 10, 'Box', 'on'); set(ax(i), 'TickDir', 'out', 'TickLength', [0.02 0.02]); end % 按格式导出 [~, ~, ext] = fileparts(filename); switch lower(ext) case '.png' exportgraphics(fig, filename, 'Resolution', 300); case '.pdf' exportgraphics(fig, filename, 'ContentType', 'vector'); case '.eps' print(fig, filename, '-depsc2', '-r600'); otherwise saveas(fig, filename); end end

这函数做了几件事:强制白色背景、关掉硬拷贝反色、统一坐标轴线条宽度和字体,最后根据扩展名选择正确的导出方式。字体设为Helvetica是因为它在绝大多数系统和期刊模板中通用性最高,不容易出现字体替换问题。InvertHardCopy这个属性值得单独提一下——它的默认值是on,意思是如果你图窗背景是黑色,保存时会自动反色成白色背景,但同时也可能把你图上本来就是黑色的文字、线条一起反成了白色,导致整张图报废。设为off可以从源头杜绝这个诡异行为。

如果你想让图片在LaTeX中更协调,可以不用Helvetica而改用Computer Modern字体,但这需要系统中装了对应字体,否则Matlab会静默替换成默认字体,反而更乱。所以我个人偏好Helvetica,稳妥优先。

4.2 批量仿真结果的自动保存

做参数扫描或者蒙特卡洛仿真的场景里,经常会遇到这样的需求:跑20组参数,每组生成一张图,统一保存到results文件夹里,命名格式按参数编号。手动一张一张存显然不现实,用脚本自动跑才是正解。

for k = 1:20 % 假设simulate(k)会返回一个绘图句柄 fig = figure('Visible', 'off'); % 后台画图,不弹窗 plot_results(simulate(k), k); filename = sprintf('results/result_%02d.png', k); exportgraphics(fig, filename, 'Resolution', 300); close(fig); end

这里的核心技巧是'Visible', 'off'。如果不关掉图窗的可见性,批量跑的时候屏幕上会快速闪烁20个窗口,既碍眼又拖慢速度。后台绘图虽然看起来“没在画”,但所有渲染流程都会正常执行,最终图片效果和可见模式完全一样。

另一处需要注意的细节是close(fig)。循环里每次新建图窗,如果不用close关闭,20次循环就会累积20个隐藏图窗。虽然肉眼看不见,但它们会占用内存,跑完几百次循环时内存就已经爆炸了。随手关闭是一个好习惯。

如果脚本中途报错导致某个图窗没被关闭,最彻底的清理方式是close all。但要注意,close all会把所有图形窗口都关掉,包括之前手动打开用于调试的图窗,调试多图时慎用。

4.3 GUI里保存图片与自定义路径

做Matlab App或GUI工具的时候,经常要实现一个“导出图像”按钮,让用户自己选择保存路径和文件名。这里推荐用uigetfile来弹出系统原生保存对话框,而不是简单写死路径:

[file, path] = uiputfile({'*.png', 'PNG 图片'; '*.pdf', 'PDF 文档'; '*.eps', 'EPS 矢量图'}, '保存图像', 'result.png'); if isequal(file, 0) return; % 用户点了取消 end filename = fullfile(path, file); exportgraphics(app.UIFigure, filename, 'Resolution', 300);

isequal(file, 0)的判断很关键。很多新手直接跳过取消分支,结果用户一按取消,程序就报错或者用空文件名覆盖了默认文件。处理了这个分支之后,工具就会像Windows里“另存为”一样友好。

另外,如果你使用的是App Designer,注意导出的目标是app.UIFigure,而不是gcf。因为在App Designer里,App界面本身就是一个Figure,gcf不一定返回正确的句柄。这种情况下必须先拿到UIFigure句柄再传给导出函数。

4.4 多个子图合并导出

还有一种不那么常见但很实用的场景:你画了好几个独立的Figure,每个Figure里都是不同的数据集,现在想把这些图拼成一张大图,用于汇报或对比。

以前的做法是复制粘贴到PPT里再排版,但其实Matlab里可以直接用tiledlayout或者subplot重新组织:

fig_combined = figure('Position', [100 100 1200 800]); tiledlayout(2, 2); nexttile; % 重新绘制第一份数据 plot_data_1(); nexttile; % 重新绘制第二份数据 plot_data_2(); exportgraphics(fig_combined, 'combined.png', 'Resolution', 300);

如果你的原始图不是由数据重新绘制的,而是已经保存成了图片文件,那可以用imread+subplot+imshow将它们重新拼起来:

img1 = imread('result_01.png'); img2 = imread('result_02.png'); fig = figure('Position', [100 100 1200 500]); subplot(1, 2, 1); imshow(img1); subplot(1, 2, 2); imshow(img2); exportgraphics(fig, 'side_by_side.png', 'Resolution', 300);

这种做法的好处是原始图像可以来自不同机器、不同时间的仿真结果,拼接后统一分辨率输出,适合做结果汇报时横向对比不同方案的效果。

5. 高频问题排查:那些贴脸开大但几乎人人中招的坑

5.1 保存出来的图模糊,字看不清

这个问题出现频率最高。如果你保存PNG后发现图中的文字和线条边缘有锯齿、放大后模糊不清,最常见的原因是分辨率设置过低。屏幕默认显示是96 DPI,如果你用saveas保存,得到的PNG很可能就是按96 DPI生成的,打印或者放大后当然模糊。

解决办法也很直接:要么用print指定DPI,比如print(gcf, 'fig.png', '-dpng', '-r300');要么用exportgraphics的Resolution参数设到300或更高。如果是矢量格式(PDF、EPS),则不存在分辨率问题,因为矢量图由数学曲线构成,输出设备有多高分辨率它就能渲染多清晰。所以如果条件允许,论文投稿首选矢量图。

5.2 白边问题:明明画的是图,出来了半张A4纸

白边主要分两种:一种是图窗整体外部的大面积白边,一种是坐标轴内部数据区周围的多余空白。

第一种场景,利用PaperPositionMode,auto可以基本解决——保存时让纸张尺寸自动等于图窗内容尺寸:

set(gcf, 'PaperPositionMode', 'auto');

第二种场景,坐标轴内部的多余空白,就要调整坐标轴的Position或TightInset。如果不想手动调数值,一个比较暴力的方式是:

ax = gca; outerpos = ax.OuterPosition; ti = ax.TightInset; left = outerpos(1) + ti(1); bottom = outerpos(2) + ti(2); ax_width = outerpos(3) - ti(1) - ti(3); ax_height = outerpos(4) - ti(2) - ti(4); ax.Position = [left bottom ax_width ax_height];

这段代码的效果是让坐标区填满图窗内部,把不必要的边距压缩到最小。它依据的是TightInset属性,这个属性记录了坐标轴边框到周围文字标签(比如刻度数字、轴标签)之间的实际距离。把坐标轴位置向外扩到这个距离的边缘,剩下的空白自然就没了。

5.3 中文显示成方框

这是一个很多中国用户都会撞上的经典问题。默认的Helvetica字体不包含中文字符,当图里有中文标题或坐标轴标签时,保存出来的图片里中文很可能变成一个个方框,或者干脆变成乱码。

saveas对这个问题的处理尤其差,因为它用的是老旧的导出管线。我的避坑办法是一般用exportgraphics或print,再加上在画图后显式指定中文字体:

set(gca, 'FontName', 'SimHei'); % Windows 下黑体 % 或者 set(gca, 'FontName', 'Microsoft YaHei'); % 微软雅黑

另外,最好在代码的早期就设置:

set(0, 'DefaultAxesFontName', 'SimHei'); set(0, 'DefaultTextFontName', 'SimHei');

这样后面所有的坐标轴和文字标签默认就会用中文字体,不用每个图都手动设置。在Linux下,如果做了容器部署,可能还需要装fonts-wqy-zenhei这类中文字体包,否则Matlab即使指定了中文字体名也找不到字。

5.4 EPS文件在LaTeX里编不过或打开报错

EPS在LaTeX中配合Ghostscript使用比较多。如果你生成的EPS在LaTeX编译时报错,先检查生成命令是不是用了-depsc2或-depsc。有些Matlab老版本里,如果你用saveas直接存EPS,生成的可能是封装不完整或带有额外注释的版本,兼容性差。

一套稳健的生成流程是:

print(gcf, 'figure.eps', '-depsc2', '-r600', '-tiff');

这里-tiff不是输出TIFF文件,而是在EPS内部嵌入一个TIFF格式的预览图。这样在Windows下直接用图片查看器双击EPS时能看到预览内容,虽然LaTeX编译不会用到这个预览,但日常查看方便了很多。

如果LaTeX编译仍然报错,一个常见的补充手段是把EPS转化为PDF再导入:

print(gcf, 'figure.pdf', '-dpdf');

因为绝大部分现代LaTeX编译器对PDF的支持远比EPS好,pdflatex可以直接使用PDF格式的图片。很多期刊表面上要求EPS,实际上你上传PDF或者转换后的TIFF都可以。

5.5exportgraphics导出透明对象时颜色丢失

用exportgraphics处理含Patch对象的图时,我遇到过颜色变淡的情况。排查后发现,这是因为exportgraphics默认采用Painter渲染器,对于半透明的Patch对象,Painter会用叠加策略模拟透明度,结果和屏幕上OpenGL渲染出来的效果有偏差。

解决方式是先把渲染器切换到OpenGL再导出:

fig.Renderer = 'opengl'; exportgraphics(fig, 'output.png', 'Resolution', 300);

或者试试使用print命令导出,因为print对OpenGL渲染对象的处理相对更成熟。

5.6 保存的图颜色和屏幕上看到的完全不一样

这种问题往往很迷惑人——屏幕上看是鲜亮的蓝色,保存出来却像褪了色一样。原因多半是色彩配置文件不一致。Matlab在Mackintosh或者特定GPU驱动下的颜色管理,和普通Windows系统的颜色管理有很大出入。

如果这个差异出现在不同电脑之间,一种实际的解决方案是,只把同一台电脑上屏幕显示与保存结果做一次校准——以保存结果为准,通过调整图形的Color属性来补偿色差。另一个思路是保存RGB色彩较多的格式时,用无损PNG代替有损JPG,因为JPG的压缩算法会在颜色上造成可见偏差。

5.7 常见问题速查表

现象首选排查方向推荐处理
图模糊、边缘锯齿DPI太低用print -r300或exportgraphics Resolution,300
白边过多PaperPositionMode设置设为auto或手动裁剪PaperPosition
中文变方框字体不含中文显式指定中文字体,用exportgraphics导出
EPS编译失败EPS格式版本改-depsc2,或转PDF再编译
透明对象颜色异常渲染器兼容性切换Renderer为opengl,或改用print
颜色偏移色彩管理用PNG无损格式,校准色差

6. 一个真正好用的经验:保存之前先跑一次自检

文章的最后一Part,分享一个可能很多人不太注意但真正影响出图质量的个人习惯:每次大批量出图之前,我先在命令行快速跑一段自检脚本,检查当前环境的图形后端、可用字体、默认分辨率等关键配置。

disp(['图形后端: ' matlab.graphics.internal.getGraphicsBackend]); disp(['默认分辨率: ' num2str(get(0, 'ScreenPixelsPerInch'))]); disp(['可用字体: ']); listfonts

getGraphicsBackend能看到当前Matlab用的是OpenGL还是其他后端。若后端有问题,保存复杂图形时容易出现渲染错误。ScreenPixelsPerInch就是前面提到的96 DPI的具体数值来源,如果这个值变了,你所有按DPI推算的像素尺寸都要重新算。listfonts则用来确认当前系统有哪些字体,尤其检查中文字体是否安装。

这个自检流程看起来很简单,但它可以一眼定位很多看似诡异的问题根源。比如如果你发现渲染后端是Painters,而你又画了带阴影的Patch对象,那出图颜色异常几乎是可以预见的,直接换成OpenGL后端就好。

保存图像从来不只是“把图存到硬盘”这个动作。真正决定图片质量的,是你在保存前对图窗、坐标轴、字体、DPI所做的全部设置。这些设置理解得越透,出图的自由度就越大——你不再是被动地“点击保存那个按钮”,而是主动控制每一条线、每一块颜色、每一个像素的输出方式。下次再有人问“Matlab怎么保存图片不模糊”,你可以直接反手甩给他这篇文章,然后告诉他:先看图窗设置,再谈保存命令。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询