
简介这份资源面向生物医学图像处理方向的学习者与科研人员提供一套基于MATLAB的细菌菌落自动计数实现方案用于替代培养皿平板琼脂图像中耗时且易出错的人工计数。包内共5个文件以2个m脚本为核心另含1张jpg示例图、1张png掩膜叠加效果图与1个txt授权说明压缩包约174KB体积轻便便于直接运行与二次修改。脚本覆盖图像预处理、菌落分割、形态学操作、连通组件分析、特征提取与分类计数等完整环节示例图与掩膜叠加图可用于直观验证分割与标记效果。目前已有839人学习下载适合具备一定MATLAB基础、希望快速上手菌落计数流程或将其迁移到自身实验数据中的读者参考借鉴。1. 从一张培养皿照片到菌落数这套 MATLAB 自动化计数方案到底能省多少事如果你做过微生物实验大概率经历过这样的场景培养箱里拿出几十个平板对着菌落计数器一个个点眼睛盯到发酸手一抖多点一个还得重来。更麻烦的是不同人判读标准不一致同一张平板两个人能数出差十几个菌落。这套基于 MATLAB 开发的自动化计数细菌群方案核心就是用图像处理算法替代人工目视计数把一张培养皿照片变成可复现的数字结果。它适合三类人需要批量处理平板图像的实验人员、想用 MATLAB 做图像分割练手的开发者、以及被重复计数折磨到想写脚本的从业者。整套逻辑不依赖深度学习框架纯靠经典图像处理链路就能跑通对硬件要求低一台普通笔记本就能复现。2. 图像预处理与菌落分割从 RGB 到二值掩膜的完整链路2.1 为什么先做灰度化和对比度增强培养皿照片直接拿来做分割最大的问题是背景光照不均和培养基颜色干扰。常见做法是先把 RGB 图转灰度再用自适应直方图均衡化把局部对比度拉起来。这里有个容易翻车的地方如果直接用imadjust做全局拉伸皿边缘的反光会被一起放大后面阈值分割时边缘会冒出一圈假菌落。我一般用adapthisteq配合ClipLimit参数控制在 0.01 到 0.03 之间超过 0.05 会把噪声也当信号增强。% 读取原始培养皿图像 img imread(plate_sample.jpg); % 转灰度减少通道冗余 gray rgb2gray(img); % 自适应直方图均衡化ClipLimit 控制对比度增强上限 % 值越大增强越猛但噪声也会被放大建议 0.01~0.03 enhanced adapthisteq(gray, ClipLimit, 0.02, NumTiles, [8 8]); % 中值滤波去椒盐噪声窗口 3x3 足够 % 窗口开太大菌落边缘会被抹平5x5 以上慎用 denoised medfilt2(enhanced, [3 3]); figure; subplot(1,3,1); imshow(gray); title(原始灰度); subplot(1,3,2); imshow(enhanced); title(对比度增强); subplot(1,3,3); imshow(denoised); title(去噪后);这段代码的逻辑很直白灰度化去掉颜色通道的干扰adapthisteq把暗处的菌落提亮medfilt2把传感器噪点抹掉。参数上NumTiles决定分块数量8×8 适合 1000×1000 左右的图图更大就调到 16×16。ClipLimit是玄学参数不同品牌的培养皿底反射率不一样建议先跑一张看效果再批量处理。2.2 阈值分割Otsu 不是万能药很多人一上来就用graythresh做 Otsu 全局阈值结果遇到菌落大小差异大的平板就崩了——大菌落被完整分割小菌落直接消失。原因是 Otsu 找的是全局最优类间方差当小菌落像素占比低于 5% 时它会被归到背景类里。解决办法是改用局部自适应阈值imbinarize的adaptive模式或者先做形态学顶帽变换把微小菌落凸显出来。% 方法一Otsu 全局阈值适合菌落大小均匀的图 level graythresh(denoised); bw_global imbinarize(denoised, level); % 方法二自适应阈值适合菌落大小差异大的图 % Sensitivity 参数控制阈值松紧0.4~0.6 之间调节 bw_adaptive imbinarize(denoised, adaptive, ... Sensitivity, 0.5, ForegroundPolarity, dark); % 形态学开运算去掉小噪点圆形结构元素半径 2 se strel(disk, 2); bw_clean imopen(bw_adaptive, se); % 填充菌落内部空洞 bw_filled imfill(bw_clean, holes); figure; subplot(1,3,1); imshow(bw_global); title(Otsu 全局阈值); subplot(1,3,2); imshow(bw_adaptive); title(自适应阈值); subplot(1,3,3); imshow(bw_filled); title(形态学清理后);ForegroundPolarity设成dark是因为菌落通常比培养基暗如果你的图像是暗背景亮菌落改成bright。Sensitivity这个参数值得多说一句它本质上是局部窗口内阈值的偏移量调高会把更多暗像素判为前景调低则相反。我一般从 0.5 开始如果发现菌落被切碎了就降到 0.4如果背景噪点太多就升到 0.6。形态学开运算的结构元素半径不要超过 3否则小菌落会被直接腐蚀掉。2.3 分水岭分割解决菌落粘连的关键一步当两个菌落挨在一起时二值化后它们会连成一个连通域直接做连通域计数就会漏数。这时候需要分水岭算法把粘连区域切开。思路是先算距离变换找到每个菌落中心的局部极大值作为种子点再从种子点向外扩展分水岭脊线。% 距离变换每个前景像素到最近背景像素的距离 D -bwdist(~bw_filled); % 强制最小值避免过分割 D2 imimposemin(D, ~bw_filled | (D -2)); % 分水岭分割 L watershed(D2); % 把分水岭脊线值为 0 的像素设为背景 bw_final bw_filled; bw_final(L 0) 0; % 去掉面积过小的区域阈值 50 像素 bw_final bwareaopen(bw_final, 50); figure; subplot(1,3,1); imshow(bw_filled); title(分割前); subplot(1,3,2); imshow(label2rgb(L)); title(分水岭标签); subplot(1,3,3); imshow(bw_final); title(分割后);imimposemin那一步是防止过分割的核心D -2这个条件是在距离变换的负值空间里保留足够深的谷底作为种子。bwareaopen的面积阈值要根据实际分辨率调假设菌落直径最小 5 像素面积就是约 20 像素设 50 是留了安全余量。如果发现菌落被切得太碎把-2改成-3或-4让种子点更少。3. 菌落计数与结果输出连通域标记、面积过滤与批量导出3.1 连通域标记与计数逻辑分割完成后用bwlabel给每个连通域打标签标签的最大值就是菌落数量。但这里有个坑如果图像边缘有被截断的菌落它们会被当成独立连通域计入导致结果偏大。常见做法是先把边缘一圈像素设为背景或者用imclearborder直接清除接触边界的连通域。% 清除接触图像边界的连通域避免截断菌落被误计 bw_noborder imclearborder(bw_final); % 连通域标记8 邻域连接 [labeled, num_colonies] bwlabel(bw_noborder, 8); % 获取每个连通域的属性 stats regionprops(labeled, Area, Centroid, Perimeter, Eccentricity); % 按面积过滤小于 30 像素的视为噪声大于 5000 的视为杂质 valid_idx find([stats.Area] 30 [stats.Area] 5000); num_valid length(valid_idx); fprintf(原始连通域数量: %d\n, num_colonies); fprintf(面积过滤后菌落数量: %d\n, num_valid); % 可视化在原图上标注菌落中心和编号 figure; imshow(img); hold on; for i 1:num_valid c stats(valid_idx(i)).Centroid; text(c(1), c(2), num2str(i), Color, r, FontSize, 8); plot(c(1), c(2), g, MarkerSize, 10); end hold off; title([检测到 num2str(num_valid) 个菌落]);bwlabel的第二个参数 8 表示 8 邻域连接对于圆形菌落来说 8 邻域比 4 邻域更合适因为对角线方向的像素也应该算连通。regionprops返回的结构体数组里Area是最常用的过滤依据Eccentricity可以用来排除细长形的杂质比如划痕一般设阈值 0.9 以上视为非菌落。面积上下限的设定需要根据你的图像分辨率来定建议先跑一张已知数量的平板做标定。3.2 批量处理与结果导出单张图跑通之后真正的效率提升来自批量处理。把待处理的图片放在一个文件夹里用dir遍历每张图走一遍完整链路最后把结果写进 CSV。这里要注意内存管理如果图片数量超过几百张每张都imshow会拖慢速度批量模式下应该关掉可视化。% 批量处理文件夹下所有 jpg 图片 input_dir plates/; output_csv colony_counts.csv; files dir(fullfile(input_dir, *.jpg)); % 打开 CSV 文件准备写入 fid fopen(output_csv, w); fprintf(fid, filename,colony_count,avg_area\n); for k 1:length(files) % 读取图像 filepath fullfile(input_dir, files(k).name); img imread(filepath); % 完整处理链路封装成函数更整洁 gray rgb2gray(img); enhanced adapthisteq(gray, ClipLimit, 0.02); denoised medfilt2(enhanced, [3 3]); bw imbinarize(denoised, adaptive, Sensitivity, 0.5); bw imopen(bw, strel(disk, 2)); bw imfill(bw, holes); % 分水岭分割 D -bwdist(~bw); D2 imimposemin(D, ~bw | (D -2)); L watershed(D2); bw(L 0) 0; bw bwareaopen(bw, 50); bw imclearborder(bw); % 计数与统计 [labeled, n] bwlabel(bw, 8); stats regionprops(labeled, Area); areas [stats.Area]; valid areas(areas 30 areas 5000); % 写入 CSV fprintf(fid, %s,%d,%.1f\n, files(k).name, length(valid), mean(valid)); % 每处理 10 张打印一次进度 if mod(k, 10) 0 fprintf(已处理 %d / %d\n, k, length(files)); end end fclose(fid); fprintf(批量处理完成结果已写入 %s\n, output_csv);这段脚本的关键点在于把处理链路内联到循环里避免反复调用函数带来的开销。fprintf写 CSV 时用%.1f保留一位小数避免面积均值出现一长串浮点数。进度打印每 10 张一次既能知道跑到哪了又不会刷屏。如果图片格式不统一把*.jpg改成*.*然后在循环里判断扩展名。3.3 参数标定用已知平板反推阈值自动化计数最怕的是“看起来能跑但数不准”。我的习惯是拿 3 到 5 张人工计数过的平板做标定把自动计数结果和人工结果对比误差超过 10% 就回去调参数。标定顺序建议是先调Sensitivity让分割结果和肉眼判断一致再调面积过滤上下限去掉明显杂质最后调分水岭的imimposemin阈值处理粘连。参数作用推荐范围调参方向ClipLimit对比度增强上限0.01~0.03菌落暗就调高噪点多就调低Sensitivity自适应阈值松紧0.4~0.6漏检多就调高误检多就调低面积下限过滤噪声20~50 像素小菌落多就调低面积上限过滤杂质3000~8000 像素大菌落多就调高imimposemin 阈值分水岭种子深度-2~-4过分割就调低绝对值标定完成后把参数写进一个结构体或配置文件批量处理时直接读取避免每次手动改脚本。4. 避坑与排查那些让计数结果翻车的细节4.1 培养皿边缘反光被当成菌落现象计数结果比人工多出 20% 到 30%可视化图上菌落集中在皿边缘一圈。原因是培养皿塑料边缘在闪光灯下产生高亮反光灰度化后形成亮环自适应阈值把它判成了前景。解决办法是在预处理阶段加一个圆形掩膜把皿外区域直接置零。用imfindcircles检测皿边界或者手动指定圆心和半径生成掩膜。% 生成圆形掩膜只保留皿内区域 [rows, cols] size(gray); [center_x, center_y, radius] imfindcircles(gray, [200 500]); mask zeros(rows, cols); [X, Y] meshgrid(1:cols, 1:rows); mask((X - center_x).^2 (Y - center_y).^2 radius^2) 1; gray_masked gray .* uint8(mask);4.2 培养基颜色偏深导致菌落对比度不足现象分割结果里菌落和背景糊在一起二值图上大片连通。原因是某些培养基比如血平板本身颜色深菌落只比背景暗一点点灰度差不到 10 个灰阶。解决办法是改用颜色通道分离在 RGB 空间里找菌落和背景差异最大的通道。常见做法是看绿色通道或蓝色通道因为菌落中的血红蛋白会吸收这些波段。4.3 分水岭过分割把单个菌落切成多块现象一个明显的大菌落被标成了 3 到 4 个计数。原因是距离变换的局部极大值太多每个小凸起都成了一个种子点。解决办法是调imimposemin的阈值或者先对距离变换图做高斯平滑再找种子。我一般用imgaussfilt对距离图做 sigma2 的平滑能把毛刺抹掉。4.4 批量处理时内存溢出现象跑到第 200 张左右 MATLAB 报内存不足。原因是每张图的中间变量没有及时清理工作区堆积了几百个矩阵。解决办法是在循环末尾加clear清理临时变量或者把处理逻辑封装成函数函数返回后局部变量自动释放。另外imshow在批量模式下应该关掉它占用的图形句柄很吃内存。4.5 不同批次平板需要重新标定现象昨天调好的参数今天换了一批平板就不准了。原因是培养基批次、培养时间、拍照光照条件都变了灰度分布跟着变。这不是 bug是图像处理方法的固有边界。我的做法是每批平板先抽 2 张跑一下看计数结果和目视是否吻合偏差大就微调Sensitivity不要指望一套参数打天下。5. 进阶技巧把计数结果做成可追溯的数据链跑通基础链路之后真正拉开效率差距的是数据管理。我现在的习惯是每张平板除了输出计数还保存分割后的二值图、标注图和原始图的缩略图三者用同一个文件名前缀。这样后面如果对某个结果有疑问可以直接翻出当时的中间图看是哪一步出了问题不用重新跑一遍。这个习惯是被一次返工逼出来的——某批平板计数结果异常但原始图已经覆盖只能全部重拍重跑血泪经验。% 为每张图建立独立的结果文件夹 [~, basename, ~] fileparts(files(k).name); result_dir fullfile(results, basename); if ~exist(result_dir, dir) mkdir(result_dir); end % 保存原始图缩略图 imwrite(imresize(img, 0.5), fullfile(result_dir, original_thumb.jpg)); % 保存二值分割图 imwrite(bw, fullfile(result_dir, binary_mask.png)); % 保存标注图 fig figure(Visible, off); imshow(img); hold on; for i 1:num_valid c stats(valid_idx(i)).Centroid; text(c(1), c(2), num2str(i), Color, r, FontSize, 8); end hold off; saveas(fig, fullfile(result_dir, annotated.png)); close(fig); % 保存参数快照方便追溯 params struct(ClipLimit, 0.02, Sensitivity, 0.5, ... AreaMin, 30, AreaMax, 5000, WatershedThresh, -2); save(fullfile(result_dir, params.mat), params);这段代码把每张图的结果隔离到独立文件夹参数快照用.mat存下来。后面如果发现某张图计数有问题打开对应文件夹就能看到当时用的所有参数和中间结果定位问题从“猜”变成“看”。figure(Visible, off)是关键它让绘图在后台完成不弹窗口批量跑几百张也不会卡界面。另一个进阶方向是把计数结果和实验记录关联起来。比如 CSV 里除了文件名和计数再加一列实验条件稀释度、培养时间、温度后面做统计分析时可以直接用。这个用readtable和writetable配合就能实现不需要额外工具。最后说一个验证技巧拿一张已知菌落数的标准平板比如商业化的质控菌株平板跑 10 次自动计数看结果的变异系数。如果 CV 超过 5%说明参数不稳定需要回去检查光照或阈值设置。我现在的习惯是每次换新批次培养基都先拿质控平板走一遍这个验证确认参数没漂再批量跑。希望帮到你。本文还有配套的精品资源点击获取