OpenCV系列教程三:形态学、图像轮廓、直方图

文章目录

    • 一、形态学
      • 1.1 阈值处理
        • 1.1.1 全局阈值处理
        • 1.1.2 全局阈值处理之Otsu's 阈值法
        • 1.1.3 自适应阈值处理
      • 1.2 腐蚀与膨胀
        • 1.2.1 腐蚀操作
        • 1.2.2 创建形态学卷积核
        • 1.2.3 膨胀操作
      • 1.3 开运算和闭运算
      • 1.4 形态学梯度
      • 1.5 顶帽操作(tophat)
      • 1.6 黑帽操作(Black Hat)
    • 二、图像轮廓
      • 2.1 轮廓的查找与绘制
      • 2.2 计算轮廓面积和周长
      • 2.3 多边形近似
      • 2.4 凸包
      • 2.5 外接矩形和外接圆
    • 三、图像金字塔
      • 3.1 高斯金字塔
      • 3.2 拉普拉斯金字塔
    • 四、图像直方图
      • 4.1 图像直方图基本概念
      • 4.2 统计直方图
        • 4.2.1 直接统计
        • 4.2.2 使用OpenCV统计图像直方图
        • 4.2.3 使用掩膜
      • 4.3 直方图均衡化
      • 4.4 自适应直方图均衡化 (CLAHE)
        • 4.4.1 实现原理
        • 4.4.2 代码实现

  • 《OpenCV系列课程一:图像处理入门(读写、拆分合并、变换、注释)、视频处理》
  • 《OpenCV系列教程二:基本图像增强(数值运算)、滤波器(去噪、边缘检测)》
  • 《OpenCV系列教程三:形态学、图像轮廓、直方图》

一、形态学

  形态学(Morphology)是指一系列用于处理图像形状和结构的算法,其基本思想是利用一种特殊的结构元(本质上就是卷积核)来测量或提取输入图像中相应的形状或特征。形态学操作通常用于预处理、图像分割、特征提取、图像滤波和图像增强等任务。形态学的基本操作包括:

  1. 腐蚀(Erosion):它将图像中的前景物体缩小,这种操作可以去除图像中的小物体,分离相互接触的物体,以及平滑物体的边界。
  2. 膨胀(Dilation):与腐蚀相反,膨胀操作将图像中的前景物体增大,可以用来填补物体中的小洞,连接相邻的物体,或者增加物体的面积。
  3. 开运算(Opening):先腐蚀后膨胀的过程,用于去除小的物体,平滑较大物体的边界,而不改变其面积。
  4. 闭运算(Closing):先膨胀后腐蚀的过程,用于填充物体内的小洞,连接邻近的物体,而不明显改变物体的边界。
  5. 形态学梯度(Morphological Gradient):膨胀图与腐蚀图之差,可以突出物体的边缘。
  6. 顶帽(Top Hat)和黑帽(Black Hat):这两种操作分别是原图与开运算结果之差(顶帽)和闭运算结果与原图之差(黑帽),用于突出比周围区域亮或暗的区域。

1.1 阈值处理

  阈值处理的主要意义是将图像中的某些区域分离出来,通常是为了突出前景(如物体)和背景(如场景)。通过二值化,可以将灰度图像转化为黑白图像(即二值图像),使后续的图像分析、边缘检测、目标识别等任务更加简便和高效。

  例如,图像由暗色背景上的亮目标组成,这时可以通过设定适当的阈值 T,将图像的像素划分为两类:灰度值大于 T 的像素集是目标,小于 T 的像素集是背景。当 T 是应用于整幅图像的常数,称为全局阈值处理;当 T 对于整幅图像发生变化时,称为可变阈值处理。有时,对应于图像中任一点的 T 值取决于该点的邻域的限制,称为局部阈值处理

1.1.1 全局阈值处理

  全局阈值处理(Global Thresholding)是对图像的所有像素点应用同一个阈值。如果像素值高于阈值,则将其设为一个值(通常是白色),否则设为另一个值(通常是黑色)。OpenCV 提供了函数 cv2.threshold 函数来实现此功能,其语法为:

retval, dst = cv2.threshold( src, thresh, maxval, type[, dst] )
  • retval:阈值,浮点型

  • dst:阈值处理后的图像(numpy数组),与src具有相同大小和类型以及通道数。

  • src:输入数组,最好是灰度图。

  • thresh:阈值。

  • maxval:用于THRESH_BINARYTHRESH_MINARY_INV阈值类型的最大值,一般取 255。

  • type:阈值类型(详见阈值类型)。

Type 类型描述
cv2.THRESH_BINARY(输出二值图像)超过阈值的像素值设为maxValue,否则设为0
cv2.THRESH_BINARY_INV(输出二值图像)超过阈值的像素值设为0,否则设为maxValue
cv2.THRESH_TRUNC超过阈值时置为阈值 thresh,否则不变
cv2.THRESH_TOZERO超过阈值的像素值保持不变,否则置0
cv2.THRESH_TOZERO_INV超过阈值的像素值设为0,否则不变
cv2.THRESH_OTSU使用 OTSU 算法选择阈值,需要与其他类型(如cv2.THRESH_BINARY)结合使用。
cv2.THRESH_TRIANGLE使用三角算法自动计算阈值,需要与其他类型结合使用。

  特殊值THRESH_OTSUTHRESH_TRIANGLE可以与上述值之一组合。在这些情况下,函数使用Otsu或Triangle算法确定最佳阈值,并使用它代替指定的阈值。Otsu和Triangle方法仅用于8位单通道图像。

  当图像中存在高斯噪声时,通常难以通过全局阈值将图像的边界完全分开。如果图像的边界是在局部对比下出现的,不同位置的阈值也不同,使用全局阈值的效果将会很差。如果图像的直方图存在明显边界,容易找到图像的分割阈值;但如果图像直方图分界不明显,则很难找到合适的阈值,甚至可能无法找到固定的阈值有效地分割图像。

import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inlineimg_read = cv2.imread("building-windows.jpg", 0) # 灰度图
retval, img_thresh = cv2.threshold(img_read, 100, 255, cv2.THRESH_BINARY)# Show the images
plt.figure(figsize=[18,5])
plt.subplot(121); plt.imshow(img_read, cmap="gray");         plt.title("Original");
plt.subplot(122); plt.imshow(img_thresh, cmap="gray");       plt.title("Thresholded");print(retval,img_thresh.shape)
(572, 800) 100.0 (572, 800)

在这里插入图片描述

1.1.2 全局阈值处理之Otsu’s 阈值法

  Otsu’s 方法是一种自动选择全局阈值的算法,通过最大化类间方差自动确定最优阈值。

  当图像中的目标和背景的灰度分布较为明显时,可以对整个图像使用固定阈值进行全局阈值处理。 为了获得适当的全局阈值,可以基于灰度直方图进行迭代计算(详见【youcans 的 OpenCV 例程200篇】159. 图像分割之全局阈值处理),另一种改进算法是OTSU 方法(又称大津算法)。使用最大化类间方差(intra-class variance)作为评价准则,基于对图像直方图的计算,可以给出类间最优分离的最优阈值。

  任取一个灰度值 T,可以将图像分割为两个集合 F 和 B,集合 F、B 的像素数的占比分别为 pF、pB,集合 F、B 的灰度值均值分别为 mF、mB,图像灰度值为 m,定义类间方差为:
