B04 - 图像处理 - 图像阈值

将图像转为二值处理,减少干扰区域,目前二值化办法有定值二值化、自适应二值化

按照颜色对图像进行分类,可分为几类?

  • 二值图像:只有黑色和白色两种颜色的图像。每个像素点可以用 0/1 表示,0 表示黑色,1 表示白色;
  • 灰度图像:只有灰度的图像。每个像素点用 8bit 数字 [0,255] 表示灰度,如:0 表示纯黑,255 表示纯白;
  • 彩色图像:彩色图像通常采用红色(R)、绿色(G)和蓝色(B)三个色彩通道的组合表示

什么是图像二值化?

  • 将我们感兴趣的像素与其他像素(最终将被拒绝)区分开来,通常根据阈值(根据要解决的问题确定)对每个像素强度值进行比较
  • 灰度图片或者彩色图片转为二值化图片的过程

Opencv 如何进行二值化?

  • cv: : threshold :固定阈值二值化或大津二值化
  • cv: : adaptiveThreshold :自适应阈值二值化
  • cv: : inRange :固定阈值区间二值化

什么是阈值处理?

  • 根据灰度值和灰度值的限制将图像划分为多个区域,或提取图像中的目标物体,是最基本的阈值处理方法
  • 如果图像的直方图存在明显边界,容易找到图像的分割阈值;但如果图像直方图分界不明显,则很难找到合适的阈值,甚至可能无法找到固定的阈值有效地分割图像
  • 函数 threshold () 可以将灰度图像转换为二值图像,图像完全由像素 0 和 255 构成,呈现出只有黑白两色的视觉效果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 生成灰度图像
    hImg, wImg = 512, 512
    img = np.zeros((hImg, wImg), np.uint8) # # 创建黑色图像 RGB=0
    cv2.rectangle(img, (60,60), (450,320), (127,127,127), -1) # -1 表示矩形填充
    cv2.circle(img, (256, 256), 120, (205,205,205), -1) # -1 表示圆形填充
    # 添加高斯噪声
    mu, sigma = 0.0, 25.0
    noiseGause = np.random.normal(mu, sigma, img.shape)
    imgNoise = img + noiseGause
    imgNoise = np.uint8(cv2.normalize(imgNoise, None, 0, 255, cv2.NORM_MINMAX)) # 归一化为 [0,255]
    # 阈值处理
    ret, imgBin1 = cv2.threshold(img, 63, 255, cv2.THRESH_BINARY) # 阈值分割, thresh=63
    ret, imgBin2 = cv2.threshold(img, 125, 255, cv2.THRESH_BINARY) # 阈值分割, thresh=125
    ret, imgBin3 = cv2.threshold(img, 175, 255, cv2.THRESH_BINARY) # 阈值分割, thresh=175
    show_images([img,imgNoise,imgBin1,imgBin2,imgBin3])
    plt.bar(bins[:-1], histNP[:])

什么是全局阈值处理方法?

  • 当图像中的目标和背景的灰度分布较为明显时,可以对整个图像使用固定阈值进行全局阈值处理
  • 为了获得适当的全局阈值,可以基于灰度直方图进行迭代计算:设定初始阈值 T 分割图像,将图像分为大于 T 和小于 T 的部分;分别计算这两部分的灰度平均值 m1, m2,然后基于 T=(m1+m2)/2 更新阈值,重复迭代,知道 T 变化小于阈值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    img = cv2.imread("../images/Fig0940a.tif", flags=0)
    deltaT = 1 # 预定义值
    histCV = cv2.calcHist([img], [0], None, [256], [0, 256]) # 灰度直方图
    grayScale = range(256) # 灰度级 [0,255]
    totalPixels = img.shape[0] * img.shape[1] # 像素总数
    totalGary = np.dot(histCV[:,0], grayScale) # 内积, 总和灰度值
    T = round(totalGary/totalPixels) # 平均灰度
    while True:
    numC1, sumC1 = 0, 0
    for i in range(T): # 计算 C1: (0,T) 平均灰度
    numC1 += histCV[i,0] # C1 像素数量
    sumC1 += histCV[i,0] * i # C1 灰度值总和
    numC2, sumC2 = (totalPixels-numC1), (totalGary-sumC1) # C2 像素数量, 灰度值总和
    T1 = round(sumC1/numC1) # C1 平均灰度
    T2 = round(sumC2/numC2) # C2 平均灰度
    Tnew = round((T1+T2)/2) # 计算新的阈值
    print("T={}, m1={}, m2={}, Tnew={}".format(T, T1, T2, Tnew))
    if abs(T-Tnew) < deltaT: # 等价于 T==Tnew
    break
    else:
    T = Tnew
    # 阈值处理
    ret, imgBin = cv2.threshold(img, T, 255, cv2.THRESH_BINARY) # 阈值分割, thresh=T

