简介:这份资源聚焦图形模式识别中的基础几何形状检测,面向正在学习数字图像处理、计算机视觉或MATLAB编程的本科生、研究生及工程技术人员,帮助解决圆形、正方形与矩形自动识别与分类的算法实现问题。压缩包共218个文件,约1.07MB,以111个bmp样本图像为核心数据,配合29个cpp源文件、32个头文件及工程配置、资源脚本等,构成一套可直接编译运行的完整工程,便于读者对照代码理解形状特征提取与分类流程。资源中已积累2661人学习下载,说明其在同类基础视觉任务中具有较高的参考价值。读者可从中获取形状识别算法的完整实现思路、样本图像组织方式以及工程目录结构,适合作为课程设计、毕业设计或入门视觉项目的参考素材,也可在此基础上替换样本、调整参数以适配自己的识别需求。
1. 图形模式识别入门:用 MATLAB 把圆、正方形、矩形从图像里认出来
工业分拣线上,摄像头拍下一堆零件,圆垫片、方垫片、长条料混在传送带上,人眼分得清,但让程序自动分类,很多人第一反应是上深度学习。其实在形状规整、背景可控的场景里,MATLAB 的图形模式识别用几十行代码就能跑通,圆、正方形、矩形这三类几何形状的区分,靠的是区域几何特征而不是像素级纹理。这套方案适合产线质检、教学演示、零件计数这类需求,输入是一张灰度图或二值图,输出是每个连通区域的形状标签。它不依赖训练数据,不需要 GPU,参数可解释,调起来心里有数。下面把我实际做过的流程拆开讲,从预处理到特征提取再到分类判定,每一步都给出可复现的代码和参数含义。
2. 图像预处理与二值化:把形状从背景里干净地切出来
形状识别翻车,十有八九不是分类器的问题,而是二值化没做好。背景有反光、零件有阴影、边缘有毛刺,这些都会让后续的圆形度、矩形度计算失真。我一般把预处理拆成四步:灰度化、去噪、二值化、形态学清理。每一步都有对应的 MATLAB 函数,参数需要根据实际图像调整,没有万能值。
2.1 灰度化与去噪的选型理由
彩色图像先转灰度,用rgb2gray。如果图像本身就是灰度图,跳过这步。去噪用中值滤波medfilt2,窗口大小一般取 3×3 或 5×5。为什么不用高斯滤波?高斯会把边缘模糊掉,而形状识别恰恰依赖边缘的锐利程度。中值滤波在去除椒盐噪声的同时能保留边缘,这是血泪经验——早期我用高斯,圆形度算出来总是偏低,后来换成中值就稳了。
% 读取图像并转灰度 img = imread('parts.png'); if size(img, 3) == 3 gray = rgb2gray(img); else gray = img; end % 中值滤波去噪,窗口3x3 gray = medfilt2(gray, [3 3]); % 显示预处理结果 figure; imshow(gray); title('去噪后的灰度图');medfilt2的第二个参数是窗口尺寸,必须是奇数。3×3 适合噪声不严重的图,5×5 适合噪声密集但形状较大的图。窗口越大,计算越慢,边缘保留能力越弱,一般不超过 7×7。
2.2 二值化阈值的选择与自适应方法
二值化是分水岭。全局阈值imbinarize配合graythresh(Otsu 法)在光照均匀时够用,但产线图像往往有明暗渐变,这时候必须用自适应阈值imbinarize(gray, 'adaptive')。自适应阈值有两个关键参数:Sensitivity和NeighborhoodSize。前者控制阈值偏移,默认 0.5,值越大越容易把暗区判为前景;后者是局部窗口大小,一般取图像短边的 1/8 到 1/4。
% 全局阈值(光照均匀时用) level = graythresh(gray); bw_global = imbinarize(gray, level); % 自适应阈值(光照不均时用) bw_adaptive = imbinarize(gray, 'adaptive', ... 'Sensitivity', 0.45, ... 'NeighborhoodSize', 2*floor(size(gray,1)/16)+1); % 对比显示 figure; subplot(1,2,1); imshow(bw_global); title('全局阈值'); subplot(1,2,2); imshow(bw_adaptive); title('自适应阈值');NeighborhoodSize必须是奇数。如果图像是 640×480,短边 480,除以 16 得 30,取奇数 31。这个窗口太小会把噪声当形状,太大则退化成全局阈值。我一般先用默认值跑一遍,看二值图里形状是否完整、背景是否干净,再微调。
2.3 形态学清理:填洞、去小面积、平滑边缘
二值化后常见三类脏东西:形状内部有空洞、背景有零星噪点、边缘有毛刺。对应三个操作:imfill填洞、bwareaopen去小面积、imopen开运算平滑边缘。顺序很重要,先填洞再去噪点,最后平滑。
% 填洞 bw = imfill(bw_adaptive, 'holes'); % 去除小于500像素的连通区域 bw = bwareaopen(bw, 500); % 开运算平滑边缘,结构元素为半径3的圆盘 se = strel('disk', 3); bw = imopen(bw, se); % 显示清理后的二值图 figure; imshow(bw); title('形态学清理后的二值图');bwareaopen的面积阈值根据实际零件大小定。如果最小零件在图像里占 800 像素,阈值设 500 能去掉噪点又不会误删。strel('disk', 3)的半径 3 适合边缘毛刺不严重的图,毛刺多可以加到 5,但太大会把尖角磨圆,影响矩形判定。
3. 区域特征提取:圆形度、矩形度、长宽比怎么算
二值图干净之后,用regionprops提取每个连通区域的几何特征。区分圆、正方形、矩形,核心看三个指标:圆形度、矩形度、长宽比。这三个指标组合起来,规则简单但边界清晰。
3.1 regionprops 的常用属性与含义
regionprops返回的结构体里,我常用的属性有:Area(像素面积)、BoundingBox(外接矩形)、Perimeter(周长)、Centroid(质心)、MajorAxisLength(长轴)、MinorAxisLength(短轴)。这些属性直接支撑后续计算。
% 提取连通区域属性 stats = regionprops(bw, 'Area', 'BoundingBox', 'Perimeter', ... 'Centroid', 'MajorAxisLength', 'MinorAxisLength'); % 显示区域数量 fprintf('检测到 %d 个连通区域\n', length(stats));Perimeter的计算基于边界像素链码,对边缘毛刺敏感。如果二值图边缘不平滑,周长会偏大,导致圆形度偏低。这也是为什么前面要做开运算。
3.2 圆形度计算与圆的判定阈值
圆形度公式:Circularity = 4 * pi * Area / Perimeter^2。理想圆为 1,越接近 1 越像圆。实际图像里,圆形的圆形度一般在 0.85 以上,正方形在 0.75 到 0.85 之间,矩形更低。但这不是绝对的,取决于分辨率和边缘质量。
% 计算每个区域的圆形度 for k = 1:length(stats) area = stats(k).Area; perim = stats(k).Perimeter; circularity = 4 * pi * area / (perim^2); stats(k).Circularity = circularity; end % 打印圆形度 for k = 1:length(stats) fprintf('区域 %d: 圆形度 = %.3f\n', k, stats(k).Circularity); end阈值怎么定?我一般先跑一批已知形状的图,统计圆形度的分布,取圆和非圆之间的谷值。常见做法是圆形度大于 0.88 判为圆。如果图像分辨率低,圆边缘锯齿严重,阈值可以降到 0.82。
3.3 矩形度与长宽比:区分正方形和矩形的关键
矩形度公式:Rectangularity = Area / (BoundingBox宽 * BoundingBox高)。正方形和矩形的矩形度都接近 1,所以单靠矩形度分不开。这时候看长宽比:AspectRatio = MajorAxisLength / MinorAxisLength。正方形长宽比接近 1,矩形明显大于 1。
% 计算矩形度和长宽比 for k = 1:length(stats) bbox = stats(k).BoundingBox; rectness = stats(k).Area / (bbox(3) * bbox(4)); aspect = stats(k).MajorAxisLength / stats(k).MinorAxisLength; stats(k).Rectangularity = rectness; stats(k).AspectRatio = aspect; end % 打印结果 for k = 1:length(stats) fprintf('区域 %d: 矩形度 = %.3f, 长宽比 = %.3f\n', ... k, stats(k).Rectangularity, stats(k).AspectRatio); end判定逻辑:圆形度 > 0.88 判为圆;否则看长宽比,小于 1.2 判为正方形,大于 1.2 判为矩形。这个 1.2 是经验值,实际要根据零件规格调整。如果正方形零件有倒角,长宽比可能到 1.15,阈值就得放宽。
4. 分类判定与可视化:把结果标回原图
特征算完,分类就是几个 if-else。但怎么把结果直观地展示出来,方便验证和调试,这一步不能省。我习惯在原图上画边界框和标签,同时输出一个结果表格。
4.1 分类逻辑的代码实现
分类函数独立出来,输入是 stats 结构体,输出是每个区域的标签。这样方便后续换阈值或加新形状。
% 分类判定 labels = cell(length(stats), 1); for k = 1:length(stats) circ = stats(k).Circularity; aspect = stats(k).AspectRatio; if circ > 0.88 labels{k} = '圆形'; elseif aspect < 1.2 labels{k} = '正方形'; else labels{k} = '矩形'; end end % 输出分类结果 for k = 1:length(stats) fprintf('区域 %d: %s\n', k, labels{k}); end这段代码里,圆形度阈值 0.88 和长宽比阈值 1.2 是两个需要根据实际数据调的参数。建议先用一批标注好的图跑一遍,看混淆矩阵,再决定往哪边偏。
4.2 在原图上叠加边界框与标签
可视化用rectangle画框,text写标签。注意 MATLAB 的坐标系原点在左上角,BoundingBox的格式是[x, y, width, height],直接传给rectangle即可。
% 在原图上叠加结果 figure; imshow(img); hold on; for k = 1:length(stats) bbox = stats(k).BoundingBox; % 画边界框 rectangle('Position', bbox, 'EdgeColor', 'r', 'LineWidth', 2); % 写标签 text(bbox(1), bbox(2)-10, labels{k}, ... 'Color', 'r', 'FontSize', 12, 'FontWeight', 'bold'); end hold off; title('形状识别结果');如果标签重叠,可以把text的 y 坐标再往上偏移,或者用Centroid定位标签。rectangle的LineWidth设为 2 在多数分辨率下清晰可见。
4.3 批量处理与结果导出
单张图跑通后,批量处理就是套一个循环。结果导出到 CSV 或 Excel,方便后续统计。
% 批量处理文件夹下的所有图片 files = dir('images/*.png'); results = table(); for i = 1:length(files) img = imread(fullfile('images', files(i).name)); % ... 预处理、特征提取、分类 ... % 假设 labels 和 stats 已生成 for k = 1:length(stats) row = table({files(i).name}, k, labels{k}, ... stats(k).Area, stats(k).Circularity, ... stats(k).AspectRatio, ... 'VariableNames', {'文件名','区域编号','形状','面积','圆形度','长宽比'}); results = [results; row]; end end writetable(results, '识别结果.csv');writetable导出的 CSV 可以直接用 Excel 打开。如果区域数量多,建议加一列Centroid坐标,方便回溯。
5. 避坑与排查:形状识别里最容易翻车的五个地方
这套流程跑通不难,但要在实际项目里稳定运行,下面五个坑我几乎每个都踩过。按「现象 → 原因 → 解决」写出来,遇到问题可以对照排查。
5.1 圆形度总是偏低,圆被判成正方形
现象:明明是个圆,圆形度算出来只有 0.78,被误判为正方形。原因:二值图边缘有毛刺,周长偏大,圆形度公式分母变大。解决:加强开运算,把strel('disk', 3)改成strel('disk', 5);或者用bwperim提取边界后做平滑再算周长。另一个原因是图像分辨率太低,圆边缘锯齿严重,这种情况只能提高相机分辨率或拉近拍摄距离。
5.2 正方形和矩形分不开,长宽比都在 1.1 左右
现象:正方形长宽比 1.1,矩形长宽比 1.15,阈值 1.2 卡在中间,两边都判错。原因:矩形不够长,或者正方形有倒角导致长轴偏大。解决:不要只看长宽比,结合矩形度一起判。正方形矩形度通常大于 0.95,矩形在 0.90 到 0.95 之间。如果还是分不开,说明这两类形状本身差异太小,需要考虑加其他特征,比如角点数量。
5.3 自适应阈值把背景噪声判成前景
现象:二值图里背景出现大片白色区域,连通区域数量暴增。原因:Sensitivity设得太高,或者NeighborhoodSize太小,局部窗口把噪声放大了。解决:把Sensitivity从 0.5 降到 0.4,NeighborhoodSize增大到短边的 1/8。如果背景本身有纹理,先做一次高斯滤波再二值化,但高斯核要小,3×3 即可。
5.4 连通区域粘连,两个零件被当成一个
现象:两个挨着的零件在二值图里连成一片,regionprops只返回一个区域。原因:二值化后零件之间没有间隙,或者形态学操作把间隙填上了。解决:先做开运算断开细连接,如果还不行,用分水岭分割watershed。分水岭需要先算距离变换bwdist,再找局部极小值。这个操作对参数敏感,建议只在粘连严重时用。
5.5 批量处理时某张图报错,整个循环中断
现象:跑批量脚本时,某张图因为格式问题或全黑导致regionprops返回空,后续代码报错。原因:没有做异常处理。解决:在循环里加try-catch,或者先判断isempty(stats)。另外,读图后用isempty(img)检查是否读取成功。批量处理最怕一张图毁掉整个任务,加个错误日志记录哪张图失败,后续单独处理。
% 批量处理加异常保护 for i = 1:length(files) try img = imread(fullfile('images', files(i).name)); if isempty(img) fprintf('警告:%s 读取为空,跳过\n', files(i).name); continue; end % ... 处理流程 ... catch ME fprintf('错误:%s 处理失败,原因:%s\n', files(i).name, ME.message); continue; end end6. 进阶技巧:用轮廓匹配和傅里叶描述子提升鲁棒性
前面讲的几何特征法在形状规整时够用,但如果零件有旋转、缩放,或者边缘有缺损,圆形度和矩形度会波动。这时候可以上轮廓匹配。MATLAB 的matchShapes函数基于 Hu 矩,对旋转缩放不敏感,适合做形状相似度比对。另一个更稳的方法是傅里叶描述子,把轮廓转换到频域,取低频分量作为特征,高频噪声直接丢掉。
6.1 用 matchShapes 做形状相似度比对
matchShapes需要两个轮廓,返回一个相似度值,越小越相似。做法是先把标准形状(圆、正方形、矩形)的轮廓存下来,然后对每个待测区域提取轮廓,逐一比对,取最小值对应的类别。
% 提取待测区域轮廓 [B, L] = bwboundaries(bw, 'noholes'); % 标准形状轮廓(预先从模板图提取) % std_circle, std_square, std_rect 为预先存好的边界坐标 % 对每个待测轮廓做匹配 for k = 1:length(B) boundary = B{k}; % 计算 Hu 矩相似度 score_circle = matchShapes(boundary, std_circle, 'Hu'); score_square = matchShapes(boundary, std_square, 'Hu'); score_rect = matchShapes(boundary, std_rect, 'Hu'); [~, idx] = min([score_circle, score_square, score_rect]); shape_names = {'圆形', '正方形', '矩形'}; fprintf('轮廓 %d: %s\n', k, shape_names{idx}); endmatchShapes的第三个参数可选'Hu'或'Convexity',前者基于 Hu 矩,后者基于凸性缺陷。Hu 矩对旋转缩放更稳,推荐用'Hu'。标准轮廓的提取要在同样的预处理流程下做,否则尺度不一致会影响匹配。
6.2 傅里叶描述子的提取与降维
傅里叶描述子的思路:把轮廓坐标表示成复数序列z = x + iy,做 FFT,取前 N 个低频系数,归一化后作为特征向量。N 一般取 10 到 20,太少区分度不够,太多引入噪声。
% 提取傅里叶描述子 function fd = fourier_descriptor(boundary, N) % boundary: 边界坐标,N: 保留的低频系数个数 z = boundary(:,2) + 1i * boundary(:,1); % 复数序列 Z = fft(z); Z = fftshift(Z); % 直流分量移到中心 % 取中心附近的 N 个系数 center = floor(length(Z)/2) + 1; fd = Z(center - floor(N/2) : center + floor(N/2)); fd = abs(fd) / abs(fd(1)); % 归一化 end % 对每个轮廓提取描述子 for k = 1:length(B) fd = fourier_descriptor(B{k}, 15); % 后续可以用 fd 做分类,比如算欧氏距离 endfftshift把直流分量移到中心,取中心附近的系数就是低频分量。归一化用abs(fd(1))做分母,消除尺度影响。分类时算待测描述子和标准描述子的欧氏距离,取最小。
6.3 两种方法的适用边界与选择建议
几何特征法快、可解释、参数少,适合形状规整、背景干净、实时性要求高的场景。轮廓匹配和傅里叶描述子慢一些,但抗旋转、抗缩放、抗轻微形变,适合零件有朝向变化或边缘不完整的场景。我一般先用几何特征法跑,如果误判率超过 5%,再上傅里叶描述子。实际项目里,两者结合也常见:几何特征做粗筛,傅里叶描述子做精判。
最后说个习惯:每次调完参数,把当前参数和对应的准确率记在一个表格里,下次换批次图像时直接查表,不用从头试。这个习惯帮我省了太多重复劳动。希望帮到你。
本文还有配套的精品资源,点击获取