简介:这是一份面向科研人员与图像处理学习者的MATLAB曲线识别工具包,用于从论文截图或实验图像中自动提取曲线坐标,省去手工描点或重写绘图代码的麻烦。压缩包共14个文件,约459KB,包含12个png示例图片、1个mat数据文件和1个m脚本文件,图片用于测试识别效果,脚本与数据文件则承载核心算法与结果存储。内容覆盖图像预处理、边缘检测、轮廓追踪、曲线拟合、数据绘图、文件读写及GUI交互等环节,并涉及算法优化思路,可帮助读者理解从灰度化、二值化到坐标提取的完整流程。已有2019人学习下载,适合需要对比分析不同论文曲线、快速复现图形数据的中高级MATLAB用户参考,也可作为图像处理课程的实践素材。
1. 曲线图片识别程序:从截图到数据表的完整还原
手里拿到一张论文截图或者实验报告照片,上面画着几条曲线,想把数据点抠出来重新画图或者做拟合,这事我干过不止一次。手动描点?几十个点还能忍,几百个点纯属折磨。这个基于 MATLAB 的曲线图片识别程序就是解决这个问题的:输入一张包含曲线的图片,输出曲线对应的坐标数据。它适合做实验数据分析的学生、需要复现文献图表的工程师,以及任何手里只有图片没有原始数据的人。整个项目不依赖深度学习工具箱,纯图像处理加坐标映射,MATLAB 基础版本就能跑,对新手相当友好。下面从架构到代码逐层拆开讲。
2. 项目架构与运行准备:文件结构、依赖与启动流程
2.1 目录结构与模块划分
这个项目的代码量不大,但模块划分清晰。拿到源码后,先看目录结构,心里有个数再动手跑。
curve_recognition/ ├── main.m % 主入口,串联整个流程 ├── preprocess.m % 图像预处理:灰度化、二值化、去噪 ├── extract_curve.m % 曲线提取:分离曲线像素与坐标轴 ├── calibrate_axis.m % 坐标轴校准:像素坐标到数据坐标的映射 ├── output_data.m % 数据输出:保存为 mat 或 csv ├── utils/ │ ├── remove_grid.m % 去除网格线 │ └── smooth_curve.m % 曲线平滑处理 └── samples/ ├── sample1.png % 示例图片 └── sample2.png主入口main.m负责按顺序调用各模块,每个模块只做一件事。这种设计的好处是,如果你只想替换其中某一步——比如用自己的二值化算法——直接改对应文件就行,不会牵一发动全身。常见做法是把所有逻辑塞进一个脚本,但那样后期维护会很痛苦,这个项目的拆分方式值得借鉴。
2.2 环境要求与依赖检查
运行前确认 MATLAB 版本。项目用到的主要函数包括imread、rgb2gray、imbinarize、bwareaopen、regionprops,这些在 R2016b 及以上版本都支持。如果用的是更早的版本,imbinarize需要换成im2bw,注意一下就行。
不需要额外工具箱。Image Processing Toolbox 是必须的,但大多数 MATLAB 安装都自带。在命令行输入ver可以查看已安装的工具箱列表,确认里面有 Image Processing Toolbox 即可。
启动方式很简单,在 MATLAB 中把当前文件夹切换到项目根目录,然后运行:
% 启动主程序,传入图片路径 main('samples/sample1.png');如果一切正常,命令窗口会输出识别到的曲线数量和数据点个数,同时在当前目录生成result.csv。第一次跑建议用自带的示例图片,确认流程通了再换自己的图。
2.3 主流程代码逐段解析
main.m是整个项目的骨架,理解它就理解了整个处理链路。下面逐段拆解。
function main(imagePath) % 读取图片并转为灰度图 img = imread(imagePath); if size(img, 3) == 3 gray = rgb2gray(img); else gray = img; end % 预处理:二值化 + 去噪 bw = preprocess(gray); % 提取曲线像素(排除坐标轴和网格) [curvePixels, axisInfo] = extract_curve(bw); % 坐标轴校准,建立像素到数据的映射 calibration = calibrate_axis(axisInfo); % 将像素坐标转换为数据坐标 dataPoints = cell(length(curvePixels), 1); for i = 1:length(curvePixels) px = curvePixels{i}(:, 1); py = curvePixels{i}(:, 2); [dx, dy] = pixel2data(px, py, calibration); dataPoints{i} = [dx, dy]; end % 输出结果 output_data(dataPoints, 'result.csv'); end这段代码的逻辑链条是:读图 → 预处理 → 提取曲线 → 校准坐标轴 → 坐标转换 → 输出。每一步的输出是下一步的输入,数据流单向传递,没有循环依赖。
参数说明:imagePath是图片路径,支持 png、jpg、bmp 等常见格式。curvePixels是一个 cell 数组,每个元素对应一条曲线的像素坐标集合。calibration结构体存储了坐标轴原点和缩放比例,后面坐标转换要用。
有个细节值得注意:代码里判断了图片是否为三通道,如果是彩色图先转灰度。这一步看似简单,但如果跳过,后续二值化处理彩色图会出问题。我见过有人直接对 RGB 图做imbinarize,结果报错都不知道为什么。
3. 核心模块拆解:预处理、曲线提取与坐标校准
3.1 图像预处理:二值化阈值怎么选
预处理的目标是把曲线从背景中分离出来,变成黑白分明的二值图。核心是阈值选择。
function bw = preprocess(gray) % 使用 Otsu 方法自动确定阈值 level = graythresh(gray); bw = imbinarize(gray, level); % 反转:让曲线为白色,背景为黑色 bw = ~bw; % 去除小面积噪点(小于 30 像素的连通区域) bw = bwareaopen(bw, 30); % 填充曲线内部可能出现的空洞 bw = imfill(bw, 'holes'); endgraythresh用的是 Otsu 算法,它会自动找到一个让前景和背景类间方差最大的阈值。对于大多数曲线图,这个自动阈值够用了。但如果图片光照不均——比如手机拍屏幕产生的反光——自动阈值可能翻车,这时候需要手动指定:
% 手动指定阈值,适用于光照不均的情况 bw = imbinarize(gray, 0.6);bwareaopen去除小面积连通区域,参数 30 是面积阈值。如果你的图片分辨率很高,噪点面积也大,这个值要相应调大。反过来,如果曲线本身很细,调太大会把曲线也去掉。我一般会先跑一遍看看二值化结果,用imshow(bw)确认曲线完整、噪点干净,再继续往下走。
imfill填充空洞是为了处理曲线较粗时二值化后内部出现空心的情况。如果曲线本身就很细,这一步可以跳过,不影响结果。
3.2 曲线提取:分离坐标轴与网格线
二值化之后,图里有曲线、坐标轴、刻度线、网格线,需要把曲线单独拎出来。这一步是整个项目最容易出问题的地方。
function [curvePixels, axisInfo] = extract_curve(bw) % 通过 Hough 变换检测直线,识别坐标轴 [H, T, R] = hough(bw); P = houghpeaks(H, 10); lines = houghlines(bw, T, R, P); % 找出最长的水平线和垂直线作为坐标轴 axisInfo = find_axes(lines, size(bw)); % 从二值图中移除坐标轴区域 bw_noaxis = remove_axes(bw, axisInfo); % 移除网格线(细的周期性强直线) bw_nogrid = remove_grid(bw_noaxis); % 连通区域标记,每个区域是一条曲线 [L, num] = bwlabel(bw_nogrid); curvePixels = cell(num, 1); for i = 1:num [rows, cols] = find(L == i); curvePixels{i} = [cols, rows]; % 注意:x 是列,y 是行 end endHough 变换用来检测直线,坐标轴通常是图中最长的水平线和垂直线。houghpeaks找峰值,houghlines把峰值转成线段。find_axes是自定义函数,逻辑是遍历所有检测到的直线,找长度最长且接近水平/垂直的那两条。
移除坐标轴和网格线之后,剩下的连通区域就是曲线。bwlabel做连通区域标记,每个标记对应一条曲线。这里有个坑:如果两条曲线交叉,它们会被标记为同一个连通区域。解决办法是在交叉点做分割,但实现起来比较复杂。常见做法是假设曲线不交叉,或者手动指定曲线数量后用其他方法分离。
curvePixels里存的是像素坐标,注意 x 是列号,y 是行号,这和 MATLAB 矩阵索引一致,但和常规笛卡尔坐标系的 xy 相反。后面坐标转换时要留意。
3.3 坐标轴校准:像素坐标到数据坐标的映射
提取到曲线像素后,这些坐标还是像素值,需要映射到实际数据坐标。映射关系是线性的:数据坐标 = (像素坐标 - 原点像素坐标) / 缩放比例。
function calibration = calibrate_axis(axisInfo) % axisInfo 包含坐标轴原点和刻度信息 % 假设已知 x 轴范围 [xmin, xmax] 和 y 轴范围 [ymin, ymax] % 原点像素坐标(左下角) origin_px = axisInfo.origin; % x 轴方向缩放:每像素对应多少数据单位 x_range = axisInfo.xmax - axisInfo.xmin; x_pixels = axisInfo.x_axis_length; calibration.x_scale = x_range / x_pixels; % y 轴方向缩放 y_range = axisInfo.ymax - axisInfo.ymin; y_pixels = axisInfo.y_axis_length; calibration.y_scale = y_range / y_pixels; calibration.origin = origin_px; calibration.xmin = axisInfo.xmin; calibration.ymin = axisInfo.ymin; end function [dx, dy] = pixel2data(px, py, cal) % 像素坐标转数据坐标 dx = cal.xmin + (px - cal.origin(1)) * cal.x_scale; dy = cal.ymin + (cal.origin(2) - py) * cal.y_scale; % y 轴翻转 end这里的关键参数是axisInfo里的坐标轴范围。程序怎么知道 x 轴是从 0 到 10 还是从 0 到 100?两种方式:一是手动输入,在运行前告诉程序;二是自动识别刻度标签,用 OCR 读取。这个项目默认用手动输入,因为 OCR 识别刻度数字的准确率不稳定,手动输入反而更可靠。
pixel2data里 y 轴做了翻转,因为图像坐标系原点在左上角,y 向下增大,而数据坐标系 y 向上增大。这个细节如果搞错,出来的曲线是上下颠倒的,而且不容易发现。
提示:坐标轴范围一定要在运行前确认清楚。如果图片里 x 轴标注的是 0 到 5,你按 0 到 10 输入,所有数据点都会差一倍。
4. 避坑与常见问题:跑不通时先查这几处
4.1 二值化后曲线断裂
现象:imshow(bw)看到曲线中间断成几截,连通区域数量比实际曲线多。
原因:阈值选得偏高,曲线颜色较浅的部分被当成背景。或者图片压缩太厉害,曲线边缘模糊。
解决:降低阈值,或者改用自适应阈值imbinarize(gray, 'adaptive')。如果曲线断裂不严重,可以用形态学闭运算连接:
% 闭运算连接断裂处 se = strel('disk', 2); bw = imclose(bw, se);4.2 坐标轴识别错误
现象:程序把网格线当成了坐标轴,或者坐标轴没检测到。
原因:Hough 变换的参数不合适,或者图片里坐标轴不是标准直线(比如手绘图)。
解决:调整houghpeaks的峰值数量,或者手动指定坐标轴位置。在find_axes里加一个判断:坐标轴应该在图像边缘附近,不在中间的直线大概率是网格。
4.3 多条曲线被合并成一条
现象:输出只有一条曲线,但图里明明有好几条。
原因:曲线交叉导致连通区域合并,或者曲线之间距离太近,二值化后粘在一起。
解决:如果曲线交叉,需要在交叉点分割。简单做法是按 x 坐标分段,每段单独处理。如果曲线粘连,先做腐蚀操作分离,再提取。
4.4 输出数据点顺序混乱
现象:csv 里数据点的 x 坐标不是递增的,画出来是乱线。
原因:find函数返回的像素坐标按行扫描顺序排列,不是按 x 排序。
解决:提取后按 x 坐标排序:
% 按 x 坐标排序 [~, idx] = sort(curvePixels{i}(:, 1)); curvePixels{i} = curvePixels{i}(idx, :);4.5 图片分辨率太低导致精度差
现象:识别出来的数据点很少,曲线画出来是折线。
原因:低分辨率图片里曲线只占几个像素宽,提取到的点自然少。
解决:换高分辨率图片,或者在预处理时做上采样。但上采样不会增加信息量,只是让曲线看起来平滑,实际精度还是受原图限制。最好的办法是找到原始矢量图或者高分辨率版本。
5. 二次开发与改造:从能跑到好用
5.1 批量处理与自动化
原始代码一次只处理一张图,实际用的时候往往有一批图要处理。改造思路是加一个批量入口:
function batch_process(folderPath) % 获取文件夹下所有图片 files = dir(fullfile(folderPath, '*.png')); for i = 1:length(files) imagePath = fullfile(folderPath, files(i).name); try main(imagePath); % 重命名输出文件,避免覆盖 [~, name, ~] = fileparts(files(i).name); movefile('result.csv', [name, '_result.csv']); catch e fprintf('处理 %s 失败:%s\n', files(i).name, e.message); end end end这个改造的关键是错误处理。批量处理时某张图失败不能影响后面的,用 try-catch 包起来,记录失败文件名,事后单独排查。
5.2 增加 GUI 交互
命令行传参对不熟悉 MATLAB 的人不友好。可以用 App Designer 做一个简单界面:选择图片、输入坐标轴范围、点击运行、显示结果预览。核心代码不用改,只是包一层界面。MATLAB 的uigetfile和uieditfield组件够用了。
5.3 提高曲线分离的鲁棒性
前面提到曲线交叉会导致合并。一个可行的改进是用颜色信息分离曲线。如果原图是彩色的,不同曲线颜色不同,可以在预处理阶段按颜色通道分离,而不是转成灰度图。这样即使曲线交叉,也能按颜色区分开。
% 按颜色通道分离曲线 r = img(:, :, 1); g = img(:, :, 2); b = img(:, :, 3); % 红色曲线 red_curve = (r > 150) & (g < 100) & (b < 100);这个方法的局限是要求曲线颜色差异明显,且背景干净。如果图片质量差,还是得回到灰度处理加手动分割的老路。
5.4 数据平滑与拟合
提取到的数据点往往有噪声,直接画图会有毛刺。可以在输出前加一步平滑:
% 移动平均平滑 windowSize = 5; smoothed = movmean(dataPoints{i}(:, 2), windowSize);但平滑会改变数据,如果后续要做精确拟合,建议保留原始数据,平滑只用于可视化。我一般会输出两份:一份原始数据,一份平滑后的,让用户自己选。
5.5 验证识别精度的方法
改完代码怎么知道识别准不准?一个实用的办法是:用 MATLAB 自己画一张已知数据的曲线图,保存成图片,再用识别程序跑一遍,对比输出数据和原始数据的差异。如果误差在可接受范围内,说明流程没问题。
% 生成测试图片 x = linspace(0, 10, 100); y = sin(x); plot(x, y); saveas(gcf, 'test_curve.png'); % 用识别程序处理,对比结果 % 理想情况下,识别出的数据应该和 x, y 高度吻合这个验证方法我每次改完核心算法都会跑一遍。有一次改了二值化阈值,测试图识别正常,但换了一张真实实验图就翻车了——因为测试图太干净,没有噪声和网格线。从那以后我每次改完都强制走一遍真实图片的验证流程,再也不敢只拿理想图测试了。
希望这些改造思路帮到你,拿到源码后先跑通示例,再按自己的需求逐步调整。
本文还有配套的精品资源,点击获取