OpenCV 从入门到精通(day_04)

news2025/4/6 10:39:54

1. 绘制图像轮廓

1.1 什么是轮廓

        轮廓是一系列相连的点组成的曲线,代表了物体的基本外形。相对于边缘,轮廓是连续的,边缘不一定连续,如下图所示。其实边缘主要是作为图像的特征使用,比如可以用边缘特征可以区分脸和手,而轮廓主要用来分析物体的形态,比如物体的周长和面积等,可以说边缘包括轮廓。

1.2 寻找轮廓

        在OpenCV中,使用cv2.findContours()来进行寻找轮廓,其原理过于复杂,这里只进行一个简单的介绍,具体的实现原理可参考:https://zhuanlan.zhihu.com/p/107257870
        寻找轮廓需要将图像做一个二值化处理,并且根据图像的不同选择不同的二值化方法来将图像中要绘制轮廓的部分置为白色,其余部分置为黑色。也就是说,我们需要对原始的图像进行灰度化、二值化的处理,令目标区域显示为白色,其他区域显示为黑色,如下图所示。

        之后,对图像中的像素进行遍历,当一个白色像素相邻(上下左右及两条对角线)位置有黑色像素存在或者一个黑色像素相邻(上下左右及两条对角线)位置有白色像素存在时,那么该像素点就会被认定为边界像素点,轮廓就是有无数个这样的边界点组成的。
        下面具体介绍一下cv2.findContours()函数,其函数原型为:

        contours,hierarchy = cv2.findContours(image,mode,method)

  • contours:表示获取到的轮廓点的列表。检测到有多少个轮廓,该列表就有多少子列表,每一个子列表都代表了一个轮廓中所有点的坐标。

  • hierarchy:表示轮廓之间的关系。对于第i条轮廓,hierarchy[i][0], hierarchy[i][1] , hierarchy[i][2] , hierarchy[i][3]分别表示其后一条轮廓、前一条轮廓、(同层次的第一个)子轮廓、父轮廓的索引(如果没有相应的轮廓,则对应位置为-1)。该参数的使用情况会比较少。

  • image:表示输入的二值化图像。

  • mode:表示轮廓的检索模式。

  • method:轮廓的表示方法。

1.2.1 mode参数

        mode参数共有四个选项分别为:RETR_LIST,RETR_EXTERNAL,RETR_CCOMP,RETR_TREE。
        RETR_LIST:表示列出所有的轮廓。并且在hierarchy里的轮廓关系中,每一个轮廓只有前一条轮廓与后一条轮廓的索引,而没有父轮廓与子轮廓的索引。
        RETR_EXTERNAL:表示只列出最外层的轮廓。并且在hierarchy里的轮廓关系中,每一个轮廓只有前一条轮廓与后一条轮廓的索引,而没有父轮廓与子轮廓的索引。
        RETR_CCOMP:表示列出所有的轮廓。并且在hierarchy里的轮廓关系中,轮廓会按照成对的方式显示。
        RETR_TREE:表示列出所有的轮廓。并且在hierarchy里的轮廓关系中,轮廓会按照树的方式显示,其中最外层的轮廓作为树根,其子轮廓是一个个的树枝。

1.2.2 method参数

        轮廓近似方法。决定如何简化轮廓点的数量。
        method参数有三个选项:CHAIN_APPROX_NONE、CHAIN_APPROX_SIMPLE、CHAIN_APPROX_TC89_L1。
        CHAIN_APPROX_NONE表示将所有的轮廓点都进行存储;
        CHAIN_APPROX_SIMPLE表示只存储有用的点,比如直线只存储起点和终点,四边形只存储四个顶点,默认使用这个方法;
        CHAIN_APPROX_TC89_L1表示使用Teh-Chin链逼近算法进行轮廓逼近。这种方法使用的是Teh-Chin链码,它是一种边缘检测算法,可以对轮廓进行逼近,减少轮廓中的冗余点,从而更加准确地表示轮廓的形状。CHAIN_APPROX_TC89_L1是一种较为精确的轮廓逼近方法,适用于需要较高精度的轮廓表示的情况。
        对于mode和method这两个参数来说,一般使用RETR_EXTERNAL和CHAIN_APPROX_SIMPLE这两个选项。

1.2.3 绘制轮廓

        轮廓找出来后,其实返回的是一个轮廓点坐标的列表,因此我们需要根据这些坐标将轮廓画出来,因此就用到了绘制轮廓的方法。

