做谷物颗粒计数这件事,最早是帮一个做种子质量检测的朋友救急。他们实验室每天要数几千粒玉米,用于千粒重测算和发芽率实验的样本统计,靠人工在数粒板上一个个点,费眼睛不说,还容易数错。后来我琢磨着,能不能直接用Matlab图像处理把这活儿给自动化了,于是就有了这套“基于Matlab的谷物颗粒数量计数识别系统”。这套系统说白了就是用摄像头或者手机拍一张玉米粒的俯视图,经过预处理、分割、粘连处理,最后自动输出玉米粒的数量,并且把每个被识别出的颗粒在图上圈出来,方便人工核对。
这套东西适合谁用?农业科研单位、粮库质检人员、种子公司的实验员,以及做机器视觉课设或毕设的同学。它不依赖昂贵的工业相机和专用光源,一台普通电脑加一个几百块的USB摄像头,甚至手机拍的照片都能跑起来。我先把整个实现思路、关键算法、踩坑记录都整理在下面,照着做,你也能搭出一套实用的计数工具。
1. 系统整体思路与设计取舍
1.1 为什么把方案锁定在Matlab上
市面上做图像识别计数,Python+OpenCV是很常见的路子,但在这类谷物计数场景里,我更推荐Matlab,原因很实际:一是Matlab自带的Image Processing Toolbox封装非常完善,像灰度化、二值化、形态学处理、分水岭分割这些函数都是现成的,不需要自己从头写算法;二是图像处理在Matlab里可以直接以脚本方式交互调试,图窗里看到什么变量是什么值,一目了然,这对反复调整参数太重要了;三是GUI开发用App Designer或GUIDE能快速搭出界面,实验人员不会写代码也能操作。
这套系统的整体流程分为五大环节:图像采集、图像预处理、图像分割、颗粒计数与结果标注、交互界面封装。采集环节解决“拍得清楚”,预处理解决“把玉米粒和背景分清”,分割环节解决“粘连玉米粒怎么拆开”,计数环节解决“怎么数得准”,最后封装成界面让非技术用户也能用。
1.2 方案选型背后的几个关键决策
我做这套系统时,遇到过几个需要决策的点,在这里说清楚选择的原因。
第一是图像采集方案。工业面阵相机效果最好,但成本高、驱动麻烦。我最终选择的是普通USB摄像头配合背光板(或者直接用A4白纸当背景),把玉米粒倒在浅色托盘里,拍摄顶部俯视图。为什么用浅色背景?因为玉米粒通常是黄色到深黄色,和浅色背景的灰度差异明显,肉眼能分辨的对比度在算法里就是像素灰度值差距,差距越大,后面的阈值分割越简单。
第二是静态图像计数还是动态视频流计数。动态视频流听起来更“智能”,但实际效果未必好——玉米粒在传送带上移动时,会产生运动模糊、遮挡和重复帧等问题,算法复杂度会翻倍。对于实验室场景,一次拍一张静态图,数完再换下一批,这完全够用。所以我选择静态图像处理路线,先把核心计数算法跑通,后续如果真要上产线,再叠加上目标跟踪模块也不迟。
第三是颜色信息用还是不用。玉米粒有黄色、白色、红色等不同品种,彩色图像里RGB三通道信息其实可以用于区分不同颜色的玉米,但同时也增加了光照影响的复杂度。当前系统面向的是一般黄玉米计数,所以选择把RGB图像转成灰度图来处理,这样算法更稳定、速度更快。如果遇到多颜色混杂的样本,可以在灰度化之前先做颜色空间转换,比如转到HSV空间提取特定色相范围,这部分我在后面的扩展思路里会说。
1.3 系统能力边界与预期效果
在正式动手前,先得明确这套系统的能力边界。它适合处理散粒状态的玉米样本,也就是人为把玉米粒均匀撒在托盘里的情况,单张图像能处理的颗粒数量在50到500粒之间,再多就会出现大面积堆叠,分割难度剧增。计数准确率方面,在光照均匀、粘连不严重的条件下,准确率可以做到95%以上,也就是300粒的样本大概数错几粒到十几粒的水平;如果粘连特别严重,准确率会下降到90%左右。
这里要提前给读者打个预防针:不要期望一个开箱即用的算法对任意乱七八糟的图像都能达到100%准确。图像质量决定了算法效果的上限,算法只是去逼近这个上限。所以实际操作中,采样规范化比调参更重要。
2. 图像预处理——把“看得清”变成“算得准”
2.1 灰度化与光照校正
经过摄像头或手机拍摄的原始图像是RGB三通道彩色图,直接处理的话数据量是灰度图的三倍,而且容易受环境光色温影响。所以第一步就是把RGB图像转成灰度图。Matlab里一句rgb2gray就搞定,它按亮度公式计算每个像素的灰度值。
但拍摄时候最恼人的一个问题是光照不均匀。比如左侧有窗户光、顶上日光灯直射,托盘里就会出现半边亮半边暗的情况。这种亮度差异会直接影响后续二值化——如果按全局阈值分割,暗区域的玉米粒可能会被当成背景,亮区域的背景又可能会被当成玉米粒。对付这个问题我用的是“顶帽变换”(Top-hat变换),它可以扣除不均匀的背景亮度,只保留比周围更亮或更暗的颗粒信息。对于浅色背景上的深色玉米粒,改成底帽变换也可以达到类似效果。
% 读取图像并转为灰度 img = imread('corn_sample.jpg'); gray = rgb2gray(img); % 使用形态学顶帽变换校正光照不均 se = strel('disk', 30); % 结构元素半径要大于单颗玉米粒半径 tophatImg = imtophat(gray, se); % 对校正后的图像做增强 enhanced = imadjust(tophatImg);注意结构元素strel('disk', 30)的半径参数不是随便填的。形态学开运算会把比结构元素小的亮区域抹掉,如果结构元素太小,会把玉米粒本身也抹掉一部分;如果太大,背景亮度变化又无法被有效拟合。根据我拍摄样本的经验,当玉米粒直径占图像宽度的1/20时,结构元素半径取20~40之间比较合适。实际操作时,可以用imdistline工具手动量一下图中玉米粒的像素直径,再反推结构元素的大小,这是最稳妥的做法。
2.2 去噪——不要让椒盐噪声干扰计数
玉米粒表面并不是光滑均匀的,它顶端有凹陷、胚部颜色深浅不一,这些天然纹理在灰度图上都会形成局部噪声。再加上拍摄环境中的灰尘,图像上经常出现一些细小的暗点或亮点。这些噪声如果不处理,会在二值化后形成假目标,导致计数结果虚高。
中值滤波是对付椒盐噪声最好用的手段,它的原理是把每个像素的值替换为其邻域内所有像素值的中位数。为什么不用均值滤波?因为均值滤波在降噪的同时会把边缘模糊掉,而玉米粒计数最后恰恰要靠边缘把颗粒分出来,边缘越清晰越好。中值滤波能较好地保留边缘信息,计算量也小。
% 中值滤波去噪,窗口大小3x3即可 denoised = medfilt2(enhanced, [3 3]);窗口大小的选择同样有门道。3x3的窗口可以滤掉孤立的椒盐噪声,同时不会把玉米粒表面的纹理细节全部抹掉。如果玉米粒本身较小(直径只有十几个像素),窗口就不能再大了,否则会把玉米粒整体抹模糊,导致后续分割粘连更严重。如果图像分辨率较高、玉米粒直径在100像素以上,可以尝试5x5窗口进一步平滑。
2.3 二值化——灰度图到黑白分明的分割
预处理完成后,需要把灰度图像变成二值图像,也就是每个像素只取0(背景)或255(前景)两种值,这一步是做分割的关键。二值化最核心的就是阈值怎么定。
我强烈推荐用Otsu方法(graythresh函数),它的核心思想是最大化前景与背景之间的类间方差,自动算出最优阈值,完全不需要人工干预。为什么不用固定阈值?因为不同批次的玉米颜色不同、不同环境光照不同,固定阈值换个场景就不适用了,而Otsu是自适应算法,每一张图都会算出一个适合当前亮度分布的阈值,鲁棒性好得多。
% Otsu自适应阈值 level = graythresh(denoised); bw = imbinarize(denoised, level);二值化之后的图像通常会有两类问题:一类是背景里的孤立噪点被误判为前景,形成白色小点;另一类是玉米粒内部的凹陷区域被误判为背景,形成颗粒内部的黑洞。这两类问题可以分别用形态学开运算和闭运算来修正。开运算是先腐蚀后膨胀,可以消除小的孤立噪点;闭运算是先膨胀后腐蚀,可以填充颗粒内部的小空洞。
% 形态学清理:先去噪点,再填充内部空洞 bw = imopen(bw, strel('disk', 2)); bw = imclose(bw, strel('disk', 5)); bw = imfill(bw, 'holes');这里有个细节:imfill(bw, 'holes')会把所有闭合区域内部的空洞都填满,但是如果有两个玉米粒粘连并围出一个封闭的背景区域,这个函数会把那个背景区域也错误地填成前景。所以这个操作必须放在粘连分割之前,而且后续还需要通过面积筛选来排除这种人为造成的假区域。
3. 核心难点——粘连玉米粒的分割与计数
3.1 为什么简单连通域计数会失败
预处理完成后,最直观的计数方法就是统计二值图像里的连通域数量。Matlab里用bwlabel可以给每个连通区域编号,再用numel(unique(L))-1得到连通域数量。但这种方式只适用于玉米粒彼此完全分离、没有接触的情况。现实中很少有这种理想状态,玉米粒从袋子里倒出来往往会挤在一起、叠在一起,形成一片连通的白色区域。这时候硬按连通域数,明明里面有5个玉米粒,可能只数出1个。
具体来说,粘连情况大致分三种:轻度粘连,两粒玉米挨在一起但轮廓还能看出边界;中度粘连,两三粒玉米挤成一团,轮廓局部融合;重度粘连,多粒叠加,大部分边界都看不清。轻度粘连用形态学处理加距离变换就能解决,中度和重度粘连需要分水岭算法介入。
3.2 距离变换 + 分水岭——拆开抱团的玉米粒
分水岭算法是图像分割里的经典方法,思路可以类比成地理上的流域划分。把灰度图想象成一张地形图,像素灰度值越高代表地势越高。往地形图里注水,水会从低处开始汇聚,先形成一个个小水坑(局部极小值区域),随着水位上升,不同水坑的水面会相遇,在水面相接处建立“堤坝”,这些堤坝就是分水岭线,对应图像中不同目标的边界。
但在实际二值图像上直接跑分水岭是不行的,因为一条裂缝、一个噪声点都可能产生一个局部极小值,导致过分割——本来一个完整的玉米粒被切成了几块。因此需要先对二值图像做距离变换。距离变换的原理是计算每个前景像素到最近背景像素的距离,也就是说,距离值越大的像素越处于目标中心,距离值越小的越靠近边缘。这样就把一个形状不规则的二值连通域,变成了一个中心高、边缘低的“山包”,每个玉米粒对应一个山包顶部。
在距离变换图上跑分水岭,会把山包按峰值区域切开,刚好对应到每个玉米粒的中心位置,从而把粘连的目标分离开。
% 距离变换 D = bwdist(~bw); % 对距离图做高斯平滑,避免过分割 D = imgaussfilt(D, 1.5); % 强制让背景区域作为极小值,防止背景被错误分割 D = imimposemin(D, ~bw); % 分水岭分割 L = watershed(D); % 只保留原前景区域内的分水岭结果 bw_split = bw; bw_split(L == 0) = 0;这里imimposemin是个容易被忽略但很关键的操作。它的作用是强制指定某些区域必须是极小值点。如果不做这一步,背景区域可能也会被分水岭算法切出乱七八糟的线,导致结果混乱。而imgaussfilt高斯平滑的作用是抹掉距离变换图上的细小起伏,避免每个微小的突起都形成一个分割边界。
3.3 分水岭参数对分割效果的影响
分水岭对参数比较敏感,不同批次的玉米粒图像可能需要微调。主要参数有两个:高斯平滑的σ值(标准差)和距离变换前的形态学开操作结构元素大小。
如果σ偏小(比如0.5),距离变换图保留的细节多,分割边界会更贴合颗粒边缘,但过分割概率增加;如果σ偏大(比如3),分割结果会更平滑,过分割减少,但也可能把真正属于两个颗粒的边界也抹掉,导致欠分割。我测试下来,玉米粒这种尺寸相对均匀的目标,σ取1到2之间是甜点区。
此外,在距离变换之前,如果某些颗粒表面的凹陷特别深,也可能被误判为分开的区域。这时候可以先对二值图像做一次开运算,把颗粒表面的小凹陷修平,再进行距离变换,能明显缓解这一类过分割。示例代码如下:
% 对二值图先做小型开运算,平滑颗粒表面 bw_smooth = imopen(bw, strel('disk', 3)); D = bwdist(~bw_smooth); D = imgaussfilt(D, 1.5); D = imimposemin(D, ~bw_smooth); L = watershed(D); bw_split = bw_smooth; bw_split(L == 0) = 0;强调一点,开运算的结构元素不能过大,否则会把本来就靠得很近的两个颗粒之间的细微“颈部”连接直接腐蚀断,导致粘连关系被错误改变。直径3到5像素是一个安全范围。
3.4 分割结果的可视化验证
分割算法的效果不能只看最终计数结果,必须用可视化方式验证每颗玉米粒是否被正确切开。我的习惯是把分水岭边界线叠加在原始灰度图上,用红色线条标出分割结果,然后肉眼过一遍每个区域是不是恰好对应一颗完整玉米。
% 在原图上绘制分割边界 boundary = (L == 0); boundaryOverlay = imoverlay(img, boundary, [1 0 0]); imshow(boundaryOverlay);这一步看起来很土,但非常有用。计数错了,第一件事应该看分割边界的叠加图,判断是过分割(一个颗粒被红色边界切断)还是欠分割(多个颗粒之间没有红色边界),还是预处理阶段就出了问题(玉米粒和背景没分开)。有了这张图,调参就有方向,而不是瞎试数字。
4. 计数实现与结果精度控制
4.1 连通域标记与面积筛选
分水岭分割完成后,图像里相邻的玉米粒已经通过“黑色边界线”隔开了,接下来就可以用bwlabel给每个连通域编号并计数了。但这里还有一道重要的关卡——面积筛选。分割之后的连通域里,可能混入三种假目标:预处理不彻底残留下来的噪点、玉米粒表面碎屑、图像边缘被切掉一部分的玉米粒残片。它们的面积通常远小于或远大于正常玉米粒的面积。
统计所有连通域的面积,画出直方图,会发现正常玉米粒的面积分布在某个较窄的区间内,而假目标的面积分布在两端。用regionprops提取面积属性后,设定一个合理的最小面积和最大面积阈值,把范围外的区域全部剔除,就能把假目标滤掉。
% 连通域标记 L = bwlabel(bw_split); % 提取面积属性 stats = regionprops(L, 'Area', 'Centroid', 'BoundingBox'); % 计算面积分布,确定筛选阈值 allAreas = [stats.Area]; minArea = 0.5 * median(allAreas); maxArea = 1.5 * median(allAreas); % 面积筛选,保留有效颗粒 validIdx = find([stats.Area] > minArea & [stats.Area] < maxArea); count = numel(validIdx);为什么用中位数(median)而不是平均值来定阈值?因为平均值容易受极端值影响,比如一个大残片面积是正常颗粒的5倍,会把平均值拉高很多,导致阈值整体偏移;而中位数对离群值不敏感,更能代表“正常颗粒面积”的典型水平。以中位数为基准,上下浮动50%作为筛选范围,在我的测试中效果最稳定。
4.2 标记显示与结果输出
计数完成后,还需要在原始图像上把识别结果标注出来,直观呈现给用户。在每颗玉米粒的形心位置画一个圆点,并标上序号。
% 在原始图上绘制结果 imshow(img); hold on; for k = 1:numel(validIdx) idx = validIdx(k); centroid = stats(idx).Centroid; plot(centroid(1), centroid(2), 'ro', 'MarkerSize', 10, 'LineWidth', 2); text(centroid(1) + 5, centroid(2), num2str(k), 'Color', 'yellow', ... 'FontSize', 10, 'FontWeight', 'bold'); end hold off;这副标注图一方面可用于人工复核,另一方面也是做实验记录时的凭证,可以导出保存。完整的计数结果还可以写到一个Excel表格里,把样本编号、计数数量、图像文件名、处理时间都记录进去,方便后期追溯。Matlab里用writetable就能轻松实现。
4.3 准确率评测方法与误差分析
做这类系统,光看“感觉差不多”不行,必须建立一套量化评测方法。我的做法是:取一批玉米粒,先人工数出准确数量(或者用电子数粒仪结果作为基准),再让系统跑一遍,对比误差。
误差通常来自三个方向。一是过分割:一颗玉米粒被切成两块,计数偏大,通常是分水岭参数过小或表面凹陷太深导致。二是欠分割:多颗玉米粒粘在一起没被切开,计数偏小,通常是粘连太紧密、边界不明显,或高斯平滑σ过大把边界抹平了。三是漏检和误检:玉米粒太靠近图像边缘被切掉一半,或者背景里的杂物被当成颗粒,这类问题主要靠面积筛选和边缘剔除来解决。
我自己的一个习惯是:对边缘处的连通域做额外判断,如果某个区域的边界与图像边缘重叠超过20%,就认为它是不完整颗粒,直接排除。这个虽然可能漏掉边缘上的完整颗粒,但整体上避免了半粒玉米被错误计入而导致严重偏差。
4.4 批量处理与自动保存
实际工作中不可能一次只处理一张图。种子实验里一次来50个样本、100个样本很常见,一个个手动处理太累了。所以我在系统里加了批量处理功能,把文件夹里的所有图片路径读取出来,循环执行预处理、分割、计数,最后生成一张汇总表。
% 批量处理文件夹内所有jpg图片 fileList = dir(fullfile('samples', '*.jpg')); results = table(); for i = 1:numel(fileList) imgPath = fullfile(fileList(i).folder, fileList(i).name); count = processCornImage(imgPath); % 封装好的计数函数 results = [results; table({fileList(i).name}, count, 'VariableNames', ... {'FileName', 'Count'})]; end writetable(results, 'count_results.xlsx');封装成函数的过程我建议务必做一次:把整个流程从读图到计数提取成一个processCornImage.m函数,输入图片路径,输出数量和标注图。这样做的好处有三个:第一,批量处理时只需循环调用;第二,后续要接GUI,函数接口已经现成;第三,想换算法(比如从普通分水岭换成改进算法)时,只需要改函数内部,不用动外层逻辑。
5. 实操中遇到的坑与排查方法
5.1 常见问题速查表
这部分是我自己调试时踩过的坑,列成表格方便你对照排查。
| 现象 | 可能原因 | 排查与解决方法 |
|---|---|---|
| 计数结果明显偏大 | 玉米粒表面凹陷造成过分割 | 增大高斯平滑σ值;先做形态学开运算平滑颗粒表面 |
| 计数结果明显偏小 | 多个玉米粒粘连成团未分开 | 检查是否缺少分水岭步骤;降低σ值;增强图像对比度 |
| 个别颗粒完全没识别出来 | 玉米粒颜色与背景太接近 | 更换背景颜色;加强光照,使前景背景对分明 |
| 背景噪声被当成玉米粒 | 二值化后孤立噪点未清理 | 增大形态学开运算结构元素;检查中值滤波是否生效 |
| 图片边缘颗粒被切碎 | 样本超出拍摄范围 | 摆放时留出边界余量;做边缘完整性剔除 |
| 处理速度很慢 | 图像分辨率过高 | 先按比例缩小图像,处理完再映射坐标 |
| 不同批次的玉米颜色不同导致效果差异大 | 灰度化后颜色信息丢失 | 改用HSV空间的颜色阈值分割来区分玉米粒 |
5.2 图像采集时的环境规范
很多同学跑来问我“为什么我拍的图算法效果那么差”,我让他们发原图一看,问题八成都出在图像采集环节。这里分享几条采集规范,真的能帮你少走很多弯路。
拍摄时把玉米粒均匀摊开,尽量不要叠放,颗粒与颗粒之间的间隙至少保持2到3像素以上,这样能给后续分割留出操作空间。背景选哑光白色或蓝色纸张,避免反光。瓷砖、塑料板表面容易产生镜面反射,会在图里形成高光区域,直接影响二值化效果。光线尽量选用高角度的漫射光,避免直射强光在颗粒表面形成强烈反光和阴影。如果发现阴影重,可以用一张白纸做成简易柔光罩,效果立竿见影。
另一个容易忽略的问题是镜头畸变。普通摄像头边缘会有桶形畸变,玉米粒在画面边缘会被拉变形,面积和形状特征都会受影响。如果精度要求高,可以用相机标定工具箱做一次畸变校正,效果会好很多。
5.3 性能优化建议
当图像分辨率较高(比如手机拍的1200万像素照片)时,算法处理速度会很慢,尤其在分水岭这一步。实际上对于玉米粒计数任务,并不需要那么高的分辨率。我的建议是先把图像缩放到宽度800到1000像素左右再处理,处理完计数后,把坐标位置按缩放比例映射回原图,再做可视化标注。这一招能把处理时间缩短到原来的十分之一左右。
Matlab里用imresize就够了。需要提醒的是,缩放后玉米粒像素面积可能变小,分水岭分割的σ参数也要相应调整,否则分割效果会变化。
5.4 拓展思路:从玉米到其他谷物的迁移
这套系统的核心是图像分割与计数,并不局限于玉米。换成大米、小麦、大豆、油菜籽,只需要调整几个关键参数:结构元素大小、面积筛选阈值、分水岭σ值。原因在于不同谷物的尺寸、颜色、形状不同,但分割与计数的算法框架是通用的。
我在迁移到大米计数时,主要改了三个地方:一是结构元素的半径,大米颗粒更细长,disk半径适当调小;二是距离变换后的高斯平滑σ值,大米表面更平滑,过分割问题不严重,σ可以调小;三是面积筛选的上下限,大米比玉米小很多,中位数自动适应,不需要手动指定绝对面积值。整体代码改动不超过十行,迁移成本很低。
6. 交互界面封装,让非技术用户也能操作
6.1 App Designer快速搭建界面
算法稳定之后,最好再封装一个简单的图形界面,否则每次都要在命令行运行脚本、改参数,对非技术用户太不友好。Matlab的App Designer是目前官方主推的GUI设计方案,拖拽控件的方式和做PPT差不多,很容易上手。
我的界面布局思路很简单:顶部一个“选择图片”按钮,中间显示原始图像和结果图像,底部显示计数结果和“导出报告”按钮。控件不用多,够用就行。关键是要把回调函数写好,让点击按钮时能调用我们之前封装好的processCornImage函数。
% 按钮回调示例 function selectImageButtonPushed(app, event) [file, path] = uigetfile({'*.jpg;*.png;*.bmp', '图像文件'}); if file == 0 return; end fullPath = fullfile(path, file); img = imread(fullPath); imshow(img, 'Parent', app.OriginalImageAxes); % 调用计数函数 [count, markedImg] = processCornImage(fullPath); imshow(markedImg, 'Parent', app.ResultImageAxes); app.CountLabel.Text = ['玉米数量: ' num2str(count)]; end6.2 编译发布,脱离Matlab环境运行
如果做出来只是自己用,界面版已经足够了。但如果要给实验室其他同事用,或者作为课设、项目成果提交,建议用Matlab Compiler把程序编译成独立可执行文件。这样目标电脑上不需要安装完整Matlab,只需要安装免费的Matlab Runtime即可运行。
编译的过程很简单,在App Designer或脚本界面选择“打包应用程序”,然后按向导选择主文件和依赖文件就可以了。有一点要注意,编译之前一定要先跑通所有功能,因为编译后的调试比在Matlab环境里麻烦得多。
6.3 实际使用体验反馈
我把这套系统给种子实验室的朋友用了两周,收集到几条很有意思的反馈。第一,他们最认可的不是计数准确率,而是“标注图”功能,因为每个标记的玉米粒都在图上显示出来了,数错没数错一眼就能看出来,不用盲目信任算法。第二,他们希望批量处理时能把每张图的标注图也一并保存,而不是只保存数量结果,后来我加了图片导出功能,实测反馈很好。第三,有用户提出能否把计数结果直接接入二维码扫描枪,读了样本编号自动记录,这个想法很好,目前还在开发中。
这些反馈也说明一个道理:做工具类系统,用户体验和结果可视化的重要性不亚于算法本身的精度。你可以不接受所有建议,但至少要保证用户能看懂结果、能验证结果。
7. 最终成果与个人心得
这套基于Matlab的谷物颗粒数量计数识别系统,主干流程从图像采集到计数输出全打通了,黄玉米的单张图像计数准确率在常规光照和散粒状态下实测可以达到95%以上,批量处理一张500万像素图片耗时约2到3秒。如果只是随便拍一张验证,速度更快。做课设、毕设或者实验室日常辅助计数,这个精度和速度都够用。
我在实际开发中踩过最大的一个坑,就是一开始死磕分水岭参数,结果怎么调都调不好。后来退一步审视整个流程,发现问题其实出在预处理阶段——光照不均导致二值化结果本身就存在大量误差,分割算法再先进也救不回来。所以现在我的调试顺序永远是:先确认二值化结果干净清晰,再调分割参数,最后才看计数结果。你如果也遇到类似的问题,建议按照这个顺序排查,会省下不少时间。
最后再分享一个小技巧:做这类图像计数系统,不用一上来就用深度学习。传统图像处理虽然“老”,但它的每一步都是可解释、可控制、可调的,对于玉米这种形状相对规则的目标,算法的稳定性和计算效率其实比深度学习更好。先用经典方法跑通全流程,如果有更复杂的场景需求,再考虑引入深度学习模型做分割,这样既能快速落地,又能保证系统的可控性。