I C V = p F ∗ ( m F − m ) 2 + p B ∗ ( m B − m ) 2 ICV = p_F * (m_F - m)^2 + p_B * (m_B - m)^2 ICV=pF(mFm)2+pB(mBm)2
  使类间方差 ICV 最大化的灰度值 T 就是最优阈值。因此,只要遍历所有的灰度值,就可以得到使 ICV 最大的最优阈值 T。

  OpenCV 提供了函数 cv.threshold 可以对图像进行阈值处理,将参数 type 设为 cv.THRESH_OTSU,就可以使用使用 OTSU 算法进行最优阈值分割。

img = cv2.imread("../images/Fig1039a.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]  # 像素总数
totalGray = np.dot(histCV[:,0], grayScale)  # 内积, 总和灰度值
T = round(totalGray/totalPixels)  # 平均灰度
while True:numC1, sumC1 = 0, 0for i in range(T): # 计算 C1: (0,T) 平均灰度numC1 += histCV[i,0]  # C1 像素数量sumC1 += histCV[i,0] * i  # C1 灰度值总和numC2, sumC2 = (totalPixels-numC1), (totalGray-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==Tnewbreakelse:T = Tnew# 阈值处理
ret1, imgBin = cv2.threshold(img, T, 255, cv2.THRESH_BINARY)  # 阈值分割, thresh=T
ret2, imgOtsu = cv2.threshold(img, T, 255, cv2.THRESH_OTSU)  # 阈值分割, thresh=T
print(ret1, ret2)plt.figure(figsize=(7,7))
plt.subplot(221), plt.axis('off'), plt.title("Origin"), plt.imshow(img, 'gray')
plt.subplot(222, yticks=[]), plt.title("Gray Hist")  # 直方图
histNP, bins = np.histogram(img.flatten(), bins=255, range=[0, 255], density=True)
plt.bar(bins[:-1], histNP[:])
plt.subplot(223), plt.title("global binary(T={})".format(T)), plt.axis('off')
plt.imshow(imgBin, 'gray')
plt.subplot(224), plt.title("OTSU binary(T={})".format(round(ret2))), plt.axis('off')
plt.imshow(imgOtsu, 'gray')
plt.tight_layout()
plt.show()

在这里插入图片描述
  全局阈值处理还有一些其它改进方法,比如处理前先对图像进行平滑、基于边缘信息改进全局阈值处理等等。

1.1.3 自适应阈值处理

  噪声和非均匀光照等因素对阈值处理的影响很大,例如光照复杂时 全局阈值分割方法的效果往往不太理想,需要使用可变阈值处理。

  自适应阈值处理(Adaptive Thresholding)对图像中的每个点,根据其邻域计算其对应的阈值,非常适合处理光照条件不均匀的图像。cv2.adaptiveThreshold函数语法为:

adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst]) -> dst
  • maxValue:为满足条件的像素指定的非零值,详见阈值类型说明。
  • adaptiveMethod:要使用的自适应阈值算法,详见AdaptiveThresholdTypes。
    • cv.ADAPTIVE_THRESH_MEAN_C:阈值是邻域的均值;
    • cv.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域的高斯核加权平均值;
  • thresholdType:阈值类型,只有两种
    • cv2.THRESH_BINARY:大于阈值时置 maxValue,否则置 0
    • cv2.THRESH_BINARY_INV:大于阈值时置 0,否则置 maxValue
  • blockSize:用于计算像素阈值的像素邻域的尺寸,例如3、5、7。
  • C: 偏移量,从平均值或加权平均值中减去该常数。

  假设您想构建一个可以读取(解码)乐谱的应用程序,这类似于文本文档的光学字符识别(OCR)。处理管道的第一步是隔离文档图像中的重要信息(将其与背景分离)。这项任务可以通过阈值技术来完成。

# 示例:乐谱阅读器img_read = cv2.imread("Piano_Sheet_Music.png", 0)# 全局阈值1
retval, img_thresh_gbl_1 = cv2.threshold(img_read,50, 255, cv2.THRESH_BINARY)# 全局阈值2
retval, img_thresh_gbl_2 = cv2.threshold(img_read,130, 255, cv2.THRESH_BINARY)# 自适应阈值
img_thresh_adp = cv2.adaptiveThreshold(img_read, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 7)# Show the images
plt.figure(figsize=[18,15])
plt.subplot(221); plt.imshow(img_read,        cmap="gray");  plt.title("Original");
plt.subplot(222); plt.imshow(img_thresh_gbl_1,cmap="gray");  plt.title("Thresholded (global: 50)");
plt.subplot(223); plt.imshow(img_thresh_gbl_2,cmap="gray");  plt.title("Thresholded (global: 130)");
plt.subplot(224); plt.imshow(img_thresh_adp,  cmap="gray");  plt.title("Thresholded (adaptive)");

在这里插入图片描述

1.2 腐蚀与膨胀

  • 腐蚀用于消除小的白色噪声,减小前景区域。
  • 膨胀用于填补物体中的空洞,增加前景区域。
1.2.1 腐蚀操作

  腐蚀是一种将前景(白色区域)缩小的操作,其原理是滑动窗口中的结构元素(kernel),如果卷积区域内所有被覆盖的像素都是前景像素(白色),中心像素保留为前景(白色),否则变为背景(黑色)。这使得前景物体逐渐缩小,细小的噪声点会被消除。

在这里插入图片描述
  如上图所示,腐蚀操作的卷积核设为5×5,只有图中虚线方框内的像素,被卷积时区域内都是白色像素,所以卷积后也是白色(设为255)。其它区域都将被置为黑色(设为0)。

腐蚀操作使用erode函数,其语法为:

erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) -> dst
  • src: 输入图像,一般是二值图像。
  • kernel: 卷积核(即结构元素)。
    • 核越大,腐蚀的效果越强。
    • 不同的形状会影响腐蚀的方向性和图像特征的保留。矩形核适合均匀腐蚀,而椭圆核能更好地保留圆滑的边缘。
  • iterations: 腐蚀操作的迭代次数,默认为1。次数越多,腐蚀效果越明显。

  下面是一个简单的示例,处理之后,白色的字体像是被橡皮擦去了一圈,变小了。

img = cv2.imread('msb.png')# 定义核
kernel = np.ones((5, 5), np.uint8)
dst = cv2.erode(img, kernel, iterations=1)plt.figure(figsize=[18,15])
plt.subplot(121); plt.imshow(img,cmap="gray");  plt.title("img");
plt.subplot(122); plt.imshow(dst,cmap="gray");  plt.title("dst");

在这里插入图片描述

1.2.2 创建形态学卷积核

  cv2.getStructuringElement 是 OpenCV 中用于生成 结构元素(也叫形态学核)的函数,常用于形态学操作(如腐蚀、膨胀、开运算、闭运算等)。结构元素决定了形态学操作(卷积核)的形状和尺寸。

