阈值法这三个字在图像处理的圈子里有点尴尬。刚入门的人觉得它太简单几行代码就能出结果没什么技术含量做过一段时间项目的人反而会对它保持谨慎因为踩过的坑太多了——同一套参数在实验室图片上跑得漂漂亮亮换一张现场采集的图就满屏噪点或者整块丢失。我自己最早接触OpenCV的时候也是把cv2.threshold当成万能工具直到在一个工业检测项目上因为阈值选取的问题连续调试了两天才真正理解固定阈值、自适应阈值、大津阈值这三种方法各自的脾气。这篇内容就是围绕传统图像分割中的阈值法展开把三种方法的原理、调用细节、参数计算、调参经验以及实际工程中的选型逻辑掰开讲清楚适合正在学习OpenCV图像分割的初学者也适合需要把传统方法落地到实际项目里的开发者参考。1. 阈值分割这件事为什么老手反而越用越谨慎1.1 阈值法在图像处理流水线里的真实位置图像分割的任务说直白一点就是把一张图里我们关心的部分和背景分开。深度学习方法这几年确实强势U-Net、YOLO的分割分支在各种数据集上刷分刷得很高但在很多实际场景里传统阈值法依然是第一道工序。原因不复杂它计算量小、可解释性强、参数少、调试直观而且不依赖训练数据。你拿到一台工业相机拍回来的图像想快速判断目标区域在哪里用阈值法先做一次粗分割往往比直接上模型更省时间。阈值法的核心假设非常朴素图像中目标和背景在灰度上存在差异找到一个或多个灰度值作为分界线把像素归类即可。这个假设在理想光照条件下成立但现实世界的光照很少理想。这就引出了三种方法的分野——固定阈值假设整张图共享一个最优分界值自适应阈值假设每个局部区域有自己的最优分界值大津阈值则试图用统计方法自动算出那个全局分界值。我在实际项目里见过太多人一上来就用固定阈值结果被光照不均打得措手不及。也见过有人不分场景地堆自适应阈值最后发现目标内部被掏空。理解每种方法背后的假设比记住函数参数重要得多。1.2 三种阈值法的核心差异与适用边界先用一个不那么严谨但足够直观的类比来说明三者的区别。假设你要在一群人里区分穿深色衣服和浅色衣服的人固定阈值相当于你事先规定“灰度低于128的算深色”不管这群人站在阳光下还是阴影里标准不变。这种做法在光照均匀、目标与背景对比明显时非常高效但一旦人群从亮处走到暗处判断就会失准。自适应阈值相当于你给每个人画一个小圈子只在小圈子里比较谁深谁浅然后决定这个人算深色还是浅色。这样光照变化被局部化处理了但代价是如果小圈子里全是深色衣服那个被圈住的人可能会被误判成浅色——因为他在局部里确实相对更亮。大津阈值相当于你先统计这群人衣服灰度的分布发现明显分成两堆然后自动找出让两堆分得最开的那个灰度值作为标准。它不需要你手动指定阈值但它的前提是分布确实呈现双峰如果衣服灰度是连续渐变的它算出来的阈值就没那么可靠。这三种方法在OpenCV里对应的调用分别是cv2.threshold配合THRESH_BINARY、cv2.adaptiveThreshold、以及cv2.threshold配合THRESH_OTSU。下面逐一来拆。2. 固定阈值入门第一课也是最容易踩坑的一课2.1 cv2.threshold 的参数逐个拆解固定阈值的函数签名是这样的retval, dst cv2.threshold(src, thresh, maxval, type)四个参数里src要求是单通道图像通常是灰度图。如果你直接把彩色图丢进去OpenCV不会报错但会按通道分别处理或者产生意料之外的结果所以养成先cv2.cvtColor转灰度的习惯。thresh就是那个分界值maxval是超过阈值时赋予的像素值type决定比较方式。type的常用取值有五种这里用表格说明清楚因为很多人只记得THRESH_BINARY其他几种在特定场景下非常好用阈值类型判断逻辑典型用途THRESH_BINARY大于thresh取maxval否则取0提取亮目标THRESH_BINARY_INV大于thresh取0否则取maxval提取暗目标THRESH_TRUNC大于thresh取thresh否则保持不变压制高亮区域THRESH_TOZERO大于thresh保持不变否则取0保留亮部原值THRESH_TOZERO_INV大于thresh取0否则保持不变保留暗部原值这里有个容易忽略的点返回的retval在固定阈值模式下就是你传入的thresh看起来没什么用。但当你后面用大津法时retval就变成了算法自动计算出的阈值这个值值得打日志记录下来方便分析。maxval一般填255因为8位灰度图的取值范围是0到255。但如果你后续要做掩膜运算填1也可以这样得到的就是一个二值掩膜直接用cv2.bitwise_and做图像抠取。我个人的习惯是统一用255需要掩膜时再除以255或者用astype转换。2.2 双峰直方图的判断方法固定阈值能不能用好关键不在代码而在于你选的那个thresh值是否合理。判断依据是灰度直方图的形态。如果直方图明显呈现两个峰中间有一个低谷那个低谷对应的灰度值就是比较理想的分界点。看直方图的代码很简单import cv2 import numpy as np import matplotlib.pyplot as plt gray cv2.imread(target.jpg, cv2.IMREAD_GRAYSCALE) hist cv2.calcHist([gray], [0], None, [256], [0, 256]) plt.plot(hist) plt.xlabel(Gray Level) plt.ylabel(Pixel Count) plt.show()如果画出来的曲线是双峰夹一谷固定阈值大概率能work。如果没有明显双峰比如前景灰度分布很宽或者背景本身有渐变那就要考虑换方法了。我在一个检测金属表面缺陷的项目里最初用固定阈值提取缺陷区域直方图看起来有个小峰但实际分割效果时好时坏。后来把图像分块统计直方图发现不同区域的峰位偏移了将近40个灰度级固定阈值根本兜不住这才转向自适应阈值。提示判断是否适合固定阈值不要只看一张图的直方图至少抽十张不同时间、不同位置采集的图分别看。如果峰位漂移超过20个灰度级固定阈值就不是好选择。2.3 固定阈值的实操代码与注意点一个完整的固定阈值处理流程通常包括读取、转灰度、滤波、阈值化、形态学处理几个步骤。滤波这一步很多人会跳过导致阈值化后噪点满天飞。高斯滤波对抑制随机噪声效果不错但会轻微模糊边缘中值滤波对椒盐噪声更有效且能较好保留边缘。具体选哪个看噪声类型。import cv2 import numpy as np img cv2.imread(part.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯滤波核大小根据噪声尺度调整 blurred cv2.GaussianBlur(gray, (5, 5), 0) # 固定阈值分割 ret, binary cv2.threshold(blurred, 127, 255, cv2.THRESH_BINARY) # 形态学开运算去小噪点 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) opened cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) cv2.imshow(original, gray) cv2.imshow(binary, binary) cv2.imshow(opened, opened) cv2.waitKey(0) cv2.destroyAllWindows()这段代码里有两个经验点。第一滤波核大小不一定是5x5如果图像分辨率很高比如工业相机常见的500万像素5x5的高斯核可能几乎不起作用需要加大到9x9甚至更大。但核越大边缘越模糊这是一对矛盾需要根据目标边缘的锐利程度来权衡。第二形态学操作的核大小也要跟着调整3x3在小图上合适大图上可能需要5x5。我一般会先跑一遍看看效果再决定是否调整。还有一点值得强调固定阈值的thresh值不要拍脑袋填127。127只是中间值不代表最优。正确的做法是先画出直方图找到谷底位置再把这个值填进去。如果谷底不明显可以通过Otsu方法先估一个值作为参考再手动微调。这其实就引出了后面的内容。3. 自适应阈值把整张图切成无数个小战场3.1 局部均值法和高斯加权法的区别自适应阈值的思路是不再用一个全局阈值而是对每个像素计算它周围邻域内的统计量用这个统计量减去一个常数作为该像素的阈值。OpenCV提供了两种计算邻域统计量的方式ADAPTIVE_THRESH_MEAN_C计算的是邻域内像素的算术平均值。这种方式的响应比较均匀但在边缘附近容易产生块状伪影因为均值对局部变化的响应不够灵敏。ADAPTIVE_THRESH_GAUSSIAN_C计算的是邻域内像素的高斯加权和离中心越近的像素权重越大。这种方式更符合图像局部相关性的直觉边缘过渡更自然是我在大多数场景下的首选。参数签名dst cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)其中thresholdType只能是THRESH_BINARY或THRESH_BINARY_INV这是自适应阈值的限制之一它只做二值化不支持TRUNC、TOZERO那几种模式。blockSize必须是奇数且大于1决定了邻域窗口的大小。C是从统计量中减去的常数用来微调阈值的偏移。3.2 blockSize和C怎么调参数计算与经验取值这两个参数是自适应阈值调参的核心很多人卡在这里不知道怎么下手。blockSize的取值直接决定了“局部”的尺度。如果目标物体的尺寸大概是50个像素宽blockSize应该明显大于这个尺寸否则窗口全部落在目标内部统计量无法反映目标与背景的差异。经验公式是blockSize取目标典型宽度的1.5到2倍左右并且取奇数。比如目标宽度50像素blockSize可以取75到101之间的奇数实际取81或101比较常见。但blockSize也不是越大越好。取太大就退化成全局阈值了失去了自适应处理光照不均的意义。我在处理一张光照从左上到右下逐渐变暗的钢板图像时试过blockSize从31一直加到201最后在101附近效果最好。太小的话钢板本身的纹理被当成目标分割出来形成大量伪影太大的话右侧暗区的目标还是分割不出来。C的取值决定了阈值相对于局部统计量的偏移量。对于亮背景暗目标的场景C一般取正值这样阈值会比局部均值更高更容易把目标分出来。对于暗背景亮目标C取负值或者较小的正值。C的取值范围通常在-10到30之间我常用的起点是C10然后根据效果增减。这里有一个我自己的调参顺序建议步骤操作目的1固定C10从31开始逐步增大blockSize观察目标是否完整找到目标不再丢失的最小值2固定blockSize调整C从-5到30观察噪点和目标边界的平衡3交叉微调在两者间找到最佳组合调参时最好把中间结果保存下来命名带参数值方便对比。不要只盯着最终结果看中间的二值化结果更能反映参数变化的影响。3.3 自适应阈值的完整实战代码下面这段代码处理的是一个光照不均的文本图像场景左侧偏亮、右侧偏暗用固定阈值处理会导致右侧文字完全丢失用自适应阈值可以较好地保留两侧文字。import cv2 import numpy as np img cv2.imread(text_page.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 轻微滤波抑制纸张纹理 blurred cv2.GaussianBlur(gray, (3, 3), 0) # 自适应阈值高斯加权blockSize31C15 binary cv2.adaptiveThreshold( blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 15 ) # 去除孤立小噪点 kernel np.ones((2, 2), np.uint8) cleaned cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) cv2.imshow(gray, gray) cv2.imshow(adaptive, binary) cv2.waitKey(0) cv2.destroyAllWindows()处理文本图像时blockSize可以取得小一些因为文字笔画细局部窗口太大会把笔画和周围空白一起平均导致笔画断裂。但也不能太小太小会把纸张纹理当成文字。31是一个比较稳妥的起点。C取15是因为文字是暗目标、纸张是亮背景需要把阈值抬高一些才能把文字从背景中分离出来。换成其它场景比如检测亮背景上的暗色瑕疵C可能需要调整到5到10之间。这个没有万能值必须结合具体图像试出来。注意自适应阈值对噪声非常敏感因为它逐像素计算局部统计量噪声会直接污染统计结果。使用前做一次滤波几乎是必须的尤其在中值滤波和高斯滤波之间要按噪声类型选择。椒盐噪声用中值滤波高斯噪声用高斯滤波。4. 大津阈值让算法替你做决定4.1 类间方差最大化的数学直觉大津法Otsus Method的核心思想是遍历所有可能的阈值把像素分成前景和背景两类计算这两类的类间方差取使类间方差最大的那个阈值作为最终结果。类间方差的公式是σ² w0 × w1 × (μ0 - μ1)²其中w0和w1分别是前景和背景像素数占总像素数的比例μ0和μ1分别是两类的平均灰度值。这个公式的直观理解是w0×w1衡量了两类的数量均衡程度两类数量越接近这个值越大(μ0-μ1)²衡量了两类灰度中心的距离距离越远越说明分得开。两者相乘就是要找一个既让两类数量相对均衡、又让两类灰度差异最大的分界点。这个数学推导看起来有点绕但换个说法就清楚了假设你有一堆混在一起的黑豆和黄豆你想找一个灰度标准把它们分开。如果标准定得太极端比如把所有豆子都归成一类那w0×w1为0方差为0如果标准定得刚好两类豆子各自聚集均值差很大方差就大。大津法就是自动找那个让方差最大的标准。4.2 THRESH_OTSU的调用方式与常见误区在OpenCV里调用大津法是把THRESH_OTSU和THRESH_BINARY或THRESH_BINARY_INV做位或运算ret, binary cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU)这里第一个参数0会被忽略因为Otsu会自动计算阈值。返回值ret就是算出来的最优阈值这个值一定要打印出来看看。我见过有人调用之后不看ret结果发现图像整体偏暗时算出来的阈值是40而他预期的是120分割结果自然不对。import cv2 gray cv2.imread(coins.jpg, cv2.IMREAD_GRAYSCALE) ret, binary cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) print(fOtsu calculated threshold: {ret})常见误区有三个。第一个是以为Otsu能处理所有图像实际上它要求直方图呈双峰分布如果图像是单峰或者多峰效果会大打折扣。第二个是忽略了Otsu对噪声的敏感性噪声会改变直方图形状导致算出的阈值偏移。第三个是把Otsu用在彩色图上但Otsu的实现假设输入是单通道多通道图像需要先转灰度。4.3 大津阈值的局限与补救手段Otsu最大的局限是它本质上还是一个全局阈值只是阈值是自动算出来的。如果图像存在光照不均即使阈值算得再准也只是一个折中的全局值无法同时适应亮区和暗区。这时候的补救手段有几个方向一是先做光照校正再上Otsu。常用的光照校正方法包括顶帽变换、背景减除、以及基于大核高斯模糊的背景估计。顶帽变换特别适合处理背景亮度不均的情况它提取的是比背景亮的细节。二是分区域做Otsu。把图像分成若干子块每个子块单独算Otsu阈值再做插值合并。这种方式介于全局Otsu和自适应阈值之间在有些场景下比自适应阈值更稳定。三是把Otsu的结果作为自适应阈值的参考。先用Otsu得到一个全局阈值观察它和理想阈值的偏差再据此调整自适应阈值的C参数。我用过顶帽变换配合Otsu处理一张光照不均的零件图代码如下import cv2 import numpy as np gray cv2.imread(part_uneven.jpg, cv2.IMREAD_GRAYSCALE) # 顶帽变换核大小要大于目标尺寸 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (51, 51)) tophat cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, kernel) # 对顶帽结果做Otsu ret, binary cv2.threshold(tophat, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) print(fThreshold after tophat: {ret})顶帽变换的核大小选择很关键要明显大于目标尺寸否则会把目标本身也当成背景抹掉。这里零件大约30像素宽所以用了51x51的椭圆核。这个参数需要根据实际目标尺寸来定不能照搬。5. 三种方法横向对比与场景选型5.1 对比表格从参数到适用条件的全维度比较把三种方法放在一起对比能更清楚地看出各自的定位对比维度固定阈值自适应阈值大津阈值阈值来源手动指定每像素局部统计自动全局计算核心参数thresh, maxvalblockSize, C无自动光照均匀性要求高低中等对噪声敏感度中等高中等计算复杂度极低中等低边缘保持好一般好适用场景光照稳定、对比明显光照不均、背景渐变双峰分布、光照基本均匀从表格可以看出自适应阈值虽然对光照不均最鲁棒但代价是噪声敏感和边缘质量下降。固定阈值最简单但对使用条件要求最高。大津阈值在两者之间适合那些光照基本均匀但阈值不好手动确定的场景。5.2 从光照条件出发的选型决策选型的时候我一般的判断流程是这样的第一步看光照条件。如果图像采集环境是封闭的、有稳定光源光照均匀那优先考虑固定阈值或大津阈值。如果图像采集环境开放、光照变化大比如户外、或者有大面积反光的场景优先考虑自适应阈值。第二步看直方图。如果直方图呈现明显双峰大津阈值是一个很好的起点不需要手动调阈值。如果直方图是单峰或者谷底不明显大津阈值不可靠需要回到固定阈值或者自适应阈值。第三步看目标尺度。如果目标尺寸和图像尺寸比例悬殊比如大图里的小目标自适应阈值的blockSize选择会很困难这时候可能要考虑先裁剪区域或者先做其它预处理。第四步看实时性要求。固定阈值最快大津次之自适应阈值最慢。如果处理的是高帧率视频流固定阈值或者大津阈值更合适如果是离线图像处理自适应阈值的计算开销可以接受。这四个步骤走下来基本能确定用哪种方法。当然实际项目中经常需要组合使用比如先用大津阈值做全局判断再在局部用自适应阈值细化。方法之间不是互斥的。6. 常见问题排查与踩坑实录6.1 问题速查表下面这张表整理了我在实际项目中遇到的典型问题和处理方式覆盖了三种阈值法的常见故障现象可能原因排查方向整张图全黑或全白阈值取值过于极端打印Otsu返回值或画直方图确认目标内部出现空洞自适应阈值blockSize过小或C不当增大blockSize调整C噪点大量出现未滤波或滤波不足增加高斯或中值滤波边缘区域分割失败光照不均全局阈值不适用改用自适应阈值或做光照校正Otsu阈值明显偏离预期直方图非双峰或噪声干扰检查直方图形状先滤波再Otsu自适应阈值结果呈块状blockSize过大或图像本身有块状纹理减小blockSize或先做平滑目标断裂成多段阈值过高或目标本身灰度不连续降低阈值检查目标灰度分布程序处理彩色图报错输入了3通道图像用cvtColor转灰度这张表里的每一条我都至少踩过一次尤其是“目标内部出现空洞”和“Otsu阈值偏离预期”这两条在项目初期花了不少时间定位。6.2 我踩过的几个坑和对应的解决办法第一个坑是忽略图像位深。有一次从相机采集回来的是16位图像我直接用cv2.threshold按8位处理结果整个图像几乎全白。原因是16位图像的像素范围是0到65535而threshold的maxval填了255导致大部分像素都超过阈值。解决办法是先做归一化或者用cv2.normalize转到8位或者把阈值和maxval都按16位范围设置。第二个坑是滤波顺序搞反。我最初习惯先阈值化再滤波觉得这样能直接去掉二值图中的噪点。实际上这样做会破坏目标边缘因为二值化后目标和背景的边界变得生硬滤波很容易把细小的目标整个抹掉。正确的顺序是先对灰度图滤波再阈值化最后用形态学做后处理清理噪点。第三个坑是自适应阈值的blockSize用了偶数。OpenCV会直接报错或者行为异常因为偶数窗口没有唯一的中心像素。这个错误很明显但初学者容易忽略尤其是从某个教程复制代码时没注意参数。养成使用奇数的习惯就好。第四个坑是忘了检查图像是否读取成功。cv2.imread在文件路径错误时返回None而不报错后续操作就会抛出各种奇怪的异常。加一行if img is None的判断能省很多调试时间。第五个坑是在自适应阈值后直接做轮廓检测没有做形态学清理。二值化后的噪点会被当成小轮廓检测出来导致轮廓数量远超预期。加一步开运算或者闭运算能显著改善结果。开运算去小白点闭运算填小黑洞具体选哪个看噪点类型。6.3 几个不那么显然的实操技巧关于固定阈值的thresh值选取有一个技巧是用滑动条实时调节。cv2.createTrackbar可以动态调整阈值边拖动边看效果找到最佳值后再固化到代码里。这个方式在调试阶段比反复改代码重跑高效得多。关于自适应阈值的C参数我发现在处理文字类图像时C取正值效果更好因为文字笔画需要从背景中被“抬高”才能分离。而在处理暗背景亮目标时C取较小的值或者负值反而更好。这个规律和图像的极性有关理解这一点能少试很多参数组合。关于大津阈值有一个容易被忽略的点是它可以配合THRESH_BINARY_INV使用。当目标比背景暗时用INV模式可以直接得到目标为白色的二值图省去反转步骤。这两种组合的返回值都是同一个阈值只是输出反相。还有一点cv2.threshold返回的ret值在调试时非常有用建议在代码中加日志输出。如果发现某个批次图像的ret值突然偏移很大说明这批图像的光照或内容发生了显著变化可能需要重新审视采集条件。7. 从阈值法延伸到更完整的处理流程7.1 阈值化之后该做什么阈值化本身只是分割的第一步得到二值图之后通常还有一系列后处理动作。最常见的是形态学操作开运算去噪点、闭运算填空洞、梯度运算提取边缘。再往后是轮廓检测cv2.findContours配合cv2.drawContours可以可视化分割结果也可以基于轮廓计算面积、周长、圆度等特征。我在一个计数项目里就是先用Otsu阈值化再做开运算去噪然后找轮廓最后通过轮廓面积过滤掉小噪点得到准确的计数结果。整个流程加起来不到三十行代码但每一步的参数都需要根据实际图像调整。比如开运算的核大小3x3在低分辨率图上够用500万像素的图上就要用到5x5甚至7x7。7.2 什么时候该考虑换方法阈值法不是万能的。如果在尝试了固定阈值、自适应阈值、大津阈值以及它们的组合之后分割效果依然达不到要求就要考虑换方法了。常见的替代方案包括基于边缘的分割Canny加轮廓、基于区域的分割分水岭算法、以及基于机器学习的分割方法。传统方法里分水岭算法适合处理粘连目标的分割Canny适合边缘清晰的场景。如果这些都不行再考虑深度学习方案。判断是否该换方法的信号有几个直方图完全单峰且没有可利用的灰度差异目标与背景在灰度上高度重叠图像噪声严重到阈值化后无法通过形态学清理光照不均严重到自适应阈值也失效。出现这些情况时继续调阈值参数只是浪费时间应该果断换方向。8. 关于参数调试和工程落地的一些个人体会阈值法看起来简单但真正把参数调到稳定可靠需要的是对图像本身的深入理解而不是对函数参数的熟练记忆。我现在的习惯是拿到一批新图像先抽几张画直方图观察灰度分布再跑一遍Otsu看自动阈值落在哪里然后决定是用固定阈值还是自适应阈值。这个流程走下来比直接凭感觉填参数快得多。还有一个体会是不要追求单张图像上的完美效果。阈值法的参数应该在一批图像上取一个折中而不是为每张图单独调参。实际项目里图像是连续采集的参数应该固定下来跑流水线。如果发现某几张图效果特别差先分析这几张图的采集条件是否和批次整体不一致而不是急着改全局参数。最后说一个具体的技巧。在调试自适应阈值时我会把blockSize和C的几组典型值生成一个对比图用matplotlib排成网格显示这样一眼就能看出参数变化对结果的影响趋势。这比逐组参数跑代码看结果高效得多也更容易找到合适的参数范围。import cv2 import matplotlib.pyplot as plt gray cv2.imread(sample.jpg, cv2.IMREAD_GRAYSCALE) blurred cv2.GaussianBlur(gray, (3, 3), 0) block_sizes [11, 31, 51, 101] c_values [5, 15, 25] fig, axes plt.subplots(len(block_sizes), len(c_values), figsize(12, 12)) for i, bs in enumerate(block_sizes): for j, c in enumerate(c_values): binary cv2.adaptiveThreshold( blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, bs, c ) axes[i][j].imshow(binary, cmapgray) axes[i][j].set_title(fbs{bs}, C{c}) axes[i][j].axis(off) plt.tight_layout() plt.show()这段代码把12种参数组合的结果排成4x3的网格调试时非常直观。我后来把这个思路扩展到了固定阈值和大津阈值的对比上基本上半小时内就能摸清一批图像的参数范围。对于刚开始接触OpenCV图像分割的朋友我的建议是先把固定阈值用熟理解直方图和阈值的关系再上手自适应阈值理解局部统计量的作用最后用大津阈值理解自动阈值计算的逻辑。这三种方法虽然简单但它们是理解更复杂分割算法的基石。把基石打牢了后面学分水岭、GrabCut、甚至深度学习方法时思路会清晰很多。