资讯详情 MATLAB红细胞计数实战:从预处理到特征筛选的完整图像处理流程
📅 2026/10/13 20:52:06
简介基于MATLAB的红细胞计数实现面向数字图像处理课程实习与生物医学图像分析初学者解决血涂片图像中红细胞自动识别与计数的典型问题。压缩包仅含1个m脚本体积约1KB代码紧凑便于快速运行与调试完整覆盖图像读取、灰度化、对比度调整、中值滤波去噪、阈值分割、形态学膨胀腐蚀及边缘检测等核心步骤可帮助学习者理清预处理、特征提取到细胞识别的完整链路。项目涉及imread、imshow、imadjust、imhist等常用函数并扩展到Canny算子、区域生长、SVM与K-means聚类等可选策略适合作为数字图像处理实验代码或实习报告的参照。已有1236人学习下载适合希望借助MATLAB Image Processing Toolbox入门细胞计数应用并进一步探索深度学习优化方向的读者。2. 预处理链路灰度化、滤波与光照校正的三个关键选择预处理是整个计数流程里最不值得省的部分。很多计数偏差看起来是分割算法不行实际是灰度图还没处理干净。血涂片本身染色深浅差异很大显微镜视场边缘又经常比中心暗这会让后面的阈值分割非常不稳定。我一般把预处理分成三件事灰度化与对比度增强、降噪、背景校正每件事的参数都和图像的实际尺度有关。这一章先把这三个环节讲透因为分割阶段的大部分“翻车”根源都在这里。2.1 灰度化与对比度增强imadjust 的百分位端点怎么定血涂片染色后红细胞呈淡红色背景是浅白或淡蓝色白细胞核则是深紫色。直接在 RGB 三通道上做分割很容易被染色偏差干扰最常见做法是转成灰度图再用 imadjust 做线性拉伸。这里有一个容易忽略的细节如果输入图像本来就是单通道灰度图再调 rgb2gray 也不会报错只是重复转换。所以我会先用 size(img, 3) 判断通道数避免在后续流程里混进一个多余通道。img imread(smear_sample.png); if size(img, 3) 3 gray rgb2gray(img); else gray img; end % 端点用 1% 和 99% 分位避免单个亮点或暗点影响整体映射 low double(prctile(gray(:), 1)); high double(prctile(gray(:), 99)); enhanced imadjust(gray, [low/255, high/255], [0 1]);逻辑说明rgb2gray 按亮度公式加权保留细胞内外的相对亮度差异。imadjust 把 [low/255, high/255] 这个灰度区间线性映射到 [0, 1]从而把中间灰度带拉开让红细胞的边缘对比度更明显。low 和 high 用 prctile 而不是 min/max是为了抵抗镜头污渍、染料结晶这类离群点——如果用 min/max一个最亮点就会把映射曲线拉得很平细胞边缘反而变糊。参数说明分位数选择 1% 和 99% 是保守值。如果染色偏深可以把区间放宽到 0.5% 和 99.5%如果染色很浅区间太宽会把背景噪声一起放大这时要看直方图再决定。注意这段代码假设图像是 uint8如果是 16 位灰度low/high 需要按 65535 换算或者先把灰度归一化到 [0, 1]。增强之后最好用 histogram 看一眼灰度分布如果红细胞和背景仍然混在一起或者背景峰很宽说明问题不在增强而在后续分割或光照校正。不要反复叠加增强操作每一次线性拉伸都会改变灰度分布的形状叠加两次以后阈值的可解释性就变差了。2.2 去噪滤波中值滤波的窗口大小和边缘保持显微图像里的噪声来源很多染料结晶、灰尘、感光元件坏点常见的还有椒盐噪声。中值滤波是排序滤波器对脉冲噪声很有效而且比均值滤波更能保持细胞边缘。高斯滤波适合高斯噪声但会把红细胞边缘变成缓坡导致后续二值化时边界不确定所以我更常用中值滤波。% 中值滤波窗口 3x3保持红细胞边界锐利 denoised medfilt2(enhanced, [3 3]);逻辑说明medfilt2 把窗口内的像素按灰度排序取中间值作为当前像素输出。椒盐噪声的极亮或极暗点会被邻居像素中和掉而真正的细胞边缘因为两边灰度差异明显在 3x3 窗口下仍然是边缘。参数说明窗口大小优先用 3x3。如果图像分辨率很高、细胞直径在 50 像素以上可以试 5x5去噪更强但边缘模糊也更明显面积和圆度特征都会受到波及。我自己的习惯是先 3x3 跑一遍如果二值图里仍有大量孤立噪点再考虑 5x5并重新检查圆度分布。注意不要连续做两次中值滤波滤波次数越多边界层丢失越严重细胞面积会系统性偏小。中值滤波的另一个隐性作用是把细胞内部的暗心噪点抹平一些这能减少后面 imfill 时的孔洞数量。如果输入图像本身很干净也可以跳过这步但跳过后二值化的杂点一般会增多最后还是得用形态学或面积过滤不如留一个轻量滤波作为缓冲。2.3 背景校正均匀光照假设失效时用顶帽变换显微镜视场经常出现边缘暗角或者样本厚薄不均导致整体亮度波动。全局阈值假设背景灰度是单一峰一旦这个假设失效边缘区域的背景就会被判成前景。顶帽变换是解决这类问题的经典手段先对灰度图做形态学开运算得到背景估计再用原图减去背景估计。开运算会保留尺度大于结构元素的缓慢变化而红细胞这类尺寸较小的结构会被抹掉所以剩下的就是背景的大致亮度。% 结构元素半径要大于红细胞半径这里细胞半径约 10~15 像素取 30 se_bg strel(disk, 30); background imopen(denoised, se_bg); corrected imsubtract(denoised, background); % 校正后背景被压扁再做一次线性增强方便观察和后续分割 corrected imadjust(corrected, stretchlim(corrected, 0.05), []);逻辑说明imopen 是先腐蚀后膨胀在灰度图像上相当于对局部极小高度敏感的背景平滑。红细胞比背景亮且直径远小于结构元素所以它们在开运算中被当作“被去掉的凸起”移除剩下的 background 就是无细胞区域的亮度估计。imsubtract 把原始灰度减去背景估计从而将光照不均的影响压平。参数说明strel(disk, 30) 的 30 是像素半径这个值必须大于图像中红细胞的半径。如果红细胞半径为 10~15 像素取 30 可以留出足够余量。如果半径取小了比如 10开运算会把部分红细胞也带进背景估计导致校正后细胞边缘出现暗边听起来像玄学实际上用 30 和用 20 跑同一张图二值化结果相差很大。所以我会先用图像标注工具量 3~5 个细胞的像素半径再乘以 2 作为结构元素半径。顶帽变换的缺点是不再保留原始灰度绝对值之后的阈值设定要基于相对尺度而不是固定的灰度值。另外顶帽变换会让细胞边缘局部变亮容易在分割后产生亮环所以后面一定要接形态学修正和孔洞填充不能拿校正结果直接去数。到这里灰度图已经做好。下一步就是把它变成二值图也就是把“细胞”和“背景”显式分开。3. 把红细胞从背景里“抠”出来分割与形态学修正分割阶段的任务是把前景所有不透明物体从背景里分离出来。红细胞的典型特征是中间淡染、边缘深染所以灰度剖面上经常是“亮环暗心”。这对二值化并不友好如果直接用全局阈值暗心可能被分割成背景形成一个个环状目标。后面我们会用填充处理但在分割时就要对形态有预判。3.1 二值化阈值Otsu 还是自适应阈值先看直方图再决定阈值选择没有万金油。我的流程是先看 corrected 的灰度直方图。如果背景峰和细胞峰分得比较开用 Otsu 全局阈值就行如果背景峰很宽或者直方图拖尾严重就用自适应阈值。全局阈值速度快、参数少但遇到光照不均或染色深浅跨度大的图像会翻车。% 先观察直方图形态 histogram(corrected, 256); title(corrected 直方图); % 双峰分明时用 Otsu 全局阈值 bw0 imbinarize(corrected); % 背景不匀时改用自适应阈值 bw0 imbinarize(corrected, adaptive, ... Sensitivity, 0.45, ForegroundPolarity, bright);逻辑说明imbinarize 默认使用 Otsu 方法它通过最大化前景与背景的类间方差来确定全局阈值。自适应阈值则是对每个像素计算其邻域内的局部统计量然后根据局部亮度确定阈值能够跟踪光照的缓慢变化。参数说明Sensitivity 取值范围是 0 到 1值越大被判为前景的像素越多。0.45 是一个比较保守的起点适合细胞与背景对比度尚可的涂片。如果细胞边缘很弱可以调到 0.55如果背景里杂质很多可以降到 0.4。ForegroundPolarity 决定了前景相对于背景是亮还是暗血涂片里细胞比背景亮所以用 bright。如果你的图像来自荧光染色且目标更亮同样用 bright如果是反向染色用 dark。自适应阈值的 NeighborhoodSize 参数也值得关注默认值是图像尺寸的 1/8。对高分辨率图来说这个邻域可能覆盖多个细胞低对比度的小细胞会被漏检。我一般把它设为一个细胞直径的 2 倍左右。假设红细胞直径约 20 像素就设 NeighborhoodSize, 40。这个参数直接影响局部背景估计的平滑程度调的时候要观察小细胞区域是否被完整保留。3.2 形态学开闭运算消除杂点、修补轻微粘连二值图里有两类常见问题孤立的小噪点以及相邻红细胞轻微粘连。开运算先腐蚀后膨胀可以去掉孤立前景点和细胞边缘的毛刺闭运算先膨胀后腐蚀可以填补细胞内的小洞和边缘的细缝。这里我强调顺序先开后闭。如果反过来闭运算会把毛刺先包进去开运算再处理时又可能把边界撕开。se_m strel(disk, 2); bw1 imopen(bw0, se_m); bw1 imclose(bw1, se_m); % 另一种快速清除小杂点的方法是 bwareaopen % 这里 minArea 根据红细胞面积下界来定先大致估算中位面积 med_area median(regionprops(bwconncomp(bw1, 8), Area).Area); bw1 bwareaopen(bw1, round(0.3 * med_area));逻辑说明imopen 把小于结构元素的前景区域腐蚀掉再通过膨胀恢复剩余区域的大小imclose 用来填补前景内部的细小孔洞。bwareaopen 删除像素数少于指定阈值的连通域比形态学开运算更直接但前提是你已经对红细胞面积有粗略估计。这里用 0.3 倍中位面积作为最小面积阈值可以过滤血小板和染料渣。参数说明strel(disk, 2) 的 2 是结构元素半径对直径 20 像素左右的红细胞来说这只刮掉边缘 2 像素的毛刺不会伤及细胞主体。如果图像中红细胞半径本身就很小比如小于 5 像素半径应降到 1。bwareaopen 的 minArea 如果设得太大会把小红细胞也删掉所以这个值要结合后面 4.2 节讨论的面积下界一起设定。需要注意的是形态学操作会轻微改变细胞面积。开运算使面积减少闭运算使面积增加两者叠加后总体变化较小。但如果结构元素半径过大面积偏小会变得不可忽略计数数量虽然可能不受影响但后续圆度计算会受影响所以不要贪图“干净”而用大半径。3.3 填充与边界处理imfill 和 imclearborder 到底该不该用红细胞中央淡染区在二值化后经常变成孔洞导致一个细胞看起来像甜甜圈。如果只做数量统计可以把孔洞填掉如果后续要做形态分类则要谨慎因为某些病理细胞本身就是环状填洞会把真实形态抹去。% 填充所有孔洞让每个红细胞变成一个实心区域 bw2 imfill(bw1, holes); % 清除与图像边界相连的对象避免不完整细胞被计数 bw3 imclearborder(bw2);逻辑说明imfill 以背景为起点检测闭合区域内的空洞并填满。imclearborder 把与图像边缘相连的所有前景连通域删掉。参数说明imfill 默认填充所有孔洞如果只想填充小面积孔洞需要先提取孔洞区域再按面积筛选代码会复杂一些。imclearborder 是一把双刃剑它会把视野边缘那些只露出一小部分的细胞整个删掉导致计数系统性偏小。所以这一行是否保留取决于你的计数策略。如果样本的边缘细胞本来就该被排除可以保留如果边缘细胞数量不少建议按下面的方式手动判断连通域是否真的碰到边界。% 只删除“边界接触面积较大”的目标而不是全删 cc0 bwconncomp(bw2, 8); s0 regionprops(cc0, BoundingBox, PixelIdxList, Area); delete_flag false(cc0.NumObjects, 1); for k 1:cc0.NumObjects bb s0(k).BoundingBox; % 如果边界框四边都远离图像边缘说明在内部 touches bb(1) 0.5 || bb(2) 0.5 || ... (bb(1) bb(3)) size(bw2, 2) - 0.5 || ... (bb(2) bb(4)) size(bw2, 1) - 0.5; if touches % 如果目标面积已经很小多半是残片删除 if s0(k).Area 0.5 * med_area delete_flag(k) true; end end end这段代码给出了一个折中策略只有那些面积小于中位面积一半的边缘接触目标才删除保留面积较大的边缘细胞。这样可以减少 imclearborder 造成的误删但也会带来边缘目标被重复计数的风险所以计数前要在图上画框检查一遍。4. 计数与特征筛选regionprops 才是真正的主力分割完成后二值图里剩下的就是一群候选区域。直接数连通域个数会得到虚高或偏低的结果因为血小板、染料渣、粘连成块的区域都混在里面。真正的计数工作集中在“特征筛选”这一步而 regionprops 是承担这项工作的主力。4.1 连通域分析bwconncomp 与 regionprops 的结构化输出MATLAB 里最常用的两个函数是 bwconncomp 和 regionprops。bwconncomp 返回每个连通域的像素索引regionprops 可以一次性提取面积、周长、质心、外接框、离心率等几何属性。cc bwconncomp(bw3, 8); stats regionprops(cc, Area, Perimeter, Centroid, BoundingBox, Eccentricity);逻辑说明bwconncomp 的第二个参数 8 表示 8 连通即上下左右和四个对角方向都算相邻。对一个圆形细胞4 连通和 8 连通差别不大但在边缘锯齿处8 连通更容易让两个轻微接触的细胞连成一个区域。这里先用 8 连通保持连通域完整后续用特征筛选接管粘连问题。regionprops 输出的 stats 是一个结构数组长度等于连通域个数每个元素都可以用 stats(k).Area 访问。参数说明如果只需要计数不画框可以少取几个属性以节省内存但 Centroid 和 BoundingBox 在调试时很有用我建议保留。注意cc.NumObjects 不能直接当计数结果。它不是红细胞数量只是候选区域数量。后面要做的就是把这堆候选区域分成“真细胞”和“非细胞”。4.2 几何特征过滤面积、圆度、离心率三件套我常用的过滤策略是基于三个特征面积、圆度、离心率。面积负责筛掉血小板和杂质圆度负责筛掉粘连块和不规则物体离心率用来筛掉被拉长的细胞碎片。areas [stats.Area]; perimeters [stats.Perimeter]; ecc [stats.Eccentricity]; circularity 4 * pi * areas ./ (perimeters .^ 2); % 以中位面积为锚点设置上下限 med_area median(areas); min_area 0.3 * med_area; max_area 2.5 * med_area; % 筛选逻辑 keep (areas min_area) (areas max_area); keep keep (circularity 0.75); keep keep (ecc 0.85); cell_stats stats(keep); red_cell_count numel(cell_stats);逻辑说明为什么用中位数而不是平均值因为平均值容易被极少数的粘连大块拉高而中位数对离群值更稳健。面积上下限以中位数倍数形式给出自动适应不同图像分辨率这一点在批处理时非常重要。圆度计算公式是 4πA/P²完整红细胞通常大于 0.75两个粘连细胞形成的“8 字形”圆度会明显低于单细胞。离心率表示区域被拉长的程度0 是圆1 是线段完整红细胞一般小于 0.85。三个条件串联能够把大部分杂质排除。参数说明min_area 0.3×med_area 适合血小板尺寸较小的图像如果血小板偏大要提到 0.5 倍。max_area 2.5×med_area 允许少量粘连块通过但如果粘连严重可以把上限降到 2.0 倍同时配合分水岭分割。circularity 0.75 是针对成熟红细胞的常见取值如果你的样本里大量红细胞呈椭圆形这个阈值要降到 0.65 左右但杂质误检也会增多。ecc 0.85 与圆度阈值有重叠但也有区别一个椭圆度较高的细长细胞圆度可能还大于 0.7而离心率却接近 0.9所以两个条件一起用能互补。如果调试时发现计数不稳定建议先把三个特征各自的分布画出来。用直方图看 areas、circularity、ecc 的分布阈值应该设在“真细胞峰”和“杂质峰”之间的低谷处而不是拍脑袋定。这一步虽然要花几分钟但能帮你弄清楚丢掉的到底是什么比反复试阈值高效得多。4.3 粘连重叠细胞的修正分水岭与圆检测的边界即便加了圆度和离心率过滤两个紧贴的红细胞仍然可能因为面积接近单细胞的两倍而被 max_area 滤掉或者因为圆度不足被丢弃。最直接的修正是引入分水岭分割把粘连区域切开。% 只对疑似粘连的大块区域做分水岭避免过度分割 label_map labelmatrix(cc); large_mask ismember(label_map, find(areas 1.8 * med_area)); D bwdist(~large_mask); D -D; D(~large_mask) -Inf; L watershed(D); % 分水岭边界位置置为 0 bw_split large_mask; bw_split(L 0) 0; % 重新对 bw_split 做连通域和特征筛选 cc_split bwconncomp(bw_split, 8); stats_split regionprops(cc_split, Area, Perimeter, Centroid, BoundingBox, Eccentricity);逻辑说明bwdist 计算背景到每个前景像素的欧氏距离取负后得到距离变换的“山谷”图像其中距离大的内部中心成为“盆地”。watershed 在盆地之间画分水岭线把粘连区域沿“山脊”切开。直接对全图做分水岭会把单个细胞内部存在的凹陷也当成分割边界导致过分割。所以先用 area 阈值圈出疑似粘连的大块区域只对这些区域做分水岭。参数说明1.8×med_area 是判断粘连的起点两个正常红细胞紧贴的面积通常在 1.8 倍以上如果细胞本身大小差异大这个倍数可能误判需要结合圆度一起看。分水岭后仍然要做一遍 4.2 的特征过滤因为分割可能会产生碎片。分水岭并不是万能的。对比较圆、轻微接触的细胞效果不错但对多个细胞堆叠、边界模糊的区域会切出奇怪形状。另一个更保守的交叉验证方案是 imfindcircles它用 Hough 变换检测圆形% 根据红细胞半径范围设定搜索区间 [centers_hough, radii_hough] imfindcircles(corrected, [8 20], ... ObjectPolarity, bright, Sensitivity, 0.9); hough_count size(centers_hough, 1);逻辑说明imfindcircles 会在 [8 20] 像素范围内查找圆形亮目标返回圆心和半径。它适合检测边界清楚的圆形细胞对重叠严重的区域会有漏检所以我不建议直接用它替代 regionprops 计数。参数说明半径范围最好来自你对样本的标定如果 range 设得过大会找到很多假圆Sensitivity 高则检测倾向更强但误检也更多。把 hough_count 和 red_cell_count 对比如果相差超过 10%就该回头检查二值图而不是盲目信某一种输出。5. 避坑从二值化到计数的 4 个高频翻车场景这一章记录我在实际跑这类项目时最常踩的坑。每一条都是“现象 → 原因 → 解决”的结构你可以直接拿来做排查清单。5.1 背景不均导致边缘大片误检现象全局阈值分割后图像边缘出现大片连续白色区域红细胞计数比目测多出 50% 以上。原因视野边缘亮度偏低边缘处的背景灰度与中心的细胞灰度重叠Otsu 找不到一个能同时区分两个区域的全局阈值。解决先看二值图如果伪前景呈片状并且贴合图像四边说明光照不均需要回到预处理做顶帽变换或者改用自适应阈值。我在某图像处理 Demo 里遇到过一张图有暗角用全局阈值跑出来的计数翻倍改成顶帽变换后马上就正常了。5.2 血小板混入面积下界设多少才合理现象计数结果里混入许多小圆颗粒面积筛不掉。原因血小板面积可能达到红细胞面积的 20%~30%如果你把 min_area 设为 0.3×med_area血小板刚好落在阈值附近。解决把面积下界提高到 0.5×med_area同时观察剩余小目标的半径。如果血小板体积更大可以加一个最小半径过滤在图上量出最小的红细胞半径 r_min再用 regionprops 输出的等效直径剔除面积过小的目标。血小板形状也接近圆所以只靠圆度无法排除面积是最有效的维度。5.3 粘连细胞被判成一个计数偏低现象肉眼数约有 300 个细胞程序输出只有 220 个且剩余连通域面积很多集中在 2 倍单细胞面积附近。原因两个细胞紧贴后连通域面积约为单细胞的两倍圆度因为形状不规则而低于阈值所以被 max_area 或 circularity 过滤掉既没合并计数也没分开计数。解决先看一下面积直方图是不是有明显的第二峰。如果有对面积大于 1.8×med_area 的区域做分水岭。分水岭后重新过一遍半径和圆度过滤通常能把计数拉回来。这里要接受一个现实分水岭算法不能完美解决所有堆叠你的目标是把系统性偏差控制在一定范围内而不是追求和肉眼完全一致。5.4 边缘细胞被 imclearborder 误删现象所有图像计数都偏小约 5%~10%且越靠近视野边缘偏差越明显。原因imclearborder 把与边界相交的细胞全部删除。很多细胞只是露出一小条边主体仍在视野内删掉它们会导致系统性低估。解决不要全局使用 imclearborder。可以按 3.3 节的方式只删除面积小于 0.5×med_area 且接触边界的区域。如果样本制备时边缘确实有残渣可以在采集阶段就只取视野中央区域但算法上要保留完整的边缘细胞避免人为引入计数偏差。5.5 形态学开运算把小细胞“开”没了现象图像里肉眼能看见的小红细胞没有出现在结果中。原因strel(disk, 2) 的结构元素半径相对这些小细胞过大开运算把它们整个腐蚀掉无法恢复。解决把结构元素半径调整为 1或者改为先用 bwareaopen 去掉小杂质再配合面积下界筛选。这里的关键是结构元素半径要相对于整张图的细胞半径来设计而不是固定一个值。如果图像分辨率较高2 像素可能没问题一旦换一组分辨率低的图片同样的 2 就可能把目标抹掉。所以我把结构元素半径也放进参数文件里跟随输入图的分辨率一起更新。6. 进阶把你的计数程序变成可复用的批处理工具一步步跑通单张图之后下一步就是把脚本改成函数。我之前在某图像处理 Demo 里把所有步骤都写在 main.m 里每换一张图就要手动改阈值后来花了半小时整理成带参数结构的函数之后的效率完全不一样。最简单的方式是把整个流程封装成两个函数一个处理单张图像一个处理文件夹并输出表格。function [n_cells, cell_stats] count_red_cells(img, params) % 预处理 gray rgb2gray(img); low double(prctile(gray(:), params.lowPct)); high double(prctile(gray(:), params.highPct)); enhanced imadjust(gray, [low/255 high/255], [0 1]); denoised medfilt2(enhanced, [params.medSize params.medSize]); se_bg strel(disk, params.bgRadius); corrected imsubtract(denoised, imopen(denoised, se_bg)); % 分割 bw0 imbinarize(corrected, adaptive, ... Sensitivity, params.sensitivity, ForegroundPolarity, bright); bw1 imopen(bw0, strel(disk, params.morphRadius)); bw1 imclose(bw1, strel(disk, params.morphRadius)); bw2 imfill(bw1, holes); % 特征筛选 cc bwconncomp(bw2, 8); stats regionprops(cc, Area, Perimeter, Centroid, BoundingBox, Eccentricity); areas [stats.Area]; perimeters [stats.Perimeter]; ecc [stats.Eccentricity]; circularity 4*pi*areas ./ (perimeters.^2); med_area median(areas); keep (areas params.minAreaFactor * med_area) ... (areas params.maxAreaFactor * med_area) ... (circularity params.minCircularity) ... (ecc params.maxEccentricity); cell_stats stats(keep); n_cells numel(cell_stats); end这个函数把之前所有步骤串起来参数集中在结构体 params 里。批处理时用 dir 遍历文件夹把每张图的计数结果、Hough 对照数量、图像文件名写进表格files dir(fullfile(images, *.tif)); results table(); for i 1:numel(files) img imread(fullfile(files(i).folder, files(i).name)); params.lowPct 1; params.highPct 99; params.medSize 3; params.bgRadius 30; params.sensitivity 0.45; params.morphRadius 2; params.minAreaFactor 0.3; params.maxAreaFactor 2.5; params.minCircularity 0.75; params.maxEccentricity 0.85; [n_cells, ~] count_red_cells(img, params); [centers_hough, ~] imfindcircles(img, [8 20], ... ObjectPolarity, bright, Sensitivity, 0.9); results.ImageName(i) string(files(i).name); results.CellCount(i) n_cells; results.HoughCount(i) size(centers_hough, 1); end writetable(results, count_results.xlsx);这个批处理工具同时保留了两套计数结果一旦某张图两数偏差超过 10%你就有依据回去抓问题。我更建议在做批处理时把每张图的二值图和 filtered 图像都保存一份快照方便之后追溯。这样换了染色条件或放大倍率后改参数不再靠猜而是直接看快照对比。最后提醒一句不要迷信某个固定阈值把它们都改成相对值才能抵抗不同染色批次带来的灰度漂移。希望帮到你。本文还有配套的精品资源点击获取