基于边缘信息改进全局阈值处理?

  • 对于大背景中的小目标,图像的直方图受到背景的大波峰控制,即由于背景内容太多而将目标在直方图中淹没了,使全局阈值处理容易失败
  • 如果只利用接近目标和背景之间的边缘的像素,忽略无效的背景区域像素对直方图的贡献,可以改善直方图的分布,从而便于通过阈值处理进行分割
  • 步骤:1) 计算图像 f (x, y) 的梯度算子,得到梯度幅值图像;2) 对梯度幅值图像进行二值处理,选取强边缘像素作为遮罩模板;3)基于遮罩模板计算图像 f (x, y) 的直方图分布;4)由基于遮罩模板的直方图找到适当的分割阈值,对图像 f (x, y) 进行全局阈值处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    img = cv2.imread("../images/Fig1041a.tif", flags=0)
    # # 全局阈值处理,作为参照比较
    histCV1 = cv2.calcHist([img], [0], None, [256], [0, 256]) # 灰度直方图
    totalPixels = img.shape[0] * img.shape[1] # 像素总数
    totalGray = np.dot(histCV1[:,0], range(256)) # 内积, 总和灰度值
    meanGray = round(totalGray/totalPixels) # 平均灰度
    ret, imgBin = cv2.threshold(img, meanGray, 255, cv2.THRESH_BINARY) # thresh=meanGray
    # (1) 计算 Sobel 梯度算子
    SobelX = cv2.Sobel(img, cv2.CV_32F, 1, 0) # 计算 x 轴方向
    SobelY = cv2.Sobel(img, cv2.CV_32F, 0, 1) # 计算 y 轴方向
    grad = np.sqrt(SobelX**2 + SobelY**2)
    gradMax = np.int(np.max(grad))
    # (2) 设置阈值 T=0.3*gradMax,对梯度图像进行阈值处理,作为遮罩模板
    _, maskBW= cv2.threshold(np.uint8(grad), 0.3*gradMax, 255, cv2.THRESH_BINARY)
    # (3) 计算基于遮罩模板的直方图分布,以排除无效背景像素的影响
    histCV2 = cv2.calcHist([img], [0], maskBW, [256], [0, 256])
    histCV2[0] = 0
    # (4) 排除无效背景像素影响后,进行阈值处理
    Tmask = 120 # 观察直方图 histCV2,找到分割阈值
    _, imgBin2 = cv2.threshold(img, Tmask, 255, cv2.THRESH_BINARY)
    show_images([img,imgBin,maskBW,imgBin2])
    plt.bar(range(256), histCV1[:,0])
    plt.bar(range(256), histCV2[:,0])

