ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MATLAB图像处理实战:菌落自动计数与分割算法详解

MATLAB图像处理实战:菌落自动计数与分割算法详解 简介这是一份面向生物医学图像处理与机器学习初学者的MATLAB实用项目目标是从培养皿平板琼脂图像中自动识别并统计细菌菌落数量替代传统人工计数提升实验效率并降低主观误差。资源包共含5个文件总大小仅174KB其中两个MATLAB脚本分别承担核心计数算法与演示流程两张示例图像包括原始平板照片和掩膜标记叠加结果便于直观对照另附txt授权说明文件。已有839人浏览学习适合微生物科研人员、高校实验课程师生以及图形界面开发初学者参考。解压后可查看授权信息运行演示脚本即可在自带图像上复现预处理、分割、形态学处理、连通区域分析和分类计数的完整链路核心代码支持调整阈值、形态学核尺寸或进一步替换为CNN分类器为定制自动化菌落计数系统提供了灵活起点。 先说说我做这个项目的契机。实验室里每天要数培养皿上的菌落人工数到一百多个就眼花不同人数的结果还不一样一份数据统计下来光数数就要耗掉大半天。后来我直接用MATLAB写了一套自动化计数的图像处理脚本配合Image Processing Toolbox从读图到输出数量全程跑下来一个菌落密集的平板大概几秒钟就能出结果和人工数的误差控制在百分之几以内。这篇文章就把这套方法的完整思路、核心代码逻辑和踩过的坑都整理出来给同样被“数菌落”折磨的同行一个能直接上手的参考。整体内容围绕三件事展开怎么把彩色菌落图像处理成干净的二值图怎么把粘连在一起的菌落正确分开以及怎么让计数结果稳定可靠、不挑拍摄环境。无论你是要处理培养皿照片还是显微镜下的细菌视野这套思路都能迁移过去。1. 整体设计思路为什么选图像分割这条路1.1 自动化计数的核心难点在哪菌落计数的本质是统计图像中有多少个独立的“目标区域”。听起来很简单但实际操作里会遇到三个问题背景不是纯色培养基可能有气泡、划痕菌落之间有粘连一个区域里可能叠着好几个菌落光照不均匀有的菌落边缘亮、有的中间亮。我最早试过用颜色阈值分离菌落但因为培养基颜色和菌落颜色太接近效果一直不稳定。后来换成了“灰度化 形态学增强 自适应分割 距离变换分水岭”这条经典路线才把问题理顺。MATLAB在这一块的优势在于图像处理的函数库非常全从读取图像到形态学操作、连通域分析、分水岭分割全部有现成函数。不需要自己逐像素实现算法可以把精力集中在参数调优和特征筛选上。另外它自带的Image Segmenter App可以先做交互式探索把阈值、形态学算子这些参数先试出来再固化到脚本里调试效率比纯写代码高很多。1.2 两种典型场景用什么策略根据“细菌群”的形态我把场景分成两类第一类是培养皿/平板上的菌落特征是目标大、数量相对少、颜色与背景差异明显但菌落之间容易粘连。这个场景用“全局阈值分割 分水岭”就能解决。第二类是显微镜下的细菌涂抹视野特征是目标小、数量极多、细胞彼此挨得很近甚至重叠这个要更抠细节先做去噪增强再用距离变换找种子点才能分得开。我的项目以第一个场景为主要对象但代码里保留了面向第二场景的处理模块实际跑下来同样适用。本文重点讲第一场景的实现第二场景的参数调整会在常见问题部分补一句。2. 图像预处理与前景分割一切从干净的图开始2.1 灰度化、去噪与光照校正的具体操作读入图像后第一件事不是直接二值化而是先做三步预处理。第一步把彩色图像转成灰度图用rgb2gray。这一步会丢失颜色信息所以更适合菌落灰度与背景有差异的情况如果你的菌落是有颜色的比如粉色的假单胞菌可以考虑在HSV空间里做色调筛选这个后文会提。第二步用中值滤波或者高斯滤波去噪代码就是img imread(plate.jpg); if size(img, 3) 3 gray rgb2gray(img); else gray img; end % 中值滤波去噪窗口取3或5太大会模糊边缘 denoised medfilt2(gray, [5 5]);第三步很关键就是背景不均匀问题的处理。培养皿边缘或底部往往有阴影直接用全局阈值会把阴影误判成菌落。解决办法是顶帽变换先做形态学开运算得到背景估计再用原图减去背景。se strel(disk, 30); % 结构元素半径要大于菌落尺寸 background imopen(denoised, se); corrected imsubtract(denoised, background);顶帽变换的核心逻辑是用一个比菌落大的结构元素去估算背景光照然后扣掉它。挑结构元素半径的原则是比最大的单个菌落直径还要大但不能大到把整个培养皿都当成背景。我常用的值在20到50像素之间。预处理做完后你会发现菌落和背景的对比度明显拉开了这时候再做二值化阈值会好选得多。这里有一个我早期踩过的坑如果直接用全局阈值去处理没校正光照的图像会出现培养皿边缘一圈全是“假菌落”怎么调阈值都顾此失彼。2.2 二值化阈值的选择固定阈值不够用MATLAB里二值化最常用的就是imbinarize默认用的是Otsu方法它会自动寻找一个阈值让前景和背景的类间方差最大。不过Otsu也有失效场景——如果前景菌落面积占比很小或者背景有大面积渐变全局阈值就可能把暗背景整个归成前景。这种情况我会切换到自适应阈值bw imbinarize(corrected, adaptive, Sensitivity, 0.45);这里的Sensitivity参数控制分割灵敏度越接近1分出的前景越多噪声也越多。我一般从0.4开始试观察二值图里的菌落是否完整如果菌落中间出现空洞就降低灵敏度如果背景噪声点多就调高。注意二值化之后一定要看一下菌落内部是不是实心的。因为菌落经常是中间凸起、边缘薄光照下中间会有一圈高光二值化后反而出现“面包圈”现象中间是空的。这种空心区域如果没有处理后面计数会把一个菌落当两个。解决办法是在二值图基础上做“填充孔洞”bw_filled imfill(bw, holes);这个操作会把所有完全被前景包围的背景区域填满非常适合处理高光造成的空心。但注意不要对前一步的背景区域使用否则会把培养皿内部噪声全部连成大片。3. 粘连菌落的分割距离变换与分水岭的配合3.1 为什么直接数连通域不靠谱做完二值化后很多人会直接用bwlabel统计连通域数量觉得任务就完成了。但菌落长得根本不是规则的圆两个菌落挨得近的时候二值图上就是一个不规则的大块bwlabel数出来是1而真实菌落数量是2。我一开始也这么干过结果拿一份密集平板的图去数出来32个实际人工数了44个误差接近三成。要解决这个问题就得把粘连区域切开。对规则圆形目标最成熟的方法是“距离变换 分水岭”。先说距离变换的原理对二值图里的每个前景像素计算它到最近背景像素的距离。如果一个区域里有多个圆形目标距离变换后每个目标的中心位置会是局部最大值这个局部最大值就是“种子点”。找到种子点之后分水岭算法会从每个种子点开始向周围扩展直到碰到其他区域扩展出来的边界从而形成分割线。3.2 用MATLAB实现距离变换和分水岭的完整步骤代码核心流程是这样的% 1. 对二值填充图做距离变换 D bwdist(~bw_filled); % 2. 对距离图做H-minima变换抑制不必要的局部极小值 D_suppressed imhmin(D, minDepth); % 3. 找局部极大值作为前景标记 mask imextendedmax(D_suppressed, 2); % 4. 强制在标记位置设置极小值点 D_mod imimposemin(-D, mask); % 5. 分水岭分割 L watershed(D_mod);这里最核心的参数是minDepth也就是在H-minima变换里用来控制“多低的谷底才被算作一个独立目标”。如果设得太小杂信号也会成为标记导致过度分割一个完整菌落被劈成三瓣如果设得太大真实粘连菌落合并成同一个标记分不出来。我的经验是minDepth取距离变换最大值的20%到30%左右比较稳可以先用max(D(:))看看值域再定。还有一个坑要注意watershed处理的结果里要排除背景区域像素值为0的区域因为背景也会被分水岭边界分割成很多小块。通常的做法是bw_labels label2rgb(L, jet, [0 0 0]);或者直接对L做掩膜把背景区域的标签设为0只统计前景区域内的标签数。我实际跑下来测过对100个左右菌落的平板距离变换分水岭能正确切开约九成以上的粘连体。剩下切不开的基本是那种完全重叠、连人眼都要仔细分辨的深度粘连菌落这种场景下可以考虑更复杂的凹点检测方法但日常实验里觉得精度已经够用了。4. 特征筛选与计数实现别让杂质混进统计结果4.1 用regionprops统计菌落数量和大小经过前面几步已经得到了一张分割好的标签图L前景中每个独立的标签区域就代表一个菌落。接下来是计数和特征提取环节核心函数是regionpropsstats regionprops(L, Area, Centroid, MajorAxisLength, MinorAxisLength); num_colonies length(stats);直接length(stats)就能得到菌落数。但这样不够严谨因为二值图里可能混入培养基杂质、气泡边缘、掉落的碎屑。这些干扰物面积通常明显小于正常菌落或者偏心度极高长条形的划痕。所以要做特征筛选。4.2 面积、圆度和离心率筛选的经验值我习惯设置三个筛选维度面积下限、面积上限、离心率上限。面积下限是过滤小杂质的关键一般取所有连通域面积中位数的五分之一。这个值不是拍脑袋定的因为菌落尺寸分布相对均匀真正的小杂质会远小于菌落取中位数的比例能自适应地过滤掉它们。离心率滤掉长条形划痕公式是ecc sqrt(1 - (MinorAxisLength / MajorAxisLength)^2);完整逻辑areas [stats.Area]; eccs sqrt(1 - ([stats.MinorAxisLength] ./ [stats.MajorAxisLength]).^2); valid_idx areas minArea areas maxArea eccs 0.9; filtered_stats stats(valid_idx); num_colonies_final length(filtered_stats);这里eccs 0.9是一个比较宽松的值因为圆形菌落的离心率接近0长条划痕接近1。如果设置太严格椭圆形的次级菌落会被误删太宽松又滤不掉条状干扰。还有一类特殊情况值得单独处理如果菌落是彩色的或者颜色差异明显可以在RGB或HSV空间增加一个颜色特征。比如用mean(H)把某种色调的菌落单独挑出来这在混合培养场景下很实用。代码上不复杂就是每个连通域求色调均值再加一个色调范围判断。4.3 导出结果与可视化叠加计数完成之后建议把结果可视化出来方便人工复核。画图方法很直接figure; imshow(img); hold on; for k 1:num_colonies_final plot(filtered_stats(k).Centroid(1), filtered_stats(k).Centroid(2), ro); text(filtered_stats(k).Centroid(1)5, filtered_stats(k).Centroid(2), ... num2str(k), Color, yellow, FontSize, 8); end这样标好号的图可以直接存档后续审稿或者汇报的时候能展示“每个被计数的目标都被标记了”比单纯给一个总数说服力强很多。有人会用insertMarker或者insertText来实现效果类似但我习惯用plot因为可以自由调整样式。另外要提醒一件事regionprops统计的结果是按照标签图的索引顺序排列的如果你前面做过特征筛选索引会错位。所以如果要把筛选结果和原连通域对应起来一定要保留stats的原始索引信息或者用bwlabel的n参数直接分组再统计。5. 常见问题与排查技巧实录5.1 培养皿边缘反光和阴影的干扰怎么处理培养皿边缘那一圈弧形反光是我遇到最多的问题二值化后经常会出现一个巨大的月牙形区域面积比菌落大几十倍完全干扰了regionprops的面积统计。解决办法有两个方向一是在预处理阶段用imopen的背景估计法已经能去掉大部分均匀阴影二是对边缘区域做掩膜扣除。后者的操作不复杂提前圈出培养皿的有效区域ROI用poly2mask生成掩膜在统计时只对掩膜内的连通域计数。我做了个简单版% 手动圈选ROI也可以用自动找圆的方法 h drawcircle(gca); mask createMask(h); stats regionprops(L .* mask, Area, Centroid);这种半自动ROI做法快缺点是换一张图就得重新圈一次。批量处理时可以用imfindcircles先自动找培养皿边缘圆把圆心和半径提取出来再生成圆形掩膜这样就能全自动处理一批图片。5.2 菌落太小和背景太杂导致误检如果菌落本身很小比如长满了整个平板、菌落直径只有十几像素中值滤波窗口稍微调大一点就会把菌落边缘模糊掉。我的经验是这种场景不要用5x5的滤波窗口改用3x3或者干脆不滤波直接做顶帽变换。噪声点的问题靠后面的面积筛选解决不要试图在第一阶段就全部滤干净否则容易误伤目标。背景太杂通常是培养基有颗粒物或者拍摄时分辨率太高引入了纹理细节。这种情况下自适应阈值的Sensitivity要调小一点让分割更保守宁可少误检几个小的杂质也不要把背景大块区域分割进来。还有一招是分水岭之前对二值图做bwareaopen直接删掉面积小于某个阈值的连通域能减少大量小噪声对分水岭的干扰。5.3 光照不均导致菌落中间出现空洞前面提过的高光空洞问题在菌落表面反光明显时特别突出。我有一次处理表面湿润的平板几乎所有菌落中间都有一个白色高光区二值化后成了空心的环。填充空洞用的是imfill但如果高光区域特别大填充后菌落中间会残留一圈黑色边界痕迹分水岭阶段又会把它当成边缘切开。这种情况我的处理顺序是先做形态学闭运算把小的裂缝补上se_close strel(disk, 3); bw_closed imclose(bw_filled, se_close);再对距离变换图做中值滤波平滑减少内部局部极小值点。这套组合拳下来空心导致的过分割基本可以消除。5.4 处理显微镜下密集菌落时的参数调整如果是显微镜照片或者涂抹视野菌落目标非常密集距离变换分水岭的参数要主动调宽。minDepth的相对比例要提高一些imextendedmax里的阈值参数上面代码里的2要按距离图的最大值比例动态设置。我的代码里一般这样写maxDist max(D(:)); minDepth maxDist * 0.25; mask imextendedmax(D, round(maxDist * 0.1));用动态比例而不是固定数值是应对不同分辨率图像最省心的办法。另外密集场景下regionprops的MinorAxisLength普遍偏小面积筛选的下限要相应降低否则会漏掉大量真实菌落。最后再分享一个实用经验整个项目做完我自己最大的体会是自动化计数的精度瓶颈往往不在算法而在图像采集端。如果你拍照的时候光照均匀、培养皿放在纯色背景下、镜头垂直拍摄后面算法的参数随便调调都能得到比较好的结果反之如果照片拍得东倒西歪、反光严重那再优秀的算法也要靠参数硬扛。所以我建议有条件的实验室先固定一个拍摄装置哪怕是用手机支架加一个小补光灯都能让后续所有图像处理工作省心一半。另外分水岭的minDepth参数建议做个“参数扫描”脚本自动尝试多个候选值把每个值对应的计数结果输出到表格里你对照人工计数选一个最佳值这样既省时间又能找到最优参数。这也是我在后期批量处理上百张图片时总结出的最实用的一招。本文还有配套的精品资源点击获取
返回列表