cv2.drawContours(image, contours, contourIdx, color, thickness)

  • image:原始图像,一般为单通道或三通道的 numpy 数组。

  • contours:包含多个轮廓的列表,每个轮廓本身也是一个由点坐标构成的二维数组(numpy数组)。

  • contourIdx:要绘制的轮廓索引。如果设为 -1,则会绘制所有轮廓。

  • color:绘制轮廓的颜色,可以是 BGR 值或者是灰度值(对于灰度图像)。

  • thickness:轮廓线的宽度,如果是正数,则画实线;如果是负数,则填充轮廓内的区域。

        示例:

import cv2 as cv

img = cv.imread('./images/num.png')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 二值化:OTSU+反阈值法
_, img_binary = cv.threshold(gray, 127, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
# 查找轮廓:cv.findContours(img,mode,method)
contours, h = cv.findContours(img_binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# 绘制轮廓:cv.drawContours(img.contours,-1,(b,g,r),2,thickness)
cv.drawContours(img, contours, -1, (0, 0, 255), 2)
cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()

2. 凸包特征检测

        在进行凸包特征检测之前,首先要了解什么是凸包。通俗的讲,凸包其实就是将一张图片中物体的最外层的点连接起来构成的凸多边形,它能包含物体中所有的内容。
        我们以点集来举例,假如有这么一些点,其分布如下图所示:

        那么经过凸包检测并绘制之后,其结果应该如下图所示:

        可以看到,原图像在经过凸包检测之后,会将最外围的几个点进行连接,剩余的点都在这些点的包围圈之内。那么凸包检测到底是怎么检测出哪些点是最外围的点呢?
        我们还是以上面的点集为例,假设我们知道这些点的坐标,那么我们就可以找出处于最左边和最右边的点,如下图所示:

        接着将这两个点连接,并将点集分为上半区和下半区,我们以上半区为例:

        找到上面这些点离直线最远的点,其中,这条直线由于有两个点的坐标,所以其表示的直线方程是已知的,并且上面的点的坐标也是已知的,那么我们就可以根据点到直线的距离公式来进行计算哪个点到直线的距离最远,假设直线的方程为:A x+B y+C=0,那么点(x_{0},y_{0})到直线的距离公式为:

        然后我们就可以得到距离这条线最远的点,将其与左右两点连起来,并分别命名为y1和y2,如下图所示:

        然后分别根据点的坐标求出y1和y2的直线方程,之后将上半区的每个点的坐标带入下面公式中:

        当d=0时,表明该点在直线上;当d>0时,表明点在直线的上方,在这里就代表该点在上图所围成的三角形的外面,也就意味着该三角形并没有完全包围住上半区的所有点,需要重新寻找凸包点;当d<0时,表明点在直线的下方,在这里就代表该点在上图所围成的三角形的里面,也就代表着这类点就不用管了。
        当出现d>0时,我们需要将出现这种结果的两个计算对象:某点和y1或y2这条线标记,并在最后重新计算出现这种现象的点集到y1或y2的距离来获取新的凸包点的坐标。在本例子中,也就是如下图所示的点和y2这条直线:

        由于本例子中只有这一个点在这个三角形之外,所以毫无疑问的它就是一个凸包点,因此直接将它与y2直线的两个端点相连即可。当有很多点在y2直线外时,就需要计算每个点到y2的距离,然后找到离得最远的点与y2构建三角形,并重新计算是否还有点在该三角形之外,如果没有,那么这个点就是新的凸包点,如果有,那就需要重复上面的步骤,直到所有的点都能被包围住,那么构建直线的点就是凸包点。这是上半区寻找凸包点的过程,下半区寻找凸包点的思路与此一模一样,只不过是需要筛选d<0(也就是点在直线的下方)的点,并重新构建三角形,寻找新的凸包点。
        上面的过程都是基于我们知道点的坐标进行的,实际上,对于未经处理的图像,我们无法直接获取点的坐标。特别是对于彩色图像,我们需要将其转换为二值图像,并使用轮廓检测技术来获取轮廓边界的点的坐标。然后,我们才能进行上述寻找凸包点的过程。因此,在处理图像时,我们需要将彩色图像转换为二值图像,并通过轮廓检测技术来获取轮廓边界的点的坐标,然后才能进行凸包点的寻找过程。

2.1 获取凸包点

        cv2.convexHull(points, hull=None, clockwise=False, returnPoints=True)

  • points:输入参数,图像的轮廓

  • hull(可选):输出参数,用于存储计算得到的凸包顶点序列,如果不指定,则会创建一个新的数组。

  • clockwise(可选):布尔值,如果为True,则计算顺时针方向的凸包,否则默认计算逆时针方向的凸包。

  • returnPoints(可选):布尔值,如果为True(默认),则函数返回的是原始点集中的点构成的凸包顶点序列;如果为False,则返回的是凸包顶点对应的边界框内的索引。

2.2 绘制凸包

        cv2.polylines(image, pts, isClosed, color, thickness=1)

  • image:要绘制线条的目标图像,它应该是一个OpenCV格式的二维图像数组(如numpy数组)。

  • pts:一个二维 numpy 数组,每个元素是一维数组,代表一个多边形的一系列顶点坐标。

  • isClosed:布尔值,表示是否闭合多边形,如果为 True,会在最后一个顶点和第一个顶点间自动添加一条线段,形成封闭的多边形。

  • color:线条颜色,可以是一个三元组或四元组,分别对应BGR或BGRA通道的颜色值,或者是灰度图像的一个整数值。

  • thickness(可选):线条宽度,默认值为1。

        示例:

import cv2 as cv

img = cv.imread('./images/tu.png')
# 取轮廓点
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
_, img_binary = cv.threshold(gray, 127, 255, cv.THRESH_OTSU)
contours, h = cv.findContours(img_binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
contours1, h1 = cv.findContours(img_binary, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
print(contours1)
# 获取凸包点:cv2.convexHull(points, hull=None, clockwise=False, returnPoints=True)
hull = cv.convexHull(contours[0])
# print(hull)
# 绘制凸包
cv.polylines(img, [hull], True, (0, 0, 255), 2)
cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()

3. 图像轮廓特征查找

3.1 外接矩形

        形状的外接矩形有两种,如下图,绿色的叫外接矩形,表示不考虑旋转并且能包含整个轮廓的矩形。其中,外接矩形可根据获得到的轮廓坐标中最上、最下、最左、最右的点的坐标来绘制外接矩形,也就是下图中的绿色矩形。

        示例:

import cv2 as cv

img = cv.imread('./images/num.png')
# 读取灰度图
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

# 二值化处理
_, img_binary = cv.threshold(gray, 127, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
# cv.imshow('img_binary', img_binary)

# 查找轮廓
contours, h = cv.findContours(img_binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

# 绘制轮廓
cv.drawContours(img, contours, -1, (0, 255, 0), 3)

# 遍历轮廓(该图有三个轮廓分别对应图中的三个数字)
for cont in contours:
    x, y, w, h = cv.boundingRect(cont)
    # print(x, y, w, h)
    cv.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)

cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()

3.2 最小外接矩形

        最小外接矩形就是上图所示的蓝色矩形,寻找最小外接矩形使用的算法叫做旋转卡壳法。下面简单说明一下旋转卡壳法的思路:
在上一章节中,我们了解到了凸包的概念,凸包就是一个点集的凸多边形,它是这个点集所有点的凸壳,点集中所有的点都处于凸包里,构成凸包的点我们叫做凸包点。而旋转卡壳法就是基于凸包点进行的,
旋转卡壳法有一个很重要的前提条件:对于多边形P的一个外接矩形存在一条边与原多边形的边共线。假设某凸包图如下所示:

        根据前提条件,上面的凸多边形的最小外接矩形与凸多边形的某条边是共线的。因此我们只需要以其中的一条边为起始边,然后按照逆时针方向计算每个凸包点与起始边的距离,并将距离最大的点记录下来。

        如上图所示,我们首先以a、b两点为起始边,并计算出e点离起始边最远,那么e到起始边的距离就是一个矩形的高度,因此我们只需要再找出矩形的宽度即可。对于矩形的最右边,以向量\overline{​{a b}}为基准,然后分别计算凸包点在向量\overline{​{a b}}上的投影的长度,投影最长的凸包点所在的垂直于起始边的直线就是矩形最右边所在的直线。

        如上图所示,d点就是在向量\overline{​{a b}}上投影最长的凸包点,那么通过d点垂直于直线ab的直线就是矩形的右边界所在的直线。矩形的左边界的也是这么计算的,不同的是使用的向量不是\overline{​{a b}}而是\overline{​{b a}}

        如上图所示,h点垂直于ab的直线就是以ab为起始边所计算出来的矩形所在的左边界所在的直线。其中矩形的高就是e点到直线ab的距离,矩形的宽是h点在向量上\overline{​{b a}}的投影加上d点在向量\overline{​{a b}}上的投影减去ab的长度,即:

        于是我们就有了以ab为起始边所构成的外接矩形的宽和高,这样就可以得到该矩形的面积。然后再以bc为起始边,并计算其外接矩形的面积。也就是说凸多边形有几个边,就要构建几次外接矩形,然后找到其中面积最小的矩形作为该凸多边形的最小外接矩形。
        在OpenCV中,可以直接使用cv2.minAreaRect()来获取最小外接矩形,该函数只需要输入一个参数,就是凸包点的坐标,然后会返回最小外接矩形的中心点坐标、宽高以及旋转角度。通过返回的内容信息,即可绘制凸多边形的的最小外接矩形。

        需要使用到的API说明:
        contours, hierarchy = cv2.findContours(image_np_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE),contours为二值图像上查找所有的外部轮廓

        rect = cv2.minAreaRect(cnt),传入的cnt参数为contours中的轮廓,可以遍历contours中的所有轮廓,然后计算出每个轮廓的小面积外接矩形,rect 是计算轮廓最小面积外接矩形:rect 结构通常包含中心点坐标 (x, y)、宽度 width、高度 height 和旋转角度 angle

        cv2.boxPoints(rect).astype(int)cv2.boxPoints(rect)返回 是一个形状为 4行2列的数组,每一行代表一个点的坐标(x, y),顺序按照逆时针或顺时针方向排列,将最小外接矩形转换为边界框的四个角点,并转换为整数坐标。

        cv2.drawContours(image, contours, contourIdx, color, thickness)

  • image:原图像,一般为 numpy 数组,通常为灰度或彩色图像。

  • contours:一个包含多个轮廓的列表,可以用上一个api得到的 [box]

  • contourIdx:要绘制的轮廓索引。如果设置为 -1,则绘制所有轮廓。

  • color:轮廓的颜色,可以是 BGR 颜色格式的三元组,例如 (0, 0, 255) 表示红色。

  • thickness:轮廓线的粗细,如果是正数,则绘制实线;如果是 0,则绘制轮廓点;如果是负数,则填充轮廓内部区域。

        示例:

import cv2 as cv
import numpy as np

img = cv.imread('./images/num.png')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

# 二值化:OTSU+反阈值法
_, img_binary = cv.threshold(gray, 127, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)

# 查找轮廓:找最外层+最有用的
contours, h = cv.findContours(img_binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# 循环,遍历每一个轮廓,查找最小外接矩阵
for con in contours:
    # 返回最小外接矩阵中心点坐标、宽高、旋转角度
    rect = cv.minAreaRect(con)  # rect中包含了centerpoint,w、h、angle
    # 返回最小外接矩形的四个顶点坐标
    points = cv.boxPoints(rect).astype(np.int32)
    # 绘制最小外接矩形
    cv.drawContours(img, [points], -1, (0, 255, 0), 2)
    print(rect)

cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()

3.3 最小外接圆

        寻找最小外接圆使用的算法是Welzl算法。Welzl算法基于一个定理:希尔伯特圆定理表明,对于平面上的任意三个不在同一直线上的点,存在一个唯一的圆同时通过这三个点,且该圆是最小面积的圆(即包含这三个点的圆中半径最小的圆,也称为最小覆盖圆)。
        进一步推广到任意 n 个不在同一圆上的点,总存在一个唯一的最小覆盖圆包含这 n 个点。
        若已经存在平面上互不共线(或共圆)的 n 个点,并确定了它们的最小覆盖圆,那么添加第 n+1 个点,并且要求这个点不在原来的最小覆盖圆内(即在圆外),为了使新的包含 n+1 个点的最小覆盖圆的半径增大,新加入的点必须位于由原 n 个点确定的最小覆盖圆的边界上(即圆周上)。这是因为,如果新点在原最小覆盖圆的内部,显然不会影响最小覆盖圆;如果新点在原最小覆盖圆之外但不在圆周上,那么通过新点和至少两个原有圆上的点可以构造出一个更大的圆,这个圆必然比原最小覆盖圆更大,因此不是包含所有 n+1 个点的最小覆盖圆。所以,按照这一逻辑,当第 n+1 个点在原 n 个点的最小覆盖圆外时,确实这个点会位于包含所有 n+1 个点的新最小覆盖圆的圆周上。
        有了这个定理,就可以先取3个点建立一个圆(不共线的三个点即可确定一个圆,如果共线就取距离最远的两个点作为直径建立圆),然后遍历剩下的所有点,对于遍历到的点P来说:如果该点在圆内,那么最小覆盖圆不变。
        如果该点在圆外,根据上述定理,该点一定在想要求得的最小覆盖圆的圆周上,又因为三个点才能确定一个圆,所以需要枚举P点之前的点来找其余的两个点。当找到与P点组成的圆能够将所有点都包含在圆内或圆上,该圆就是这些点的最小外接圆。
        在OpenCV中,可以直接使用cv2.minEnclosingCircle()来获取最小外接圆,该函数只需要输入一个参数,就是要绘制最小外接圆的点集的坐标,然后会返回最小外接圆的圆心坐标与半径。通过该函数返回的内容信息即可绘制某点集的最小外接圆。如下图所示:

        需要使用的API说明

        cv2.minEnclosingCircle(points) -> (center, radius)

        参数说明:

  • points:输入参数图片轮廓数据

        返回值:

  • center:一个包含圆心坐标的二元组 (x, y)

  • radius:浮点数类型,表示计算得到的最小覆盖圆的半径。

        cv2.circle(img, center, radius, color, thickness)

  • img:输入图像,通常是一个numpy数组,代表要绘制圆形的图像。

  • center:一个二元组 (x, y),表示圆心的坐标位置。

  • radius:整型或浮点型数值,表示圆的半径长度。

  • color:颜色标识,可以是BGR格式的三元组 (B, G, R),例如 (255, 0, 0) 表示红色。

  • thickness:整数,表示圆边框的宽度。如果设置为 -1,则会填充整个圆。

示例:

import cv2 as cv
import numpy as np

img = cv.imread('./images/num.png')
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

# 二值化
_, binary = cv.threshold(img_gray, 127, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)

# 获取轮廓值
contours, h = cv.findContours(binary, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)

for cont in contours:
    (x, y), r = cv.minEnclosingCircle(cont)
    x, y, r = np.int_(x), np.int_(y), np.int_(r)
    cv.circle(img, (x, y), r, (0, 255, 0), 2, cv.LINE_AA)

cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()

4. 直方图均衡化

4.1 什么是直方图

直方图是对数据进行统计的一种方法,并且将统计值组织到一系列实现定义好的 bin 当中。其中, bin 为直方图中经常用到的一个概念,可以译为 “直条” 或 “组距”,其数值是从数据中计算出的特征统计量,这些数据可以是诸如梯度、方向、色彩或任何其他特征。

4.2 绘制直方图

        就是以像素值为横坐标,像素值的个数为纵坐标绘制一个统计图。

        hist=cv2.calcHist(images, channels, mask, histSize, ranges)

  • images:输入图像列表,可以是一幅或多幅图像(通常是灰度图像或者彩色图像的各个通道)。

  • channels:一个包含整数的列表,指示在每个图像上计算直方图的通道编号。如果输入图像是灰度图,它的值就是 [0];如果是彩色图像的话,传入的参数可以是 [0],[1],[2] 它们分别对应着通道 B,G,R。

  • mask(可选):一个与输入图像尺寸相同的二值掩模图像,其中非零元素标记了参与直方图计算的区域,None为全部计算。

  • histSize:一个整数列表,也就是直方图的区间个数(BIN 的数目)。用中括号括起来,例如:[256]。

  • ranges:每维数据的取值范围,它是一个二维列表,每一维对应一个通道的最小值和最大值,例如对灰度图像可能是 [0, 256]

        返回值hist 是一个长度为255的数组,数组中的每个值表示图像中对应灰度等级的像素计数

        minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(hist),获取直方图的最小值、最大值及其对应最小值的位置索引、最大值的位置索引

        cv2.line(img, pt1, pt2, color, thickness)

  • img:原始图像,即要在上面画线的numpy数组(一般为uint8类型)。

  • pt1pt2:分别为线段的起点和终点坐标,它们都是元组类型,例如 (x1, y1)(x2, y2) 分别代表线段两端的横纵坐标。

  • color:线段的颜色,通常是一个包含三个元素的元组 (B, G, R) 表示BGR色彩空间的像素值,也可以是灰度图像的一个整数值。

  • thickness:线段的宽度,默认值是1,如果设置为负数,则线宽会被填充。

示例:

import cv2 as cv
import numpy as np

img = cv.imread('./images/lvbo.png')
# cv.imshow('img', img)

# 统计彩色图中蓝色通道中的所有像素中0-255出现的频率
hist = cv.calcHist(img, [0], None, [256], [0, 256])
# print(hist, hist.shape)
minval, maxval, mini, maxi = cv.minMaxLoc(hist)
print(maxval)
# print(minval, maxval, mini, maxi)
# 初始化一个图像,用于绘制直方图
backg = np.zeros([256, 256, 3], np.uint8)
# 指定直方图最大高度
hmax = int(0.9 * 256)
# 遍历像素个数
for h in range(256):
    # 高度归一化hmax的高度上
    h_g = int(hist[h].item() * hmax / maxval)
    cv.line(backg, (h, 256), (h, 256 - h_g), (255, 0, 0), 1)

cv.imshow('hist', backg)
cv.waitKey(0)
cv.destroyAllWindows()

4.3 直方图均衡化

        一副效果好的图像通常在直方图上的分布比较均匀,直方图均衡化就是用来改善图像的全局亮度和对比度。
        如果一幅图像整体很亮,那所有的像素值的取值个数应该都会很高,所以应该把它的直方图做一个横向拉伸,就可以扩大图像像素值的分布范围,提高图像的对比度
        通俗的讲,就是遍历图像的像素统计出灰度值的个数、比例与累计比例,并重新映射到0-255范围(也可以是其他范围)内,其实从观感上就可以发现,下面两幅图中前面那幅图对比度不高,偏灰白。

        可以看到均衡化后图片的亮度和对比度效果明显好于原图。

4.3.1 自适应直方图均衡化

        自适应直方图均衡化(Adaptive Histogram Equalization, AHE),通过调整图像像素值的分布,使得图像的对比度和亮度得到改善。具体过程如下所示:

        假设有一个3*3的图像,其灰度图的像素值如上图所示,现在我们要对其进行直方图均衡化,首先就是统计其每个像素值的个数、比例以及其累计比例。如下图所示。

        接下来我们就要进行计算,就是将要缩放的范围(通常是缩放到0-255,所以就是255-0)乘以累计比例,得到新的像素值,并将新的像素值放到对应的位置上,比如像素值为50的像素点,将其累计比例乘以255,也就是0.33乘以255得到84.15,取整后得到84,并将84放在原图像中像素值为50的地方,像素值为100、210、255的计算过程类似,最终会得到如下图所示的结果,这样就完成了最基本的直方图均衡化的过程。

        dst = cv.equalizeHist(imgGray):imgGray为需要直方图均衡化的灰度图返回值为处理后的图像。
        示例:

import cv2 as cv

img = cv.imread('images/zhifang.png')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 均衡化
img_n = cv.equalizeHist(gray)

cv.imshow('gray', gray)
cv.imshow('img_n', img_n)
cv.waitKey(0)
cv.destroyAllWindows()
cv.waitKey(0)

        该方法适用于图像的灰度分布不均匀,且灰度分布集中在更窄的范围,图像的细节不够清晰且对比度较低的情况,然而,传统的直方图均衡化方法会引入噪声,并导致图像中出现过度增强的区域。这是因为直方图均衡化方法没有考虑到图像的局部特征和全局对比度的差异。

4.3.2 对比度受限的自适应直方图均衡化

        很明显,因为全局调整亮度和对比度的原因,脸部太亮,大部分细节都丢失了。自适应均衡化就是用来解决这一问题的:它在每一个小区域内(默认8×8)进行直方图均衡化。当然,如果有噪点的话,噪点会被放大,需要对小区域内的对比度进行了限制,所以这个算法全称叫:对比度受限的自适应直方图均衡化(Contrast Limited Adaptive Histogram Equalization, CLAHE)。

        其主要步骤为:

  1. 图像分块(Tiling):图像首先被划分为多个不重叠的小块(tiles)。这样做的目的是因为在全局直方图均衡化中,单一的直方图无法反映图像各个局部区域的差异性。通过局部处理,AHE能够更好地适应图像内部的不同光照和对比度特性。(tiles 的 大小默认是 8x8)

  2. 计算子区域直方图:对于每个小块,独立计算其内部像素的灰度直方图。直方图反映了该区域内像素值的分布情况。

  3. 子区域直方图均衡化:对每个小块的直方图执行直方图均衡化操作。这涉及重新分配像素值,以便在整个小块内更均匀地分布。均衡化过程会增加低频像素的数量,减少高频像素的数量,从而提高整个小块的对比度。

  4. 对比度限制(Contrast Limiting):如果有噪声的话,噪声会被放大。为了防止过大的对比度增强导致噪声放大,出现了限制对比度自适应直方图均衡化(CLAHE)。CLAHE会在直方图均衡化过程中引入一个对比度限制参数。当某一小块的直方图在均衡化后出现极端值时,会对直方图进行平滑处理(使用线性或非线性的钳制函数),确保对比度增强在一个合理的范围内。

  5. 重采样和邻域像素融合:由于小块之间是不重叠的,直接拼接经过均衡化处理的小块会产生明显的边界效应。因此,在CLAHE中通常采用重采样技术来消除这种效应,比如通过双线性插值将相邻小块的均衡化结果进行平滑过渡,使最终图像看起来更为自然和平滑。

  6. 合成输出图像:将所有小块均衡化后的结果整合在一起,得到最终的自适应直方图均衡化后的图像。

        clahe = cv2.createCLAHE(clipLimit=None, tileGridSize=None)

  • clipLimit(可选):对比度限制参数,用于控制直方图均衡化过程中对比度增强的程度。如果设置一个大于1的值(如2.0或4.0),CLAHE会限制对比度增强的最大程度,避免过度放大噪声。如果不设置,OpenCV会使用一个默认值。

  • tileGridSize(可选):图像分块的大小,通常是一个包含两个整数的元组,如(8, 8),表示将图像划分成8x8的小块进行独立的直方图均衡化处理。分块大小的选择会影响到CLAHE的效果以及处理速度。

创建CLAHE对象后,可以使用 .apply() 方法对图像进行CLAHE处理:

img=clahe.apply(image)

  • image:要均衡化的图像。

  • img均衡后的图像

示例:

import cv2 as cv

img = cv.imread('images/zhifang.png', cv.IMREAD_GRAYSCALE)
cv.imshow('img', img)

img_e = cv.equalizeHist(img)  # 自适应,全局亮度调整
cv.imshow('img_e', img_e)

clahe = cv.createCLAHE(2.0, (8, 8))  # 受限的,局部:8x8
# 调用apply方法,返回受限的自适应直方图均衡化的图像
img_l = clahe.apply(img)
cv.imshow('img_l', img_l)

cv.waitKey(0)
cv.destroyAllWindows()

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

多模态学习(八):2022 TPAMI——U2Fusion: A Unified Unsupervised Image Fusion Network

论文链接&#xff1a;https://ieeexplore.ieee.org/stamp/stamp.jsp?tp&arnumber9151265 目录 一.摘要 1.1 摘要翻译 1.2 摘要解析 二.Introduction 2.1 Introduciton翻译 2.2 Introduction 解析 三. related work 3.1 related work翻译 3.2 relate work解析 四…

JavaEE-0403学习记录

通过前期准备后&#xff0c;项目已经能够成功运行&#xff1a; 1、在文件UserMapper.java中添加如下代码&#xff1a; List<User> selectUSerByIdDynamic(User user); 2、在文件UserMapper.xml中添加如下代码&#xff1a; <select id"selectUSerByIdDynamic&quo…

图像处理:使用Numpy和OpenCV实现傅里叶和逆傅里叶变换

文章目录 1、什么是傅里叶变换及其基础理论 1.1 傅里叶变换 1.2 基础理论 2. Numpy 实现傅里叶和逆傅里叶变换 2.1 Numpy 实现傅里叶变换 2.2 实现逆傅里叶变换 2.3 高通滤波示例 3. OpenCV 实现傅里叶变换和逆傅里叶变换及低通滤波示例 3.1 OpenCV 实现傅里叶变换 3.2 实现逆傅…

RNN模型与NLP应用——(7/9)机器翻译与Seq2Seq模型

声明&#xff1a; 本文基于哔站博主【Shusenwang】的视频课程【RNN模型及NLP应用】&#xff0c;结合自身的理解所作&#xff0c;旨在帮助大家了解学习NLP自然语言处理基础知识。配合着视频课程学习效果更佳。 材料来源&#xff1a;【Shusenwang】的视频课程【RNN模型及NLP应用…

使用YoloV5和Mediapipe实现——上课玩手机检测(附完整源码)

目录 效果展示 应用场景举例 1. 课堂或考试监控&#xff08;看到这个学生党还会爱我吗&#xff09; 2. 驾驶安全监控&#xff08;防止开车玩手机&#xff09; 3. 企业办公管理&#xff08;防止工作时间玩手机&#xff09; 4. 监狱、戒毒所、特殊场所安保 5. 家长监管&am…

XT-912在热交换站的应用

热网监控需求 随着国民经济的不断进步和人民生活水平日益提高&#xff0c;社会对环境的要求越来越高。近年来国家大力提倡城镇集中供热&#xff0c;改变原来各单位、各片区自己供热、单独建立锅炉房给城市带来的污染&#xff0c;由城市外围的一个或者多个热源厂提供热源&#…

语文常识推翻百年“R完备、封闭”论

​语文常识推翻百年“R完备、封闭”论 黄小宁 李四光&#xff1a;迷信权威等于扼杀智慧。语文常识表明从西方传进来的数学存在重大错误&#xff1a;将无穷多各异数轴误为同一轴。 复平面z各点z的对应点zk的全体是zk平面。z面平移变换为zk&#xff08;k是非1正实常数&#xf…

基于Docker容器部署DeepSeek-R1-Distill-Qwen-7B

首先打开魔搭社区&#xff0c;然后搜索DeepSeek-R1-Distill-Qwen-7B&#xff0c;进入详情页 官方推荐使用vllm来启动&#xff0c;但是手动搭建vllm环境容易出各种问题&#xff0c;我们这里直接找一个vllm的Docker镜像 一、拉取镜像 docker pull vllm/vllm-openai 如果拉取不…

UART双向通信实现(序列机)

前言 UART&#xff08;通用异步收发传输器&#xff09;是一种串行通信协议&#xff0c;用于在电子设备之间进行数据传输。RS232是UART协议的一种常见实现标准&#xff0c;广泛应用于计算机和外围设备之间的通信。它定义了串行数据的传输格式和电气特性&#xff0c;以确…

CentOS 7 全流程部署Magic-PDF数据清洗工具(附GPU加速方案)

CentOS 7 全流程部署Magic-PDF数据清洗工具&#xff08;附GPU加速方案&#xff09; 一、环境准备与方案选型 1.1 硬件要求 配置项最低要求推荐配置CPU4核8核内存8GB16GB存储50GBSSD/NVMeGPU可选NVIDIA T4 1.2 系统环境检查 # 查看系统版本 cat /etc/redhat-release# 检查G…

LabVIEW多线程

在 LabVIEW 中&#xff0c;多线程编程是提升程序执行效率的关键手段&#xff0c;尤其是在需要并行处理数据采集、控制执行和用户界面交互的场景下。LabVIEW 本身是基于数据流&#xff08;Dataflow&#xff09;的编程语言&#xff0c;天然支持多线程&#xff0c;但要高效利用多线…

ctfshow _萌新 萌新_密码篇

萌新_密码1 先对密文进行 Hex 解码&#xff0c;得到了 S1lkZjBhM2ViZDVjNGRjMTYwLUV7ZmI2M2VlMDI5OGI4ZjRkOH0 再进行 base64 解码&#xff0c;得到了 KYdf0a3ebd5c4dc160-E{fb63ee0298b8f4d8} 再进行栅栏解码&#xff0c;得到了 flag KEY{dffb06a33eeeb0d259c84bd8cf146d08…

蓝桥杯2024省赛PythonB组——日期问题

题目链接: https://www.lanqiao.cn/problems/103/learning/?page1&first_category_id1&name%E6%97%A5%E6%9C%9F%E9%97%AE%E9%A2%98 题目内容&#xff1a; 解题思路 import os import sys# 请在此输入您的代码 from datetime import datetime date_str input().str…

带头结点 的单链表插入方法(头插法与尾插法)

带头结点的单链表插入方法&#xff08;头插法与尾插法&#xff09; 在单链表的操作中&#xff0c;插入是最常见的操作之一&#xff0c;本文介绍 带头结点的单链表 如何实现 后插法 和 前插法&#xff08;包括 插入法 和 后插数据交换法&#xff09;&#xff0c;并提供完整的 C …

Opencv之dilib库:表情识别

一、简介 在计算机视觉领域&#xff0c;表情识别是一个既有趣又具有挑战性的任务。它在人机交互、情感分析、安防监控等众多领域都有着广泛的应用前景。本文将详细介绍如何使用 Python 中的 OpenCV 库和 Dlib 库来实现一个简单的实时表情识别系统。 二、实现原理 表情识别系统…

基于web的生产过程执行管理系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 随着世界经济信息化、全球化的到来和电子商务的飞速发展&#xff0c;推动了很多行业的改革。若想达到安全&#xff0c;快捷的目的&#xff0c;就需要拥有信息化的组织和管理模式&#xff0c;建立一套合理、畅通、高效的线上管理系统。当前的生产过程执行管理存在管理效率…

C++:继承+菱形虚拟继承的一箭双雕

目录 一、继承概念与定义 1.1、什么是继承&#xff1f; 1.2、继承定义 二、继承关系与访问限定符 2.1、继承方式 三、基类与派生类对象的赋值转换 3.1、向上转型 3.2、对象切片 四、继承中的作用域 4.1、隐藏 五、派生类中的成员函数 5.1、构造与析构 六、继承与友…

网络:华为数通HCIA学习:静态路由基础

文章目录 前言静态路由基础静态路由应用场景 静态路由配置静态路由在串行网络的配置静态路由在以太网中的配置 负载分担配置验证 路由备份&#xff08;浮动静态路由&#xff09;配置验证 缺省路由配置验证 总结 华为HCIA 基础实验&#xff0d;静态路由 & eNSP静态路由 基础…

CFResNet鸟类识别:原网络基础上改进算法

​本文为为&#x1f517;365天深度学习训练营内部文章 原作者&#xff1a;K同学啊​ 先放一张ResNet50模型的鸟类识别结果图 一 ResNetSE-NetBN import matplotlib.pyplot as plt import tensorflow as tf import warnings as w w.filterwarnings(ignore) # 支持中文 plt.rcP…

Ubuntu 20.04 出现问号图标且无法联网 修复

在 Ubuntu 中遇到网络连接问题&#xff08;如出现问号图标且无法联网&#xff09;&#xff0c;可以通过以下命令尝试重启网络服务&#xff1a; 1. 推荐先修改DNS 编辑 -> 虚拟机网络编辑器-> VMnet8 ->NAT 设置 -> DNS 设置 -> 设置DNS 服务器 DNS填什么 取决…