getStructuringElement(shape, ksize[, anchor]) -> retval
  • shape:指定结构元素(卷积核)的形状,常见的形状有:

    • cv2.MORPH_RECT:矩形
    • cv2.MORPH_ELLIPSE:椭圆形
    • cv2.MORPH_CROSS:十字形
  • ksize:结构元素的大小,通常以 (width, height) 的形式给出。例如 (5, 5) 表示 5x5 的结构元素。

  • anchor(可选):结构元素的锚点,表示结构元素的参考中心点。默认是结构元素的中心 (ksize[0]//2, ksize[1]//2),但也可以指定其他锚点。

kernel_RECT = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
kernel_ELLIPSE=cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
kernel_CROSS=cv2.getStructuringElement(cv2.MORPH_CROSS, (5, 5))kernel_RECT,kernel_ELLIPSE,kernel_CROSS
 array([[1, 1, 1, 1, 1],[1, 1, 1, 1, 1],[1, 1, 1, 1, 1],[1, 1, 1, 1, 1],[1, 1, 1, 1, 1]], dtype=uint8
 array([[0, 0, 1, 0, 0],[1, 1, 1, 1, 1],[1, 1, 1, 1, 1],[1, 1, 1, 1, 1],[0, 0, 1, 0, 0]], dtype=uint8)
 array([[0, 0, 1, 0, 0],[0, 0, 1, 0, 0],[1, 1, 1, 1, 1],[0, 0, 1, 0, 0],[0, 0, 1, 0, 0]], dtype=uint8)
1.2.3 膨胀操作

  膨胀(Dilation)是一种将前景(白色区域)扩大的操作。膨胀的原理与腐蚀相反,只要滑动窗口中的结构元素覆盖下有一个像素是前景像素(白色),中心像素就保留为前景。这可以使前景区域扩大,填补物体中的小孔,并连接分离的小物体,其函数语法为:

dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) -> dst
  • src:输入图像,一般是二值图像。
  • kernel:卷积核,定义操作的结构元素。
  • iterations: 膨胀操作的迭代次数,默认为1。
img = cv2.imread('./j.png')
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
dst = cv2.dilate(img, kernel, iterations=1)plt.figure(figsize=[8,4])
plt.subplot(121); plt.imshow(img,cmap="gray");  plt.title("img");
plt.subplot(122); plt.imshow(dst,cmap="gray");  plt.title("dst");

在这里插入图片描述

1.3 开运算和闭运算

  cv2.morphologyEx 是 OpenCV 中一个用于执行更复杂的形态学操作的函数,它基于基础的腐蚀和膨胀操作,并提供了一系列的高级形态学变换。通过这个函数,我们可以实现诸如开运算、闭运算、形态学梯度、顶帽操作和黑帽操作等操作。

操作类型操作顺序用途
开运算先腐蚀,后膨胀消除小的噪声点,保留前景物体的整体形状
闭运算先膨胀,后腐蚀填补前景物体中的小孔,连接分散的小区域
梯度膨胀与腐蚀的差提取物体的边缘,提取图像中的轮廓
顶帽输入图像 - 开运算提取前景外的亮区域,常用于不均匀光照的图像处理中
黑帽输入图像 - 闭运算提取前景中的暗区域,适合分析背景中的暗部特征

cv2.morphologyEx 语法为:

morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) -> dst
  • src:输入图像,通常是二值图像(黑白图像)。
  • op:表示要执行的形态学操作。常见的操作包括:
    • cv2.MORPH_OPEN:开运算。先腐蚀,后膨胀 ,在消除噪声的同时保持前景部分不变。
    • cv2.MORPH_CLOSE:闭运算。先膨胀扩大前景,再腐蚀,擦掉前景中的黑色部分。
    • cv2.MORPH_GRADIENT:形态学梯度;
    • cv2.MORPH_TOPHAT:顶帽操作;
    • cv2.MORPH_BLACKHAT:黑帽操作;
  • kernel:结构元素(卷积核),通常由 cv2.getStructuringElement() 生成(包括形状和大小)。
  • iterations:迭代次数,默认为 1。
# 开运算,先腐蚀后膨胀,前景保持不变img = cv2.imread('./dotj.png')
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))# 直接调用cv2.morphologyEx更方便
# dst = cv2.erode(img, kernel, iterations=2)
# dst = cv2.dilate(dst, kernel, iterations=2)
dst = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel, iterations=2)
plt.figure(figsize=[8,4])
plt.subplot(121); plt.imshow(img,cmap="gray");  plt.title("img");
plt.subplot(122); plt.imshow(dst,cmap="gray");  plt.title("open");

在这里插入图片描述

# 闭运算img = cv2.imread('dotinj.png')
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
dst = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel, iterations=2)plt.figure(figsize=[8,4])
plt.subplot(121); plt.imshow(img,cmap="gray");  plt.title("img");
plt.subplot(122); plt.imshow(dst,cmap="gray");  plt.title("close");

在这里插入图片描述

1.4 形态学梯度

  形态学梯度 = 原图 - 腐蚀,也就是得到被腐蚀掉的部分。这会突出显示物体的边缘,生成的是前景物体的轮廓。

img = cv2.imread('./j.png')
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
dst = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel, iterations=1)plt.figure(figsize=[8,4])
plt.subplot(121); plt.imshow(img,cmap="gray");  plt.title("img");
plt.subplot(122); plt.imshow(dst,cmap="gray");  plt.title("GRADIENT");

在这里插入图片描述

1.5 顶帽操作(tophat)

  顶帽 = 原图 - 开运算。开运算的效果是去除图形外的噪点,,原图 - 开运算就得到了图形外的噪点,可以用于突出显示图像中的亮区域。

import cv2 
import numpy as npimg = cv2.imread('./dotj.png')
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
dst = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel, iterations=2)plt.figure(figsize=[8,4])
plt.subplot(121); plt.imshow(img,cmap="gray");  plt.title("img");
plt.subplot(122); plt.imshow(dst,cmap="gray");  plt.title("TOPHAT");

在这里插入图片描述

1.6 黑帽操作(Black Hat)

  黑帽 = 原图 - 闭运算。闭运算可以将图形内部的噪点去掉,那么原图 - 闭运算的结果就是图形内部的噪点,用于突出显示图像中的暗区域。

img = cv2.imread('./dotinj.png')
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
dst = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel, iterations=2)plt.figure(figsize=[8,4])
plt.subplot(121); plt.imshow(img,cmap="gray");  plt.title("img");
plt.subplot(122); plt.imshow(dst,cmap="gray");  plt.title("BLACKHAT");

在这里插入图片描述

二、图像轮廓

2.1 轮廓的查找与绘制

  轮廓可以看作是具有相同强度或颜色的所有连续点的边界,通常在处理二值图像时使用。通过轮廓,图像中的物体形状和结构可以被有效提取,这在对象检测、识别和分析中非常有用。

cv2.findContours 是 OpenCV 中用于检测图像中轮廓的函数,其语法为:

