拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于机器视觉与Matlab的LED自动计数方法实践

先聊个实际场景电子厂来料质检一整盘几百颗贴片LED摆在静电托盘里实习的小姑娘对着物料表一颗一颗数数到一半被打断回头又得重头来。这种活计我干过也知道有多崩溃。后来我用Matlab搭了一个LED元器件计数系统输入一张物料图片程序自动输出器件数量、位置坐标和可视化标注框一托盘散料几秒钟出结果彻底告别了“人肉计数器”。这套系统的本质是机器视觉里的“目标计数”任务。它解决的问题很具体人工数数不稳定人一疲劳、光源一变、器件一密集漏数错数就来了。系统通过“图像预处理 阈值分割 形态学处理 连通域统计”这条经典路线把每一颗LED从背景里分离出来然后数出连通域个数。它适合电子厂IQC来料检验、SMT料盘盘点、实验室小批量元件清点也适合正在做Matlab图像处理课设的学生。实测下来100颗以内的散料托盘在固定光源下准确率基本能到100%几百颗的编带料盘误差也控制在个位数以内。这篇文章把我整个实现过程、踩过的坑和调参经验全摊开讲。1. 项目拆解先搞清楚数LED需要哪些信息1.1 从目标特征倒推算法路线做计数系统之前先别急着上代码。先问一个问题LED在图像里长什么样我观察过不同封装类型的器件发现它们有很强的结构先验外形以圆形或近似圆形为主常见的有直插5mm、食人鱼、贴片1206、0805、0603等表面通常能看到一个发光芯片内芯高倍镜下是个小圆点或十字引脚或焊盘分布在器件边界两侧颜色上多为白、红、蓝、绿、黄和黑色、深蓝色的托盘背景能形成明显反差。这些特征意味着什么意味着计数算法不需要上复杂模型只要让“圆”在二值图里成为最显眼的形状剩下的就是查连通域个数。这是典型的传统机器视觉任务和“图像里有很多圆形物体数一数有多少个”的经典实验一脉相承。反过来想如果目标换成杂乱的叠层物料、严重反光的镜面金属件、或者形状差异极大的混装元器件那传统二值化方案就会非常吃力。所以动手前先判断我的目标是否具备“规则形状 背景对比度高 目标之间可分离”这三个条件。LED明满足这就是这个项目能跑通的底层原因。1.2 系统的数据流与功能边界整套系统的数据流很简洁就六个环节图像采集 → 灰度化与增强 → 二值化分割 → 形态学净化 → 连通域分析与筛选 → 计数与可视化每条环节都能在Matlab里用现成函数解决这也是我选择Matlab而非纯手写图像算法的原因。系统输出三样东西总数量、每颗器件的中心坐标、带边框标注的可视化图片。如果是在线读取相机实时画面还能做到按帧连续计数。这里要界定清楚系统的边界它假定器件是平铺的允许轻微挨着但不太接受严重叠放。如果一盘料倒下去堆成一团那算法会很难分清边界这种场景应该先摇匀再拍或者改用深度学习方案。传统方法的最大优势是快、稳、可解释但“可解释”的前提是场景足够规矩。1.3 工程指标能数多快、能数多准我把自己在不同物料形态下的实测数据整理成了一张表方便各位心里有个底物料形态单张耗时实测准确率主要限制散料托盘100颗以内0.3~0.8秒接近100%需要防叠放编带料盘500颗左右1~2秒接近100%料腔朝向需稳定混装物料含电阻电容0.5~1秒95%~99%需形状与颜色联合筛选平面反光金属件1秒左右90%以下对打光要求极高这几组数据是在“相机垂直拍摄 环形无影灯 黑色磨砂背景”条件下测出来的。换成自然光、手持拍摄参数可能就得重新调。做这类项目硬件环境对结果的影响权重远超算法本身这点后面细说。2. 方案选型Matlab做这件事好在哪2.1 对比OpenCV与深度学习的真实差别经常有人问我这东西用Python加OpenCV不是更流行吗为什么选Matlab我承认OpenCV能完成同样的事尤其在生产部署阶段Python生态的灵活性和对工业相机的接口支持确实更强。但研发和验证阶段Matlab有它独特的节奏——写完一行读图代码立刻能显示中间结果变量面板里拖出数组就能看数值分布改参数不用重跑整个脚本。这种交互式调试体验做算法原型时效率极高。深度学习方案我也尝试过。用YOLO一类的检测模型确实能在极端场景下硬撑比如严重叠放、反光爆炸、形态怪异。但代价也摆在桌面上标注几千张图、训练好几个小时、部署时还要带模型权重文件。对“在一个规矩场景里数圆形LED”这种任务杀鸡用牛刀。我做过对比同样一批编带料盘的图传统连通域方法跑一张不到1秒而裁剪、缩放到模型输入再推理整体流程反而更慢。所以我的选择逻辑很简单任务几何特征明确先用传统机器视觉。只有传统方法确实解决不了时才升级到深度学习。这个判断标准也同样适用于其他类似计数项目。2.2 图像采集与打光影响精度最深的“隐形环节”做图像处理的人都知道一句话能把打光做好算法就成功了一半。这个项目里我踩过最大的坑不是算法而是反光。先说相机选择。工业相机最好哪怕是最入门的130万像素USB相机也比手机稳定。手里没有工业相机的话手机也能跑通但必须固定高度和角度不能手持乱晃。分辨率上200万像素对散料托盘计数完全足够再高反而拖慢处理速度。再说打光。环形无影灯是首选因为它从四周均匀照射能最大程度压掉器件边缘的镜面反射。没有环形灯的情况下用两只台灯从左右45度斜照也能凑合但反光会明显变多。最忌讳的是只用天花板上的日光灯光线方向不固定上午和下午拍出来的图完全是两个亮度分布。背景也有讲究。深色磨砂亚克力板、黑色绒布都行关键是“哑光”而不反光。我试过用普通黑色塑料托盘结果表面亮晶晶的反光条纹直接让二值化崩掉。最后是拍摄方式。相机务必垂直于物料平面。倾斜超过30度圆形LED会变成椭圆圆形度筛选的阈值就要放宽增加误检风险。2.3 Matlab开发环境下的交付物形态有人担心Matlab做出来的东西怎么给产线同事用。其实Matlab的交付路径很成熟把核心流程封装成函数再用App Designer做一个简单的选择图片、显示结果、导出表格的界面需要更独立的环境时用MATLAB Compiler打成独立exe目标机器装个运行时就能跑。我实际交付给同事的版本就是一个exe加一个图片文件夹。界面只有三个按钮打开图片、开始计数、导出结果。内部逻辑全封装在计数函数里表面上看不到任何算法细节。当然这个部署路径需要Compiler工具箱如果只有基础版Matlab直接跑脚本也完全够用。3. 核心算法逐层拆解从原图到数字3.1 预处理灰度化、滤波、对比度增强拿到彩色图像第一步是灰度化。Matlab里用rgb2gray即可本质是把RGB三个通道按亮度权重加权求和。为什么先转灰度因为LED颜色五花八门红、黄、蓝都有直接在彩色空间里分割需要为每种颜色单独设阈值而灰度化后所有颜色统一映射到亮度维度大大简化后续处理。接着是中值滤波。LED托盘场景里最常见的噪声是小颗粒灰尘和高频传感器噪声中值滤波对这类“椒盐噪声”抑制效果极好还能保住边缘形状。窗口大小我一般用3×3或5×5分辨率越高窗口可以开大一点但窗口过大会让LED的小尺寸边缘变得模糊。这个项目里3×3在大多数情况下是安全的。然后是imadjust对比度拉伸。它的作用是把灰度直方图向两端撑开让暗的更暗、亮的更亮器件和背景的亮度差进一步拉大。可以自动计算上下限也可以手动指定。光照均匀的情况下用自动版就够因为这一步的目的不是精雕细琢而是给二值化提供一个更容易分割的输入。3.2 分割阈值二值化的关键选择二值化是整个系统的核心决策点。Matlab里最常用的是imbinarize配合graythresh自动计算大津法OTSU最优阈值。大津法的原理是遍历所有可能的灰度阈值找到一个能让“前景类方差”和“背景类方差”之和最小的分割点本质上是自动寻找类间方差最大的那个灰度值。对光照均匀的LED物料图这个阈值通常非常稳。但全局阈值有个致命弱点扛不住光照不均。如果图片左边亮右边暗一个固定阈值会让半边器件变成背景、半边背景变成器件。解决方案是自适应阈值imbinarize支持adaptive选项它把图像划分成小块每一块独立计算局部阈值。用自适应方法时有个Sensitivity参数我一般从0.4起步值越大分割越敏感、越容易把噪点也带进来越小则越保守、容易漏掉暗部目标。还有个小技巧二值化之后可能要取反。因为imbinarize默认把高亮度区域置1如果你的LED是亮目标、背景是深色拿到的是亮目标白底黑背景那不用取反反过来如果背景是白色托盘则要判断到底哪边才是器件。我的习惯是先看一眼二值图确认白色区域和LED位置重合再决定要不要bw取反。这个问题看起来简单实际是新手最容易卡住的地方。3.3 形态学净化开运算、闭运算与孔洞填充二值图出来以后不会是一颗颗干净完整的圆。最常见的情况是器件边缘带毛刺内部中心因为反光出现白洞相邻的两颗LED之间有一两像素的粘连。这些瑕疵直接影响连通域计数必须用形态学处理收尾。开运算是先腐蚀再膨胀作用是去掉边缘毛刺和小块噪点。可以想象成把图像轮廓磨掉一层再把剩下的轮廓长回来于是细小的凸起就被抹平了。闭运算是先膨胀再腐蚀作用是填补凹坑和细小断裂让一个物体内部不出现裂缝。两者组合使用我一般在开运算后做闭运算顺序不要反。结构元素用圆盘尺寸由LED在图像中的像素直径决定。比如一颗12mil封装的贴片LED在视野里直径约30像素开运算用disk 2半径2像素就够闭运算可以用disk 3。最后是imfill的孔洞填充。很多LED中心有高亮的发光芯片二值化后正好在圆中心形成一个黑点如果不填掉连通域可能会被判定成“一个圆环加一个圆心”计数时出现多算。注意imfill填的是封闭区域不会把开放的背景缺口填掉所以相对安全。3.4 连通域统计与特征筛选真正出数字的地方形态学处理结束后就进入计数环节。这里用bwconncomp找出所有相互连通的白色像素组再用regionprops提取每一组的几何属性。这一步相当于把“图像”转成“物体清单”每一行代表一个候选目标后面所有过滤都在这个表格上进行。regionprops能提取的属性很多我常用五类Area面积像素数、Centroid质心坐标、BoundingBox外接矩形、Perimeter周长、EquivDiameter等效直径。这些属性是筛选的核心原料。筛选通常分三步走。第一步是面积过滤小于某个阈值的区域是灰尘噪点大于某个阈值的可能是多颗LED粘连成的大块直接排除。第二步是形状过滤计算圆形度4πA/P²完美圆的圆形度为1正方形约0.785越扁越接近0。对圆形LED我设0.75~0.85的下限能滤掉大部分细长异物。第三步是长宽比过滤用外接矩形的宽高比剔除明显的长条形物体圆形和正方形都接近1矩形电阻则明显偏大。三道关卡做完剩下的连通域数量就是LED总数。这一整套流程下来没有任何黑魔法每一步都是可解释的。这也是我在项目总结里最想强调的传统图像处理不是过时的技术在规则工业场景里它的稳定性和可调性仍然无可替代。4. 代码实录跑通一版完整计数脚本4.1 主流程代码单张图片从读取到出数下面的脚本是完整可运行的主流程直接复制进Matlab把图片路径改成你自己的文件就能测试。我尽量把注释写详细方便一边跑一边对照理解。%% LED自动计数主脚本单图版 % 适用场景托盘/料盘上的圆形LED背景深色光照均匀 clear; clc; close all; % --- A. 读取与预处理 --- img imread(led_samples.jpg); figure; imshow(img); title(原始图像); gray rgb2gray(img); gray medfilt2(gray, [3 3]); % 去除椒盐噪声 gray imadjust(gray); % 对比度拉伸 figure; imshow(gray); title(预处理后灰度图); % --- B. 二值化分割 --- % 方案一全局阈值大津法适合光照均匀场景 level graythresh(gray); bw imbinarize(gray, level); % 方案二自适应阈值光照不均时取消下面这行的注释 % bw imbinarize(gray, adaptive, Sensitivity, 0.4); % 根据实际情况决定是否取反亮目标为白色则为1暗目标为白色则取反 % bw ~bw; figure; imshow(bw); title(二值图); % --- C. 形态学净化 --- bw imopen(bw, strel(disk, 2)); % 去毛刺和小噪点 bw imclose(bw, strel(disk, 3)); % 补全轻微断裂 bw imfill(bw, holes); % 填补内部孔洞 figure; imshow(bw); title(形态学处理后); % --- D. 连通域分析与特征筛选 --- cc bwconncomp(bw, 8); stats regionprops(table, cc, Area, Centroid, ... BoundingBox, Perimeter); if height(stats) 0 error(未检测到任何区域请检查预处理参数); end % 面积区间过滤按实际图像尺寸调整 minArea 20; % 小于它的视为噪点 maxArea 2000; % 大于它的视为粘连团块 validIdx stats.Area minArea stats.Area maxArea; % 圆形度过滤4*pi*面积 / 周长^2越接近1越像圆 circularity 4 * pi * stats.Area ./ (stats.Perimeter .^ 2); validIdx validIdx (circularity 0.75); % 长宽比过滤剔除细长异物 w stats.BoundingBox(:, 3); h stats.BoundingBox(:, 4); aspectRatio max(w, h) ./ min(w, h); validIdx validIdx (aspectRatio 1.5); % 筛选结果 validStats stats(validIdx, :); nLED height(validStats); fprintf(检测到LED数量%d\n, nLED); % --- E. 可视化标注 --- visImg insertShape(img, Rectangle, validStats.BoundingBox, ... LineWidth, 2, Color, red); labelStr cellstr(num2str((1:nLED))); visImg insertText(visImg, ... [validStats.BoundingBox(:, 1), validStats.BoundingBox(:, 2) - 10], ... labelStr, FontSize, 12, BoxColor, yellow, TextColor, black); figure; imshow(visImg); title([计数结果共 num2str(nLED) 颗LED]);运行这段脚本前提醒两点一是insertShape和insertText属于Computer Vision Toolbox如果报未定义可以用rectangle和text两个绘图函数替代效果差不多二是所有阈值参数都和你的图像分辨率、器件像素大小有关第一次跑完先看中间变量再根据实际情况微调。4.2 批量处理与结果表格导出单张图片能跑通以后下一步是批量处理一个文件夹里的所有图片并把结果导出成表格。产线质检的场景通常是一批图片都要数手工一张张跑脚本根本不现实。把这个函数式脚本稍微改造一下就能实现批量处理。建议先把主流程封装成一个函数输入图片路径输出计数结果function nLED ledCounter(imgPath) img imread(imgPath); gray rgb2gray(img); gray medfilt2(gray, [3 3]); gray imadjust(gray); level graythresh(gray); bw imbinarize(gray, level); bw imopen(bw, strel(disk, 2)); bw imclose(bw, strel(disk, 3)); bw imfill(bw, holes); cc bwconncomp(bw, 8); stats regionprops(table, cc, Area, BoundingBox, Perimeter); if isempty(stats), nLED 0; return; end minArea 20; maxArea 2000; validIdx stats.Area minArea stats.Area maxArea; circularity 4 * pi * stats.Area ./ (stats.Perimeter .^ 2); validIdx validIdx (circularity 0.75); w stats.BoundingBox(:, 3); h stats.BoundingBox(:, 4); aspectRatio max(w, h) ./ min(w, h); validIdx validIdx (aspectRatio 1.5); nLED sum(validIdx); end有了这个函数批量循环就非常简单%% 批量统计脚本 files dir(fullfile(images, *.jpg)); if isempty(files), error(目录下没有jpg图片); end results table(); for k 1:length(files) filePath fullfile(files(k).folder, files(k).name); count ledCounter(filePath); results [results; table({files(k).name}, count, ... VariableNames, {FileName, LEDCount})]; end writetable(results, led_count_result.csv); disp(results);导出结果时writetable会自动生成UTF-8编码的CSV文件Excel直接打开就能用。我在实际项目里还会顺手加一列“备注”当计数结果与人工预期不一致时标记出来方便质检员复查。4.3 参数速查表一把梭还是微调看这张表调参是这个项目里最频繁的工作。下面这张表是我反复试错后总结的经验值给初次上手的人一个起点参数推荐起始值调节方向说明中值滤波窗口3×3噪点多则增大到5×5过大边缘模糊慎用二值化方式graythresh全局光照不均改adaptive全局稳自适应贵自适应Sensitivity0.4噪声多减小漏检增大值越大分割越敏感开运算结构元素disk 2毛刺多增大到3~4过大小目标会被吃掉闭运算结构元素disk 3断裂多增大到5~6过大易把两颗粘成一颗面积下限20像素噪点多上调按图像分辨率缩放面积上限每颗面积×1.8粘连多下调需先测单颗像素面积圆形度下限0.75矩形异物多上调到0.85非圆形目标慎用长宽比上限1.5混料形状复杂的场景放宽圆形目标标准是1这些数值没有一个能脱离具体图像空谈。我每次换相机、换光源、换物料批次都会先跑一遍脚本然后用disp或者变量面板看一眼stats中间量再对着速查表调。熟练以后一个场景的参数收敛通常只需要十几分钟。5. 实战调参不同现场下的参数修正5.1 反光与光照不均的对抗方案我遇到的第一个棘手问题是反光。LED表面本身就是镜面强光一打中心直接过曝成一个大白块二值图里原本应该是一颗圆结果变成了带黑洞的圆环连续性被破坏。处理这种问题我总结了三层手段从下到上依次变粗暴第一层是物理层面解决。把直射光改成环形灯或者加一层柔光布让光线散射。拍摄时适当降低曝光时间让高光区域不要过曝到“白色饱和”。从源头把反光压住后面能省很多事。第二层是算法层面补救。轻微反光导致的内部黑点用imfill的孔洞填充就能解决前提是孔洞必须被完整闭合的边缘包裹。第三层是换个思路分割。如果反光实在严重干脆不走“亮目标”路线改用底部背光拍摄让LED变成深色剪影轮廓完整、内部细节全部消失分割起来反而更稳。光照不均是另一个常见病。图像左边亮右边暗全局阈值必然翻车。我在这种场景下试过两种有效方案首选是adaptive自适应阈值其次是顶帽运算top-hat。顶帽运算的做法是先用大半径圆盘做形态学开运算得到一个低频背景估计再用原图减去背景把亮度不均匀的部分抹平。这个方法对“背景太亮导致器件对比度不够”的情况特别管用但会额外引入一圈过渡带需要后续形态学处理兜住。5.2 器件粘连与严重叠放的拆招器件轻微挨着、边缘有几像素粘连这类问题不算严重靠形态学里的开运算和闭运算组合就能拆开。但如果一整排LED贴在一起粘连面积较大开运算直接把轮廓啃变形了也没拆干净这时我会上分水岭算法。分水岭的基本逻辑是把灰度图想象成地形图目标是山谷区域每个目标的内部是较低洼处目标之间的边界是高耸的山脊。算法从局部极小值向周围“注水”两个不同区域的“水”即将汇合的地方就是分水岭也就是物体边界。在Matlab里实现分水岭距离变换的代码很短% 在二值图基础上做距离变换分水岭 D -bwdist(~bw); L watershed(D); bw(L 0) false; % 把分水岭脊线置为背景这段代码接在第4节的主流程后面能有效把轻微粘连的圆分开。但分水岭不是银弹它会把内部有凹陷的目标误切开所以适用前提是目标内部相对平滑。如果LED叠放成堆已经看不出单个轮廓那再好的形态学都无济于事。我的经验是散料托盘拍摄前用振动盘摇匀或者手动拨一下尽量让每颗器件都露出完整轮廓。叠放严重的场景老老实实上深度学习检测别硬刚传统方法。5.3 漏料、异物与非圆形干扰的甄别编带料盘和散料托盘有一个本质区别编带里每个料腔的坐标是固定的有规律可循。缺料检测反而比散料计数更简单——不需要对每个LED做完整的形状分析只要对准每个料腔的中心位置检查局部区域里有没有器件即可。我在处理编带料盘时会先根据料腔行间距做一次网格定位然后在每个网格内统计二值图白色像素占比占比低于阈值就判定为缺料。这种方法速度快、准确率极高。混料场景里最头疼的是“长得像LED的异物”。最常见的是贴片电容和贴片电阻它们外形也为矩形或圆角矩形反光特性还和LED接近。实测下来单纯靠圆形度过滤这类目标不够因为很多贴片电容的圆角处理让周长和面积关系接近圆。我在这类场景里引入了两个额外特征一是中心亮点LED内部有半透明的发光芯片低倍镜下隐约可见中心一个更亮的点而电容表面是均匀的陶瓷材质二是颜色信息在HSV空间里统计色相分布LED芯片的颜色明显集中在特定色相区间。把这两条规则加入筛选后混料误检率从百分之几降到了千分之几以内。6. 常见问题与排查实录6.1 现象、原因、对策速查表调参过程中踩坑是常态我把高频故障整理成了一张速查表建议直接收藏现象可能原因排查步骤与对策数量比实际少很多曝光过度LED连成白色大块降低曝光或改用暗目标分割多颗器件的圆被数成一个大对象粘连未分离开运算加大或启用分水岭同一颗LED被数成两个内部孔洞未填充或硬断开闭运算加imfill检查滤波窗口灰尘和噪点被当成LED形态学未净化干净面积下限上调开运算加大贴片电容电阻混入计数圆形度阈值太宽松圆形度上调到0.85加颜色/中心点筛选图片亮度一换参数就失效环境光不稳定固定光源改用adaptive阈值二值图里目标“缺胳膊少腿”光照不均或对比度不足试试顶帽运算或分块直方图均衡运行提示regionprops输入无效二值图类型不是logical确认imbinarize输出类型必要时logical(bw)6.2 两个印象深刻的排查案例第一个案例一整盘白色LED放在浅灰色托盘上拍出来的图全局阈值直接崩了LED和背景混成一团。我一开始以为是阈值问题调了半天好转不大后来发现根子是拍摄时背景选错了——浅灰色托盘反射太强。换成黑色磨砂亚克力板之后对比度立刻上来了同样的阈值逻辑直接自动收敛。这件事让我记住先看环境再调参数。硬件层面的微小改动抵得上算法层面的百次微调。第二个案例一块电源板上要数LED数量旁边全是电解电容。电容顶面是圆形铝壳反光强圆形度也接近0.9和LED几乎无法用形状区分。我一开始的筛选规则把电容全数进去了数量虚高一倍。后来仔细放大图像发现电容顶面有防爆刻痕一个十字纹LED中心则有明显的发光芯片亮斑。最终我加了一条简单规则取每个连通域中心周围3×3区域的平均灰度与整个连通域的平均灰度做差值LED中心灰度明显高于边缘而电容中心与边缘接近。就这一条规则误检率直接清零。这类案例的共性是算法上的“笨办法”往往比复杂模型更可靠。你只要愿意多看一眼图像里的细节多理解目标的物理结构就能写出成本极低但极其有效的规则。结尾我在这个项目里最深的体会是计数系统的算法部分其实只占两成工作量剩下八成都在处理“如何让图像更干净”。光源角度、背景颜色、相机曝光、器件摆放状态每一个物理层面的细节都比代码里的阈值更能决定最终效果。如果让我给后来者一个建议那就是拿到项目后先花半天时间在实验台上摆弄光源和背景拍一组对比图再决定怎么写算法比你埋头调三天参数都管用。另外还有一个实用的小技巧每次调参前把当前图片的灰度直方图打印出来看一眼。很多时候阈值选在哪里直方图已经告诉你答案了——两个波峰之间的谷底就是你能找到的最优分割点。这个习惯帮我节省了大量试错时间也让你在面对一张从未见过的新图时能有底气地说出一句“这个阈值大概在哪个范围”。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门