基于 Laplace 边缘信息改进全局阈值处理?

  • 使用 Laplace 算子计算梯度,可以得到亮点的边缘像素,忽略背景区域像素对直方图的贡献,可以改善直方图的分布,从而便于通过阈值处理进行分割
  • 对于酵母细胞图像,希望通过全局阈值处理等等图像中与亮点对应的区域。如果直接使用 OTSU 方法可以分割细胞区域,但不能检测亮点
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    img = cv2.imread("../images/Fig1043a.tif", flags=0)
    # # 全局阈值处理,作为参照比较
    histCV1 = cv2.calcHist([img], [0], None, [256], [0, 256]) # 灰度直方图
    ret1, imgOtsu = cv2.threshold(img, 127, 255, cv2.THRESH_OTSU) # 阈值分割, thresh=T
    # (1) 计算 Laplacian 梯度算子
    laplace = cv2.Laplacian(img, cv2.CV_32F, ksize=3) # Laplace 卷积算子
    grad = cv2.convertScaleAbs(laplace)
    gradMax = np.int(np.max(grad))
    # (2) 以灰度值的 99.5% 分位为阈值, 对边缘图像进行二值处理, 作为遮罩模板
    per995 = np.percentile(grad, q=99.5) # 99.5 分位的灰度值, [0, per995] 占比99.5%
    _, gradPer995 = cv2.threshold(np.uint8(grad), per995, 1, cv2.THRESH_BINARY) # 对边缘图像二值处理
    # (3) 计算基于遮罩模板的直方图分布,以排除无效背景像素的影响
    fp = np.uint8(img * gradPer995)
    histCV2 = cv2.calcHist([fp], [0], None, [256], [0, 256])
    histCV2[0] = 0 # fp 非零像素直方图
    # (4) OTSU 算法计算 fp 非零像素的最佳分割阈值
    # nonzeroPixels = np.count_nonzero(gradPer995) # 非零像素总数
    nonzeroPixels = sum(histCV2[1:]) # 非零像素总数
    totalGray = np.dot(histCV2[:,0], range(256)) # 内积, 总和灰度值
    mG = totalGray / nonzeroPixels # 平均灰度
    icv = np.zeros(256)
    numFt, sumFt = 0, 0
    for t in range(0, 256): # 遍历灰度值
    numFt += histCV2[t,0] # F(t) 像素数量
    sumFt += histCV2[t,0] * t # F(t) 灰度值总和
    pF = numFt / nonzeroPixels # F(t) 像素数占比
    mF = (sumFt/numFt) if numFt>0 else 0 # F(t) 平均灰度
    numBt = nonzeroPixels-numFt # B(t) 像素数量
    sumBt = totalGray - sumFt # B(t) 灰度值总和
    pB = numBt / nonzeroPixels # B(t) 像素数占比
    mB = (sumBt/numBt) if numBt>0 else 0 # B(t) 平均灰度
    icv[t] = pF * (mF-mG)**2 + pB * (mB-mG)**2 # OTSU 算法: 灰度 t 的类间方差
    maxIcv = max(icv) # ICV 的最大值
    maxIndex = np.argmax(icv) # 最大值的索引
    print(per995, nonzeroPixels, maxIcv, maxIndex)
    # 使用 fp 非零像素的最佳分割阈值,对原始图像进行固定阈值处理
    ret, imgBin = cv2.threshold(img, maxIndex, 255, cv2.THRESH_BINARY) # 以 maxIndex 作为最优阈值

什么是大津 (Otsu) 二值化?

  • OTSU 方法又称大津算法,使用最大化类间方差(intra-class variance)作为评价准则,基于对图像直方图的计算,可以给出类间最优分离的最优阈值
  • 任取一个灰度值 T,可以将图像分割为两个集合 F 和 B,集合 F、B 的像素数的占比分别为 pF、pB,集合 F、B 的灰度值均值分别为 mF、mB,图像灰度值为 m,定义类间方差

ICV=pr(mFm)2+pB(mBm)2\text{ICV}=\mathrm{pr}*\left(\mathrm{m_F}-\mathrm{m}\right)^2+\mathrm{p_B}*\left(\mathrm{m_B}-\mathrm{m}\right)^2

  • 使类间方差 ICV 最大化的灰度值 T 就是最优阈值,只要遍历所有的灰度值,就可以得到使 ICV 最大的最优阈值 T
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    img = cv.imread('noisy2.png',0)
    # global thresholding
    ret1,th1 = cv.threshold(img,127,255,cv.THRESH_BINARY)
    # Otsu's thresholding
    ret2,th2 = cv.threshold(img,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
    # Otsu's thresholding after Gaussian filtering
    blur = cv.GaussianBlur(img,(5,5),0)
    ret3,th3 = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU)
    # plot all the images and their histograms
    show_images([img, 0, th1])
    show_images([img, 0, th2])
    show_images([blur, 0, th3])