findContours(image, mode, method[, contours[, hierarchy[, offset]]]) -> contours, hierarchy
  • image:输入图像,通常是二值图像(黑白图像)。可以使用 cv2.thresholdcv2.Canny 将图像转换为二值图像。

  • mode:轮廓检索模式,决定如何检索轮廓。常见的模式有:

    • cv2.RETR_EXTERNAL:只检测最外层轮廓。
    • cv2.RETR_TREE:按照树型检测所有轮廓, 从里到外,从右到左
      在这里插入图片描述
  • method:轮廓近似方法,决定如何处理轮廓点。常见的方法有:

    • cv2.CHAIN_APPROX_NONE:存储所有轮廓点,但这通常是没必要的,会产生很多冗余。
    • cv2.CHAIN_APPROX_SIMPLE:常用,只保留轮廓的关键拐点。

函数最终返回两个值:

  • contours:轮廓点列表。列表中每个元素是一个 ndarray 数组,表示一个轮廓(轮廓上所有点的坐标)。
  • hierarchy:层级信息。对于每个轮廓,存储其父轮廓、子轮廓、下一轮廓和前一轮廓的索引。

  轮廓查找完之后,返回的只是轮廓点的坐标信息。我们可以使用cv2.drawContours函数,将轮廓绘制出来,其语法为:

drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]]) -> image
  • image:将要绘制轮廓的图像,会被直接修改,可以考虑拷贝一份来绘制。
  • contours:轮廓列表,每个轮廓都是一个numpy数组,表示轮廓上的点。
  • contourIdx: 轮廓的索引,如果设置为负数,所有的轮廓都会被绘制。
  • color: 轮廓线的颜色,用(B, G, R)元组表示。
  • thickness: 轮廓线的厚度。如果为负数,轮廓内部会被填充指定的颜色。
  • lineType:轮廓线类型,默认是cv2.LINE_8。其他选项包括cv2.LINE_4cv2.LINE_AA等。
  • hierarchy: 轮廓的层次结构信息,只有在绘制轮廓的子集时才需要。
  • maxLevel: 绘制轮廓的最大级别。如果为0,只绘制指定的轮廓;如果为1,绘制轮廓及其子轮廓;以此类推。
  • offset: 轮廓的偏移量,所有的轮廓都会按照这个偏移量进行移动。
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline# 原图是一个3通道彩色图,但显示出来是黑白图。
img = cv2.imread('./contours1.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 应用二值化处理
thresh, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY) contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)# 绘制轮廓会直接修改原图,如果想保持原图不变, 建议copy一份
img_copy = img.copy()
# -1表示绘制所有轮廓,2为轮廓线厚度
cv2.drawContours(img_copy, contours, -1, (0, 0, 255), 2)plt.figure(figsize=[8,4])
plt.subplot(121); plt.imshow(img[:,:,::-1]);  plt.title("img");
plt.subplot(122); plt.imshow(img_copy[:,:,::-1]);  plt.title("img_copy");

在这里插入图片描述

2.2 计算轮廓面积和周长

  轮廓面积是指每个轮廓中所有的像素点围成区域的面积,单位为像素。使用 cv2.contourArea() 函数可以计算轮廓的面积,其语法为:

contourArea(contour[, oriented]) -> retval

  使用 cv2.arcLength() 函数可以计算轮廓的周长,其语法为:

arcLength(curve, closed) -> retval
  • curve:轮廓,一般是用findContours 函数返回的轮廓列表中的一个轮廓
  • closed:布尔值,如果为 True,表示轮廓是封闭的,计算周长;如果为 False,表示轮廓是开放的,计算曲线长度。

轮廓面积和周长有多种应用:

  1. 物体大小分析:通过计算面积,可以比较不同物体的大小。例如,机器人视觉可以通过面积判断不同物体的大小,从而做出选择和处理;
  2. 形状特征提取:结合面积和周长可以分析物体形状。例如,通过周长和面积的比率,可以判断轮廓是接近圆形、方形还是其他形状,以便检测图像中的指定形状的物体;
  3. 形状筛选:在特定场景中,可能需要过滤掉面积或周长过小或过大的轮廓。例如,在车牌识别中,可以通过设定面积阈值只保留符合条件的轮廓;
  4. 物体检测与分类:在图像中通过轮廓的面积来分类不同类型的物体。例如,根据物体的大小将它们分为大、中、小三类。
  5. 过滤噪声:在物体检测任务中,可能会检测到一些非常小的噪声点,可以通过面积筛选将它们过滤掉。

下面是车牌识别中,轮廓面积的简单应用示例:

import cv2
import numpy as np# 读取图像并转换为灰度图像
image = cv2.imread('car.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 应用高斯模糊,减少噪声
blurred = cv2.GaussianBlur(gray, (5, 5), 0)# 使用Canny边缘检测
edges = cv2.Canny(blurred, 50, 150)# 进行轮廓检测,返回轮廓列表及其索引
contours, _ = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)# 设定车牌的面积阈值
min_area = 1000    # 车牌的最小面积
max_area = 15000   # 车牌的最大面积# 遍历所有轮廓,筛选符合面积条件的轮廓
for contour in contours:area = cv2.contourArea(contour)# 过滤掉不在面积范围内的轮廓if min_area < area < max_area:# 在图像上绘制轮廓cv2.drawContours(image, [contour], -1, (0, 255, 0), 2)# 计算轮廓的边界框(矩形)x, y, w, h = cv2.boundingRect(contour)# 提取轮廓对应的区域并显示plate_region = image[y:y+h, x:x+w]cv2.imshow("Plate Region", plate_region)# 显示最终筛选后的图像
cv2.imshow("Filtered Contours", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

2.3 多边形近似

  findContours找到的轮廓比较精细,有时候我们只想得到一个大致的轮廓。cv2.approxPolyDP是 OpenCV 中用于轮廓近似的算法,可以对找出的轮廓进行多边形近似,来简化轮廓。

  cv2.approxPolyDP的实现是基于Douglas-Peucker 算法,其原理如下(详见《DP算法——道格拉斯-普克 Douglas-Peuker》):

  1. 初始设定:选择轮廓的两个端点作为多边形的起点和终点。
  2. 寻找最大距离点:在轮廓中找到离这条线段距离最远的点,如果该距离大于设定的阈值 epsilon,则保留该点。
  3. 递归处理:将轮廓分成两个子段,分别递归执行该过程,直到所有剩余点的距离小于 epsilon,最终形成近似的多边形。

在这里插入图片描述

approxPolyDP(curve, epsilon, closed[, approxCurve]) -> approxCurve
  • curve:要简化的轮廓
  • epsilon :DP算法使用的阈值,阈值越大精度越低,保留的轮廓点数越少
  • closed:布尔值,指示轮廓是否封闭。如果为 True,输出的近似轮廓是封闭的。

2.4 凸包

  逼近多边形是轮廓的高度近似,但是有时候,我们希望使用一个多边形的凸包来进一步简化它。

  凸包是包含给定点集的最小凸多边形。换句话说,它是能够包围所有给定点的最小凸形状。凸包的每一处都是凸的,即在凸包内连接任意两点的直线都在凸包的内部。

  cv2.convexHull是OpenCV库中用于计算凸包(convex hull)的函数,其语法是:

convexHull(points[, hull[, clockwise[, returnPoints]]]) -> hull
  • points:要简化的轮廓
  • colckwise:方向标志,默认为False。如果为True,输出的凸包为顺时针方向。
  • returnPoints:默认为True,表示返回凸包顶点的坐标,否则只返回凸包顶点的索引。

下面进行多边形近似和凸包的演示:

import cv2
import numpy as npimg = cv2.imread('./hand.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_,binary= cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)# 查找轮廓并画出,contours[0]是手的轮廓
contours, _ = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
img_contours = img.copy()
cv2.drawContours(img_contours, contours, 0, (0, 0, 255), 2)# 进行多边形逼近, 返回的是多边形上一系列的点, 即多边形逼近之后的轮廓
# 凸包和多边形都可以使用drawContours函数绘制,只是其接受的是轮廓列表格式
approx = cv2.approxPolyDP(contours[0], 20, True)
img_approx=img.copy()
cv2.drawContours(img_approx, [approx], 0, (0, 255, 0), 2)# 计算凸包
hull = cv2.convexHull(contours[0])
img_hull=img.copy()
cv2.drawContours(img_hull, [hull], 0, (255, 0, 0), 2)plt.figure(figsize=[16,8])
plt.subplot(141); plt.imshow(img[:,:,::-1]);  plt.title("img");
plt.subplot(142); plt.imshow(img_contours[:,:,::-1]);  plt.title("img_contours");
plt.subplot(143); plt.imshow(img_approx[:,:,::-1]);  plt.title("img_approx");
plt.subplot(144); plt.imshow(img_hull[:,:,::-1]);  plt.title("img_hull");

在这里插入图片描述

2.5 外接矩形和外接圆

关于轮廓还有一些其它的操作,比如最小外接矩阵、最大外接矩阵和最小外接圆。

cv2.minAreaRect(points) -> retval
  • points:轮廓
  • 返回一个元组 (center(x, y), (width, height), angle),表示最小外接矩形的 中心点坐标,高宽,以及矩形相对于水平轴的旋转角度。

  cv2.minAreaRect返回的结果是外接矩形的中心点坐标、高宽以及旋转角度,可以使用opencv提供的cv2.boxPoints函数,自动计算出矩形的四个角点坐标,也就得到了轮廓数据。然后就可以使用cv2.drawContours将其标记出来。

cv2.boundingRect(array) -> retval
  • array:轮廓
  • 返回一个元组(center(x, y), (width, height))。最大外接矩形一定是水平的,所以没有旋转角度,所以可以直接用画矩形的函数在图像上cv2.rectangle画出来。
minEnclosingCircle(points) -> center, radius

下面进行简单的演示:

img = cv2.imread('./hello.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_,binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)
contours,_= cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)# 最外面的轮廓是整个图像, contours[1]才是Hello语的轮廓
# rect是一个元组,包括(x, y), (w, h), angle
rect = cv2.minAreaRect(contours[1])
# 快速把rect转化为轮廓数据,得到的结果是浮点类型,要转为整型
box = cv2.boxPoints(rect)
box = np.round(box).astype('int64')# 绘制最小外接矩形
img1=img.copy()
cv2.drawContours(img1, [box], 0, (255, 0, 0), thickness=2)# 绘制最大外接矩形
x,y, w, h = cv2.boundingRect(contours[1])
img2=img.copy()
cv2.rectangle(img2, (x, y), (x + w, y + h), (0, 255, 0), thickness=2)# 绘制最小外接圆,返回的也是浮点类型
center, radius=cv2.minEnclosingCircle(contours[1])
center, radius= np.round(center).astype('int64'),np.round(radius).astype('int64')
img3=img.copy()
cv2.circle(img3,center,radius,(0, 0, 255),thickness=2) plt.figure(figsize=[16,8])
plt.subplot(141); plt.imshow(img[:,:,::-1]);  plt.title("img");
plt.subplot(142); plt.imshow(img1[:,:,::-1]);  plt.title("minAreaRect");
plt.subplot(143); plt.imshow(img2[:,:,::-1]);  plt.title("boundingRect");
plt.subplot(144); plt.imshow(img3[:,:,::-1]);  plt.title("minEnclosingCircle");

在这里插入图片描述

三、图像金字塔

  图像金字塔是图像处理中的一种常用技术,它通过对原始图像进行一系列的降采样操作来创建一组图像。在OpenCV中,图像金字塔有两种类型:高斯金字塔和拉普拉斯金字塔。
在这里插入图片描述
图像金字塔在多种图像处理任务中有应用,包括:

  • 图像缩放:快速放大或缩小图像。
  • 图像融合:将不同分辨率的图像融合在一起。
  • 图像分割:在不同的分辨率层次上分析图像。
  • 多尺度目标检测:在不同尺度上检测目标。

3.1 高斯金字塔

  高斯金字塔 (Gaussian Pyramid)是通过连续应用高斯模糊和降采样来构建的。每一层的图像都是上一层的图像经过高斯模糊后,删除其偶数行和列得到的。这样,金字塔的每一层都比上一层小,分辨率也低。

  在构建高斯金字塔时,通常使用的是5x5的高斯卷积核来进行高斯模糊。这个卷积核的权重是根据高斯分布(正态分布)计算得出的。
在这里插入图片描述
  将 G i G_i Gi(表示不同层级的图像)与高斯卷积核进行卷积之后,去除所有偶数行和列,就得到一次下采样的结果。每次下采样之后,图像尺寸都减半,多次处理就得到整个高斯金字塔。

  高斯模糊的过程,类似于将每个像素的特征分配一部分到邻域像素中,所以减去一半的行和列,图像基础特征不变。不过每次下采样,还是会丢失部分图像信息。
在这里插入图片描述
具体来说,我们使用下面两个函数进行操作:

  • cv2.pyrDown:使用高斯金字塔进行一次降采样。
  • cv2.pyrUp:上采样,通过插入0来扩大图像,然后使用与pyrDown相同的卷积核进行卷积。
    在这里插入图片描述
      使用下采样时相同的高斯卷积核进行卷积,可以达到将原先像素分配到邻近插入的0像素的效果,近似恢复原图信息。下面进行演示:
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inlineimg = cv2.imread('./lena.png')
dst1 = cv2.pyrDown(img)
dst2 = cv2.pyrUp(dst1)
print(f' {img.shape=} {dst1.shape=} {dst2.shape=}')plt.figure(figsize=[16,8])
plt.subplot(131); plt.imshow(img[:,:,::-1]);  plt.title("img");
plt.subplot(132); plt.imshow(dst1[:,:,::-1]);  plt.title("pyrDown");
plt.subplot(133); plt.imshow(dst2[:,:,::-1]);  plt.title("pyrDown+pyrUp");

在这里插入图片描述
可以看到,先下采样再上采样,原图的信息还是有部分丢失,处理后的图像没有原图清晰。

3.2 拉普拉斯金字塔

  拉普拉斯金字塔是基于高斯金字塔构建的,它主要用于图像的重建。拉普拉斯金字塔的每一层都是通过当前层原图减去其先下采样后上采样的图像得到的,代表了二者之间的残差。用数学公式表示就是:
L i = G i − P y r U p ( P y r D o w n ( G i ) ) L_{i}=G_{i}-PyrUp(PyrDown(G_{i})) Li=GiPyrUp(PyrDown(Gi))
其中, L i , G i L_{i},G_{i} Li,Gi分别是某一层的原始图像及其拉普拉斯金字塔图像。

在这里插入图片描述

lap0=img-dst2plt.figure(figsize=[16,8])
plt.subplot(131); plt.imshow(img[:,:,::-1]);  plt.title("img");
plt.subplot(132); plt.imshow(dst2[:,:,::-1]);  plt.title("pyrDown+pyrUp");
plt.subplot(133); plt.imshow(lap0[:,:,::-1]);  plt.title("lap0");

在这里插入图片描述
  拉普拉斯金字塔包含了图像的细节和高频信息,所以可以间接地包含轮廓信息,多用于图像的多尺度表示和图像重建任务。

四、图像直方图

4.1 图像直方图基本概念

参考《相机直方图:色调和对比度》

  图像直方图是一种显示图像中像素值分布情况的统计图表。它表示图像中各个像素强度值出现的频率,可以用来分析图像的对比度、亮度、动态范围等特性。直方图的横轴表示像素值(如果是灰度图,0表示最暗,255表示最亮),纵轴表示各像素值的像素数量。

在这里插入图片描述
  如上图所示,左图水面整体偏亮,此部分对应于图像直方图右侧高亮度区域。右图将其分为上中下三个部分分别进行统计,上部像素分布均匀;中间是水面,像素过于集中;下方整体偏亮。

图像直方图既可以统计灰度图,也可以统计彩色图:

  1. 灰度图像直方图:横轴为0-255的灰度值,纵轴为该灰度值出现的频率。
  2. 彩色图像直方图:对RGB图像,可以为每个通道(红、绿、蓝)绘制单独的直方图,显示各通道像素值的分布。

4.2 统计直方图

4.2.1 直接统计

由于图像直方图是统计图像中像素值分布情况,所以可以直接使用plt.hist对图像的灰度值进行统计。

import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图像并转换为灰度
img = cv2.imread('./lena.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 创建图形
plt.figure(figsize=(12, 5))
ax1 = plt.subplot(121);ax1.imshow(gray, cmap='gray');ax1.axis('off');ax1.set_title('Grayscale Image')
ax2 = plt.subplot(122);ax2.hist(gray.ravel(), 256, [0, 256]);ax2.set_title('Histogram')# tight_layout自调整子图参数,使之填充整个图像区域,同时确保子图之间的标签和标题不会重叠。
plt.tight_layout()
plt.show()

在这里插入图片描述

使用plt.subplot的方式并排显示,由于其默认显示坐标轴,两张图坐标会互相重叠

4.2.2 使用OpenCV统计图像直方图

OpenCV 中可以使用cv2.calcHist进行图像直方图计算,其语法为:

calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]]) -> hist
  1. images:输入图像(列表形式,可以对一批图像进行统计)。即使只传入一张图像,也要放在列表中。

  2. channels:需要计算直方图的通道。对于灰度图只能为[0](单通道);对于彩色图像, [0][1][2] 分别表示蓝、绿、红三个通道。

  3. mask:掩膜图像。如果只想计算图像某一部分的直方图,可以传入一个与原图像大小相同的二值掩膜图像,白色部分表示计算区域,黑色部分忽略。若不需要则设 None

  4. histSize:直方图的 bins 数量,一般设置为 [256],表示256个灰度值都单独统计。假设设为16,则每15个像素区间统计一次。
    在这里插入图片描述

  5. ranges:统计的像素值范围,一般为 [0, 256]

  6. accumulate:是否累积,默认为False。如果对一组图像进行统计,可以设为True,表示统计图像时,在上一个直方图的基础上累积结果,而不是从0开始。

import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图像
img = cv2.imread('./lena.png')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 统计直方数据
histb = cv2.calcHist([img], [0], None, [256], [0, 255])
histg = cv2.calcHist([img], [1], None, [256], [0, 255])
histr = cv2.calcHist([img], [2], None, [256], [0, 255])# 创建一个图形窗口,包含两个子图
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))# 在第一个子图中显示原图
ax1.imshow(img_rgb);ax1.set_title('Original Image');ax1.axis('off')  # 不显示坐标轴# 在第二个子图中绘制直方图
ax2.plot(histb, color='b', label='blue');ax2.plot(histg, color='g', label='green');
ax2.plot(histr, color='r', label='red');ax2.set_title('Histogram using opencv');ax2.legend()plt.tight_layout()
plt.show()

在这里插入图片描述

4.2.3 使用掩膜

  我们可以通过使用掩膜,只统计图中感兴趣的区域。掩膜是与原图像大小相同的二值掩膜图像,只有白色区域会被统计。
在这里插入图片描述

# 生成灰度图,并创建掩膜
img = cv2.imread('./lena.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
mask = np.zeros(gray.shape, np.uint8)				# 生成掩膜图像
mask[200:400, 200: 400] = 255						# 直接设置掩码区域# 生成掩码部分的灰度图
# gray和gray做与运算结果还是gray, 结果再和mask做与运算,黑色部分置0,白色部分不变
gray_mask=cv2.bitwise_and(gray, gray, mask=mask)    # 对是否使用掩膜进行分别统计
hist_mask = cv2.calcHist([gray], [0], mask, [256], [0, 255])
hist_gray = cv2.calcHist([gray], [0], None, [256], [0, 255])plt.figure(figsize=[10,5])
plt.subplot(121); plt.imshow(gray_mask,cmap='gray'); plt.title("gray_mask");
plt.subplot(122); plt.plot(hist_mask, label='mask');plt.plot(hist_gray, label='gray');plt.legend();

在这里插入图片描述

4.3 直方图均衡化

  有的时候拍出的图片整体偏亮或偏暗,或者亮度很不均匀。直方图均衡化可以改善图像的对比度。它通过重新分配图像像素的灰度值,使得图像中灰度值的分布更加均匀,从而增强细节,使图像看起来更清晰(使灰度值扩展到整个范围,从而增加图像的全局对比度)。

直方图均衡化的实现原理:

  1. 计算图像的直方图: 首先统计出原始图像中每个灰度级(0-255)所出现的频率,即构建图像的直方图。
    在这里插入图片描述

  2. 计算累积直方图

在这里插入图片描述
|

  1. 映射原始像素值:将累计直方图结果直接乘以255就是最终均衡直方图的结果

在这里插入图片描述
  在OpenCV中,可以使用cv2.equalizeHist()函数来实现直方图均衡化。该函数只适用于灰度图像。下面我们对一张图进行整体增亮和增暗处理,然后进行直方图均衡化看看效果。

img=cv2.imread('lena.png')
matrix = np.ones(img.shape, dtype = "uint8") * 50img_brighter = cv2.add(img, matrix) 
img_darker   = cv2.subtract(img, matrix)# Show the images
plt.figure(figsize=[18,5])
plt.subplot(131); plt.imshow(img_darker[:,:,::-1]);  plt.title("Darker");
plt.subplot(132); plt.imshow(img[:,:,::-1]);         plt.title("Original");
plt.subplot(133); plt.imshow(img_brighter[:,:,::-1]);plt.title("Brighter");

在这里插入图片描述
  彩色图像有多个通道(如 RGB 或 HSV 颜色空间),直接对每个通道进行直方图均衡化可能会导致颜色失真。因此,通常不会对 RGB 三个通道直接进行均衡化。比较常见的方法是将图像转换到亮度通道可分离的颜色空间(如 YUV 或 HSV),然后只对亮度通道进行直方图均衡化,再将处理后的图像转换回原来的颜色空间。

# 将图像从 BGR 转换到 YUV 颜色空间
yuv_darker=cv2.cvtColor(img_darker, cv2.COLOR_BGR2YUV)
yuv_brighter=cv2.cvtColor(img_brighter, cv2.COLOR_BGR2YUV)# 对 Y 通道(亮度通道)进行直方图均衡化
yuv_darker[:,:,0] = cv2.equalizeHist(yuv_darker[:,:,0])
yuv_brighter[:,:,0] = cv2.equalizeHist(yuv_brighter[:,:,0])# 将图像从 YUV 转换回 BGR 颜色空间
darker_equ=cv2.cvtColor(yuv_darker, cv2.COLOR_YUV2BGR)
brighter_equ=cv2.cvtColor(yuv_brighter, cv2.COLOR_YUV2BGR)# # 显示原图和均衡化后的图像
plt.figure(figsize=[18,5])
plt.subplot(131); plt.imshow(darker_equ[:,:,::-1]);  plt.title("darker_equ");
plt.subplot(132); plt.imshow(img[:,:,::-1]);         plt.title("Original");
plt.subplot(133); plt.imshow(brighter_equ[:,:,::-1]);plt.title("brighter_equ");

在这里插入图片描述

4.4 自适应直方图均衡化 (CLAHE)

4.4.1 实现原理

直方图均衡化的局限性:

  • 噪声增强:对于含有大量噪声的图像,均衡化可能会使噪声也得到增强,导致图像质量下降。
  • 细节丢失:直方图均衡化是一种全局处理方法,无法处理局部区域对比度问题。如果图像中存在不同亮度的区域,全局均衡化可能会使局部细节丢失。

  针对上述问题,OpenCV提供了自适应直方图均衡化(CLAHE, Contrast Limited Adaptive Histogram Equalization),它通过对图像的局部区域(称为“子图块”)分别进行直方图均衡化,从而增强局部对比度,同时避免过度增强噪声。

  1. 将图像分割成多个子图块: CLAHE将图像划分为多个较小的矩形区域(称为“子图块”或“窗口”,通常是8x8或16x16的网格)。每个子图块会单独进行直方图均衡化,这样可以增强每个局部区域的对比度。

  2. 对每个子图块进行直方图均衡化: 在每个子图块上执行和普通直方图均衡化类似的操作,计算该子图块的直方图,然后根据该直方图的累积分布函数 (CDF) 来重新分配像素值。

  3. 应用对比度限制: 在局部直方图均衡化时,某些子图块中的像素可能集中在特定的灰度范围内,导致对比度过度增强,尤其是在图像包含噪声时。因此,CLAHE引入了一个对比度限制参数clipLimit,用于限制每个灰度级的像素频率。当某个灰度级的频率超过 clipLimit 时,多余的部分会均匀分配到其他灰度级。

    • clipLimit:表示限制直方图中某个灰度级出现的最大频率,防止噪声被过度放大。
  4. 插值平滑: 对于每个像素,由于它位于多个子图块的边界上,CLAHE对这些子图块的均衡化结果进行插值平滑,避免由于直接均衡化子图块而产生块状效应(blocky effect)。通过插值,这些子图块的边界变得平滑,使得过渡更加自然。

CLAHE的效果:

  • 局部对比度增强:相比全局直方图均衡化,CLAHE能够有效增强图像中不同区域的对比度,因此在处理具有复杂光照或局部对比度差异大的图像时效果更好。
  • 防止过度增强噪声:由于引入了对比度限制参数,CLAHE可以防止对比度过度增强,从而避免了噪声的放大。
  • 适合自然图像:CLAHE常用于医学图像、卫星图像和低光照图像的处理,这些图像通常需要增强局部区域的对比度,而不希望整体图像变得太过刺眼。
属性普通直方图均衡化CLAHE(自适应直方图均衡化)
处理范围全局局部,分块处理
效果提高全局对比度,可能导致局部细节丢失提高局部对比度,增强细节
噪声处理可能过度增强噪声使用clipLimit限制对比度增强,避免噪声过度增强
适用场景适用于灰度值集中分布的图像,全局对比度不高适用于包含复杂光照或局部对比度差异大的图像(如医学、卫星图像)
常见问题对局部细节处理不佳,可能丢失对比度使用不当时,可能引入分块效应,不过插值技术可以有效减缓
4.4.2 代码实现

   OpenCV 中使用cv2.createCLAHE 函数进行自适应直方图均衡化,它生成一个 CLAHE 对象,可以通过该对象对图像应用自适应直方图均衡化。

createCLAHE([, clipLimit[, tileGridSize]]) -> retval
  1. clipLimit:对比度限制阈值,浮点型,默认为2.0clipLimit 限制了每个灰度级像素频率的最大值,超过 clipLimit 的频率会被平摊到其他灰度级,从而避免过度增强局部噪声。

    • 如果 clipLimit 值较低,对比度增强较弱。
    • 如果 clipLimit 值较高,则会增强对比度。
  2. tileGridSize:整型元组,表示子图块的大小。默认为(8, 8),即将图像分为 8×8 个子图块。对每个子图块单独进行直方图均衡化,然后在子图块之间进行插值以避免边界出现突变现象。

    • 值越大:处理的大块区域更多,图像整体的对比度调整幅度更大,但局部细节增强不明显。
    • 值越小:处理的小块区域更多,图像局部对比度更强,但可能会引入噪声和块效应。
# 将图像从 BGR 转换到 YUV 颜色空间
yuv_darker=cv2.cvtColor(img_darker, cv2.COLOR_BGR2YUV)
yuv_brighter=cv2.cvtColor(img_brighter, cv2.COLOR_BGR2YUV)# 创建CLAHE对象,设定clipLimit和tileGridSize
clahe = cv2.createCLAHE(clipLimit=1.0, tileGridSize=(4, 4))
# 对 Y 通道(亮度通道)进行直方图均衡化
yuv_darker[:,:,0] = clahe.apply(yuv_darker[:,:,0])
yuv_brighter[:,:,0] = clahe.apply(yuv_brighter[:,:,0])# 将图像从 YUV 转换回 BGR 颜色空间
darker_equ=cv2.cvtColor(yuv_darker, cv2.COLOR_YUV2BGR)
brighter_equ=cv2.cvtColor(yuv_brighter, cv2.COLOR_YUV2BGR)# # 显示原图和均衡化后的图像
plt.figure(figsize=[18,5])
plt.subplot(131); plt.imshow(darker_equ[:,:,::-1]);  plt.title("darker_equ");
plt.subplot(132); plt.imshow(img[:,:,::-1]);         plt.title("Original");
plt.subplot(133); plt.imshow(brighter_equ[:,:,::-1]);plt.title("brighter_equ");

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/434793.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot集成Matlab软件实战

在项目中处理矩阵等复杂数据结构的时候&#xff0c;可以用Matlab程序来运行&#xff0c;其优点是很多的。 专用工具箱和强大的矩阵运算能力&#xff1a;MATLAB 拥有强大的数学工具箱和优化工具箱&#xff0c;适合处理大规模矩阵运算以及水文模型的率定。MATLAB 的 Optimization…

2024平价电容笔推荐!精选五大靠谱电容笔测评盘点!

现在电子设备已经成为我们生活、学习和工作中不可或缺的重要工具。而电容笔作为与电子设备紧密配合的配件&#xff0c;其重要性也日益凸显&#xff0c;为我们的数字操作体验带来极大的便利和提升。然而&#xff0c;市场上电容笔的品牌众多&#xff0c;价格、性能和品质参差不齐…

STM32F407HAL库输出互补PWM波以及死区时间计算

互补PWM波配置 STM32F407VET6的高级定时器TIM1、TIM8可以生成互补的PWM波&#xff0c;用HAL库配置非常方便。 我们使用高级定时器TIM1&#xff0c;选择一个通道&#xff08;我这里选择通道二&#xff09;&#xff0c;然后选择PWM Generation CH2 CH2N。这里N的意思是互补&…

字符串逆序

字符串逆序&#xff0c;面试常考点&#xff0c;由于实现思路很容易&#xff0c;面试官也通常会让你使用多种解法实现&#xff0c;并手写c伪代码&#xff0c;其中每种解法的时空复杂度都要很清楚&#xff0c;能够分析&#xff0c;尤其是最后一种递归实现属于比较进阶的思维了&am…

基于Python大数据的B站热门视频的数据分析及可视化系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

遗传算法与深度学习实战——使用进化策略实现EvoLisa

遗传算法与深度学习实战——使用进化策略实现EvoLisa 0. 前言1. 使用进化策略实现 EvoLisa2. 运行结果相关链接 0. 前言 我们已经学习了进化策略 (Evolutionary Strategies, ES) 的基本原理&#xff0c;并且尝试使用 ES 解决了函数逼近问题。函数逼近是一个很好的基准问题&…

【Git】克隆主项目,并同时克隆所有子模块

子模块 带有箭头的文件夹&#xff08;relaxed_ik_core&#xff09;通常表示这是一个 Git 子模块&#xff08;submodule&#xff09;。Git 子模块是一种嵌入式的 Git 仓库&#xff0c;它允许你在一个仓库中引用其他的 Git 仓库。换句话说&#xff0c;relaxed_ik_core 不是这个项…

基于python+spark的外卖餐饮数据分析系统设计与实现(含论文)-Spark毕业设计选题推荐

博主介绍&#xff1a; 大家好&#xff0c;本人精通Java、Python、C#、C、C编程语言&#xff0c;同时也熟练掌握微信小程序、Php和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我有丰富的成品Java、Python、C#毕设项目经验&#xff0c;能够为学生提供各类…

YOLOv8 Windows c++推理

#添加一个**yolov8\_。onx **和/或**yolov5\_。Onnx **模型(s)到ultralytics文件夹。 #编辑**main.cpp**来改变**projectBasePath**来匹配你的用户。#请注意&#xff0c;默认情况下&#xff0c;CMake文件将尝试导入CUDA库以与opencv dnn (cuDNN) GPU推理一起使用。 #如果你的Op…

在matlab中Application Compiler后的软件无法打开

&#x1f3c6;本文收录于《全栈Bug调优(实战版)》专栏&#xff0c;主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&am…

怎么给儿童掏耳朵比较安全?5款安全的儿童掏耳勺!

儿童的耳部娇嫩&#xff0c;在为其掏耳朵时需格外谨慎。市面上的传统耳勺存在诸多风险&#xff0c;稍不注意会刮伤儿童的耳道肌肤。在此建议家长们为孩子选用儿童专用可视挖耳勺。这种挖耳勺能够让家长清晰地看到孩子耳道内的情况&#xff0c;从而更加安全、精准地为孩子清理耳…

React 启动时webpack版本冲突报错

报错信息&#xff1a; 解决办法&#xff1a; 找到全局webpack的安装路径并cmd 删除全局webpack 安装所需要的版本

Docker Desktop 安装Centos 7.9 使用yum install不可用问题

安装centos镜像并run之后&#xff0c;使用yum install 命令安装出现如下错误&#xff0c;可使用此命令替换mirror。 报错信息&#xff1a; Could not retrieve mirrorlist http://mirrorlist.centos.org/?release7&archaarch64&repoos&infracontainer error was…

2015年国赛高教杯数学建模B题互联网+时代的出租车资源配置解题全过程文档及程序

2015年国赛高教杯数学建模 B题 互联网时代的出租车资源配置 出租车是市民出行的重要交通工具之一&#xff0c;“打车难”是人们关注的一个社会热点问题。随着“互联网”时代的到来&#xff0c;有多家公司依托移动互联网建立了打车软件服务平台&#xff0c;实现了乘客与出租车司…

Spring-bean实例化的方式

前言 什么是bean的实例化&#xff1f; 通常我们使用spring管理java的对象&#xff0c;一般称这个java对象为一个实例化的bean。bean的实例化方式&#xff0c;实际上就是spring创建并管理java对象实例的方式 bean的实例化方式 在Java和Spring框架的上下文中&#xff0c;Bean的实…

医院安保巡更管理应用二维码无纸化巡更方式

医院安保巡查是维护医院秩序安全的重中之重&#xff0c;在确保医院的安全运行&#xff0c;预防和减少安全事故的发生。通过定期的安全巡查&#xff0c;可以及时发现和解决潜在的安全隐患&#xff0c;保障医护人员和患者的安全。例如&#xff1a;‌安全疏散通道、‌监控设备‌、…

ACDsee简体中文版网盘资源下载(含教程)

如大家所熟悉的&#xff0c;ACDSee是一款集看图、编辑和管理于一体的软件&#xff0c;其凭借着打开速度快、管理功能强、操作界面友好简单等等优势&#xff0c;广受用户的喜欢。目前最新为ACDSee 2024版本。 一、文件管理 ACDSee数据库在文件管理方面表现出色。它可以帮助用户…

四气两尘监测站中空气质量传感器推荐

在快速发展的工业化进程中&#xff0c;空气质量已成为衡量一个地区环境健康水平的重要指标。随着公众环保意识的增强&#xff0c;对空气质量的关注不再局限于直观的蓝天白云&#xff0c;而是深入到更为细微、复杂的污染物层面&#xff0c;其中&#xff0c;“四气两尘”便是这一…

操作平台使用中应每月不少于几次定期检查?

在当今数字化时代&#xff0c;操作平台作为企业与个人日常运营的核心载体&#xff0c;其稳定性和安全性直接关系到业务的高效运行与数据的严密保护。因此&#xff0c;定期进行操作平台的检查与维护&#xff0c;成为了不可忽视的重要环节。特别是&#xff0c;确保每月进行不少于…

JAVA的版本

Java的版本开始还正常&#xff1a;1.0 ->1.1 顺序增加&#xff0c;到了2004年&#xff0c;不知什么原因1.5又有了新的平行名字5&#xff0c;这样Java 1.6对应Java6&#xff0c;一直到Java1.8 对应 Java8&#xff0c;然后到在2017年彻底没了Java1.9,只有Java9了。好吧这可以忍…