多阈值的大津 (Otsu) 二值化处理?

  • 大津 (Otsu) 二值化方法使用最大化类间方差(intra-class variance)作为评价准则,基于对图像直方图的计算,可以给出类间最优分离的最优阈值,OTSU 方法可以扩展到任意数量的阈值
  • 此时,通常使用双阈值对图像进行二值化,如果是更多阈值,需要采用聚类或启发式方法来获得分割阈值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    def doubleThreshold(img):
    histCV = cv2.calcHist([img], [0], None, [256], [0, 256]) # 灰度直方图
    grayScale = np.arange(0, 256, 1) # 灰度级 [0,255]
    totalPixels = img.shape[0] * img.shape[1] # 像素总数
    totalGray = np.dot(histCV[:,0], grayScale) # 内积, 总和灰度值
    mG = totalGray / totalPixels # 平均灰度,meanGray
    varG = sum(((i-mG)**2 * histCV[i,0]/totalPixels) for i in range(256))
    T1, T2, varMax = 1, 2, 0.0
    # minGary, maxGray = np.min(img), np.max(img) # 最小灰度,最大灰度
    for k1 in range(1, 254): # k1: [1,253], 1<=k1<k2<=254
    n1 = sum(histCV[:k1, 0]) # C1 像素数量
    s1 = sum((i * histCV[i, 0]) for i in range(k1))
    P1 = n1 / totalPixels # C1 像素数占比
    m1 = (s1 / n1) if n1 > 0 else 0 # C1 平均灰度
    for k2 in range(k1+1, 256): # k2: [2,254], k2>k1
    # n2 = sum(histCV[k1+1:k2,0]) # C2 像素数量
    # s2 = sum( (i * histCV[i,0]) for i in range(k1+1,k2) )
    # P2 = n2 / totalPixels # C2 像素数占比
    # m2 = (s2/n2) if n2>0 else 0 # C2 平均灰度
    n3 = sum(histCV[k2+1:,0]) # C3 像素数量
    s3 = sum((i*histCV[i,0]) for i in range(k2+1,256))
    P3 = n3 / totalPixels # C3 像素数占比
    m3 = (s3/n3) if n3>0 else 0 # C3 平均灰度
    P2 = 1.0 - P1 - P3 # C2 像素数占比
    m2 = (mG - P1*m1 - P3*m3)/P2 if P2>1e-6 else 0 # C2 平均灰度
    var = P1*(m1-mG)**2 + P2*(m2-mG)**2 + P3*(m3-mG)**2
    if var>varMax:
    T1, T2, varMax = k1, k2, var
    epsT = varMax / varG # 可分离测度
    print(totalPixels, mG, varG, varMax, epsT, T1, T2)
    return T1, T2, epsT
    img = cv2.imread("../images/Fig1043a.tif", flags=0)
    # img = cv2.imread("../images/Fig1045a.tif", flags=0)
    histCV = cv2.calcHist([img], [0], None, [256], [0, 256]) # 灰度直方图
    T1, T2, epsT = doubleThreshold(img)
    print("T1={}, T2={}, esp={:.4f}".format(T1, T2, epsT))
    binary = img.copy()
    binary[binary<T1] = 0
    binary[binary>T2] = 255
    ret, imgOtsu = cv2.threshold(img, 127, 255, cv2.THRESH_OTSU) # OTSU 阈值分割
    ret1, binary1 = cv2.threshold(img, T1, 255, cv2.THRESH_TOZERO) # 小于阈值置 0,大于阈值不变
    ret2, binary2 = cv2.threshold(img, T2, 255, cv2.THRESH_TOZERO)
    show_images([img,imgOtsu,binary1,binary2,binary3])
    plt.bar(range(256), histCV[:,0])

什么是自适应阈值处理?

  • 噪声和非均匀光照等因素对阈值处理的影响很大,例如光照复杂时 Otsu 算法等全局阈值分割方法的效果往往不太理想,需要使用可变阈值处理
  • 可变阈值是指对于图像中的每个像素点或像素块有不同的阈值,如果该像素点大于其对应的阈值则认为是前景
  • 局部阈值分割可以根据图像的局部特征进行处理,根据其邻域的性质计算阈值,标准差和均值是对比度和平均灰度的描述,在局部阈值处理中非常有效
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    img = cv2.imread("../images/Fig1050a.tif", flags=0)
    # img = cv2.imread("../images/Fig1043a.tif", flags=0)
    # OTSU 全局阈值处理
    histCV = cv2.calcHist([img], [0], None, [256], [0, 256]) # 灰度直方图
    ret, imgOtsu = cv2.threshold(img, 127, 255, cv2.THRESH_OTSU) # 阈值分割, thresh=T
    # 自适应局部阈值处理
    binaryMean = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 5, 3)
    binaryGauss = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 5, 3)
    # 自适应局部阈值处理
    ratio = 0.2
    imgBlur = cv2.boxFilter(img, -1, (5,5)) # 盒式滤波器,均值平滑
    localThresh = img - (1.0-ratio) * imgBlur
    binaryBox = np.ones_like(img) * 255 # 创建与 img 相同形状的白色图像
    binaryBox[localThresh<0] = 0
    show_images([img,imgOtsu,binaryMean,binaryGauss,binaryBox])
    plt.bar(range(256), histCV[:,0])

什么是基于移动平均的可变阈值处理?

  • 灰度遮蔽着点照明(如闪光灯)图像中十分常见。图中被斑点灰度模式遮蔽的手写文本图像,如果使用 OTSU 全局阈值处理,不能克服灰度变化的影响。在不均匀的光照场中,阈值分割的效果不好。使用移动平均的局部阈值,则能很好地进行处理
  • 移动平均法是线性的 Z 字形模式扫描整个图片,对每个像素产生一个阈值,然后进行阈值处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    def movingThreshold(img, n, b):
    img[1:-1:2, :] = np.fliplr(img[1:-1:2, :]) # 向量翻转
    f = img.flatten() # 展平为一维
    ret = np.cumsum(f)
    ret[n:] = ret[n:] - ret[:-n]
    m = ret / n # 移动平均值
    g = np.array(f>=b*m).astype(int) # 阈值判断,g=1 if f>=b*m
    g = g.reshape(img.shape) # 恢复为二维
    g[1:-1:2, :] = np.fliplr(g[1:-1:2, :]) # 交替翻转
    return g*255
    img1 = cv2.imread("../images/Fig1049a.tif", flags=0)
    img2 = cv2.imread("../images/Fig1050a.tif", flags=0)
    ret1, imgOtsu1 = cv2.threshold(img1, 127, 255, cv2.THRESH_OTSU) # OTSU 阈值分割
    ret2, imgOtsu2 = cv2.threshold(img2, 127, 255, cv2.THRESH_OTSU)
    imgMoveThres1 = movingThreshold(img1, 20, 0.5) # 移动平均阈值处理
    imgMoveThres2 = movingThreshold(img2, 20, 0.5) # n=20, b=0.5
    show_images([img1,imgOtsu1,imgMoveThres1,img2,imgOtsu2,imgMoveThres2])

如何根据掩码快速提取图片的子区域?

  • 使用 bitwise_and 快速提取某一张图的子区域
    1
    2
    3
    4
    5
    6
    7
    import cv2 as cv
    img = cv.imread('messi.jpeg')
    img2gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
    # 提取mask
    ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY)
    # mask区域就是要提取的掩码区域
    img1_bg = cv.bitwise_and(img,img,mask = mask)

如何融合两张图片?

  • 首先截取待操作的 ROI,然后使用 bitwise_and 在准备底图上挖空,去掉顶图未融合部分,然后使用 add 将处理后的两部分相加即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    img1 = cv2.imread("../images/imgLena.tif")  # 读取彩色图像(BGR)
    img2 = cv2.imread("../images/logoCV.png") # 读取 CV Logo
    x, y = (0, 10) # 图像叠加位置
    W1, H1 = img1.shape[1::-1]
    W2, H2 = img2.shape[1::-1]
    if (x + W2) > W1: x = W1 - W2
    if (y + H2) > H1: y = H1 - H2
    print(W1,H1,W2,H2,x,y)
    imgROI = img1[y:y+H2, x:x+W2] # 从背景图像裁剪出叠加区域图像
    img2Gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # img2: 转换为灰度图像
    ret, mask = cv2.threshold(img2Gray, 175, 255, cv2.THRESH_BINARY) # 转换为二值图像,生成遮罩,LOGO 区域黑色遮盖
    maskInv = cv2.bitwise_not(mask) # 按位非(黑白转置),生成逆遮罩,LOGO 区域白色开窗,LOGO 以外区域黑色
    # mask 黑色遮盖区域输出为黑色,mask 白色开窗区域与运算(原图像素不变)
    img1Bg = cv2.bitwise_and(imgROI, imgROI, mask=mask) # 生成背景,imgROI 的遮罩区域输出黑色
    img2Fg = cv2.bitwise_and(img2, img2, mask=maskInv) # 生成前景,LOGO 的逆遮罩区域输出黑色
    # img1Bg = cv2.bitwise_or(imgROI, imgROI, mask=mask) # 生成背景,与 cv2.bitwise_and 效果相同
    # img2Fg = cv2.bitwise_or(img2, img2, mask=maskInv) # 生成前景,与 cv2.bitwise_and 效果相同
    # img1Bg = cv2.add(imgROI, np.zeros(np.shape(img2), dtype=np.uint8), mask=mask) # 生成背景,与 cv2.bitwise 效果相同
    # img2Fg = cv2.add(img2, np.zeros(np.shape(img2), dtype=np.uint8), mask=maskInv) # 生成背景,与 cv2.bitwise 效果相同
    imgROIAdd = cv2.add(img1Bg, img2Fg) # 前景与背景合成,得到裁剪部分的叠加图像
    imgAdd = img1.copy()
    imgAdd[y:y+H2, x:x+W2] = imgROIAdd # 用叠加图像替换背景图像中的叠加位置,得到叠加 Logo 合成图像

参考:

  1. 【OpenCV 例程 200 篇】21. 图像的叠加_opencv 图像叠加_youcans_的博客 - CSDN 博客
  2. 【youcans 的 OpenCV 例程 200 篇】158. 阈值处理之固定阈值法_opencv 设置图像固定位置阈值_youcans_的博客 - CSDN 博客
  3. 【youcans 的 OpenCV 例程 200 篇】159. 图像分割之全局阈值处理_youcans_的博客 - CSDN 博客
  4. 【youcans 的 OpenCV 例程 200 篇】160. 图像处理之 OTSU 方法_youcans_的博客 - CSDN 博客
  5. 【youcans 的 OpenCV 例程 200 篇】163. 基于边缘信息改进全局阈值处理_youcans_的博客 - CSDN 博客
  6. 【youcans 的 OpenCV 例程 200 篇】164. 使用 Laplace 边缘信息改进全局阈值处理_youcans_的博客 - CSDN 博客
  7. 【youcans 的 OpenCV 例程 200 篇】165. 多阈值 OTSU 处理方法_多阈值 otsu matlab_youcans_的博客 - CSDN 博客
  8. 【youcans 的 OpenCV 例程 200 篇】166. 自适应阈值处理_自适应阈值处理题目_youcans_的博客 - CSDN 博客
  9. 【youcans 的 OpenCV 例程 200 篇】167. 基于移动平均的可变阈值处理_基于移动平均的可变阈值处理的过程_youcans_的博客 - CSDN 博客
  10. 【OpenCV 例程 200 篇】37. 图像的灰度化处理和二值化处理(cv2.threshold)_1. 对获取图像进行合适大小变换后,双边滤波、灰度化、二值化并得到轮廓 (可以结合 hs_youcans_的博客 - CSDN 博客