目录

一、图像色彩空间转换

1、颜色加法

1.1OpenCV 加法 (cv2.add())

1.2NumPy 加法 (+ 运算符)

2、颜色加权加法(cv2.addWeighted)

二、颜色转换

1、RGB转Gray(灰度)

2、 RGB转HSV

三、灰度化

1、灰度图

2、 最大值法

3、平均值法

4、加权均值法

5、极端的灰度值

四、图像二值化处理

全局阈值法

1、阈值法(THRESH_BINARY)

2、反阈值法(THRESH_BINARY_INV)

4、低阈值零处理(THRESH_TOZERO)

5、超阈值零处理(THRESH_TOZERO_INV)

6、OTSU阈值法

阈值法(THRESH_OTSU)

反阈值法(THRESH_INV_OTSU)

7、自适应二值化

1. 取均值

2. 加权求和

五、图像翻转(图像镜像旋转)

六、图像仿射变换

1、图像旋转

2、 图像平移

3、 图像缩放

4、 图像剪切


一、图像色彩空间转换

OpenCV中,图像色彩空间转换是一个非常基础且重要的操作,就是将图像从一种颜色表示形式转换为另一种表示形式的过程。通过将图像从一个色彩空间转换到另一个色彩空间,可以更好地进行特定类型的图像处理和分析任务。常见的颜色空间包括RGB、HSV、YUV等。

  • 色彩空间转换的作用

    • 提高图像处理效果

    • 节省计算资源

RGB颜色空间

在图像处理中,最常见的就是RGB颜色空间。RGB颜色空间是我们接触最多的颜色空间,是一种用于表示和显示彩色图像的一种颜色模型。RGB代表红色(Red)、绿色(Green)和蓝色(Blue),这三种颜色通过不同强度的光的组合来创建其他颜色,广泛应用于我们的生活中,比如电视、电脑显示屏以及上面实验中所介绍的RGB彩色图。

RGB颜色模型基于笛卡尔坐标系,如下图所示,RGB原色值位于3个角上,二次色青色、红色和黄色位于另外三个角上,黑色位于原点处,白色位于离原点最远的角上。因为黑色在RGB三通道中表现为(0,0,0),所以映射到这里就是原点;而白色是(255,255,255),所以映射到这里就是三个坐标为最大值的点。

RGB颜色空间可以产生大约1600万种颜色,几乎包括了世界上的所有颜色,也就是说可以使用RGB颜色空间来生成任意一种颜色。

注意:在OpenCV中,颜色是以BGR的方式进行存储的,而不是RGB,这也是上面红色的像素值是(0,0,255)而不是(255,0,0)的原因。

1、颜色加法

OpenCV加法和Numpy加法之间存在差异。OpenCV的加法是饱和操作,而Numpy添加是模运算。

你可以使用OpenCV的cv.add()函数把两幅图像相加,或者可以简单地通过numpy操作添加两个图像,如res = img1 + img2。两个图像应该具有相同的大小和类型。

1.1OpenCV 加法 (cv2.add())

  • 饱和运算 (Saturation Operation)

    • 当像素值相加超过 255(8位图像的最大值)时,OpenCV 会将其截断到 255(即“饱和”)

a = np.uint8([200])
b = np.uint8([100])
cv2.add(a, b)  # 200 + 100 = 300 → 255(饱和)

1.2NumPy 加法 (+ 运算符)

  • 模运算 (Modulo Operation)

    • NumPy 直接相加,如果超过 255,会回绕到 0(类似 (a + b) % 256)。

a = np.uint8([200])
b = np.uint8([100])
cv2.add(a, b)  # 200 + 100 = 300 → 255(饱和)

2、颜色加权加法(cv2.addWeighted)

在图像处理中,加权加法(Weighted Addition) 是一种常见的颜色混合技术,它允许我们按照不同的比例混合两幅图像或颜色。OpenCV 提供了 cv2.addWeighted() 函数来实现这一功能。

cv2.addWeighted(src1,alpha,src2,deta,gamma)
  • src1src2:输入图像。

  • alphabeta:两张图象权重。

  • gamma:亮度调整值。

    • gamma > 0,图像会变亮。

    • gamma < 0,图像会变暗。

    • gamma = 0,则没有额外的亮度调整。

示例:

import cv2  as cv
import numpy as np
cao = cv.imread("./images/cao.png")
cao = cv.resize(cao,(520,520))
pig = cv.imread("./images/pig.png")
pig = cv.resize(pig,(520,520))
#饱和操作 cvv.add(img1,img2) np.uint8 范围0-255 250+10=255
dst1= cv.add(cao,pig)
cv.imshow("dst1",dst1)
​
#numpy直接相加 取模运算 对256取模 250+10=4
dst2=cao+pig
cv.imshow("dst2",dst2)
​
x=np.uint8([[250]])
y=np.uint8([[10]])
xy1=cv.add(x,y) #255
xy2=x+y
print(xy1)
print(xy2)
​
#颜色加权加法 cv.addWeighted(img1,alpha,img2,beta,gamma)
dst3=cv.addWeighted(pig,0.7,cao,0.3,0)
cv.imshow("dst3",dst3)
​
​
cv.waitKey(0)
cv.destroyAllWindows()

运行结果:

二、颜色转换

HSV颜色空间指的是HSV颜色模型,这是一种与RGB颜色模型并列的颜色空间表示法。RGB颜色模型使用红、绿、蓝三原色的强度来表示颜色,是一种加色法模型,即颜色的混合是添加三原色的强度。而HSV颜色空间使用色调(Hue)、饱和度(Saturation)和亮度(Value)三个参数来表示颜色,色调H表示颜色的种类,如红色、绿色、蓝色等;饱和度表示颜色的纯度或强度,如红色越纯,饱和度就越高;亮度表示颜色的明暗程度,如黑色比白色亮度低。

HSV颜色模型是一种六角锥体模型,如下图所示:

色调H:

使用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。它们的补色是:黄色为60°,青色为180°,紫色为300°。通过改变H的值,可以选择不同的颜色

饱和度S:

饱和度S表示颜色接近光谱色的程度。一种颜色可以看成是某种光谱色与白色混合的结果。其中光谱色所占的比例越大,颜色接近光谱色的程度就越高,颜色的饱和度就越高。饱和度越高,颜色就越深而艳,光谱色的白光成分为0,饱和度达到最高。通常取值范围为0%~100%,其中0%表示灰色或无色,100%表示纯色,通过调整饱和度的值,可以使颜色变得更加鲜艳或者更加灰暗。

明度V:

明度表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射比或反射比有关。通常取值范围为0%(黑)到100%(白),通过调整明度的值,可以使颜色变得更亮或者更暗。

一般对颜色空间的图像进行有效处理都是在HSV空间进行的,然后对于基本色中对应的HSV分量需要给定一个严格的范围,下面是通过实验计算的模糊范围(准确的范围在网上都没有给出)。

H: 0— 180

S: 0— 255

V: 0— 255

此处把部分红色归为紫色范围:

为什么有了RGB颜色空间我们还是需要转换成HSV颜色空间来进行图像处理呢?

  • 符合人类对颜色的感知方式:人类对颜色的感知是基于色调、饱和度和亮度三个维度的,而HSV颜色空间恰好就是通过这三个维度来描述颜色的。因此,使用HSV空间处理图像可以更直观地调整颜色和进行色彩平衡等操作,更符合人类的感知习惯。

  • 颜色调整更加直观:在HSV颜色空间中,色调、饱和度和亮度的调整都是直观的,而在RGB颜色空间中调整颜色不那么直观。例如,在RGB空间中要调整红色系的颜色,需要同时调整R、G、B三个通道的数值,而在HSV空间中只需要调整色调和饱和度即可。

  • 降维处理有利于计算:在图像处理中,降维处理可以减少计算的复杂性和计算量。HSV颜色空间相对于RGB颜色空间,减少了两个维度(红、绿、蓝),这有利于进行一些计算和处理任务,比如色彩分割、匹配等。

因此,在进行图片颜色识别时,我们会将RGB图像转换到HSV颜色空间,然后根据颜色区间来识别目标颜色。

1、RGB转Gray(灰度)

cv2.cvtColor是OpenCV中的一个函数,用于图像颜色空间的转换。可以将一个图像从一个颜色空间转换为另一个颜色空间,比如从RGB到灰度图,或者从RGB到HSV的转换等。

  • cv2.cvtColor(img,code)
    • img:输入图像,可以是一个Numpy数组绘着一个OpenCV的Mat对象

      • Mat 是一个核心的数据结构,主要用于存储图像和矩阵数据。在 Python 中使用 OpenCV 时,通常直接处理的是 NumPy 数组,cv2 模块自动将 Mat 对象转换为 NumPy 数组。二者之间的转换是透明且自动完成的。例如,当你使用 cv2.imread() 函数读取图像时,返回的是一个 NumPy 数组,但在C++中则是 Mat 对象。

    • code:指定转换的类型,可以使用预定义的转换代码。

      • 例如cv2.COLOR_RGB2GRAY表示从rgb到灰度图像的转换。

import cv2 as cv
img=cv.imread("./images/1.jpg")
img=cv.resize(img,(400,400))
#颜色转换RGB转Gray cv.cvColor(img,code)
gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
cv.imshow("gray",gray)

2、 RGB转HSV

HSV(Hue-Saturation-Value)是一种更符合人类视觉感知的颜色空间模型,与RGB相比能更直观地描述颜色特性:

  • H(色相/Hue):表示颜色类型,范围0-180(OpenCV中)或0-360(一般理论)

  • S(饱和度/Saturation):表示颜色纯度,范围0-255

  • V(明度/Value):表示颜色亮度,范围0-255

import cv2
import numpy as np
​
# 读取BGR图像(注意OpenCV默认是BGR顺序)
bgr_img = cv2.imread('image.jpg')
​
# 转换为HSV
hsv_img = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2HSV)
​
# 显示结果
cv2.imshow('BGR', bgr_img)
cv2.imshow('HSV', hsv_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

三、灰度化

将彩色图像转换为灰度图像的过程称为灰度化,这种做法在图像处理和计算机视觉领域非常常见。

灰度图与彩色图最大的不同就是:彩色图是由R、G、B三个通道组成,而灰度图只有一个通道,也称为单通道图像,所以彩色图转成灰度图的过程本质上就是将R、G、B三通道合并成一个通道的过程。本实验中一共介绍了三种合并方法,分别是最大值法、平均值法以及加权均值法。

1、灰度图

每个像素只有一个采样颜色的图像,这类图像通常显示为从最暗黑色到最亮的白色的灰度,尽管理论上这个采样可以任何颜色的不同深浅,甚至可以是不同亮度上的不同颜色。灰度图像与黑白图像不同,在计算机图像领域中黑白图像只有黑色与白色两种颜色;但是,灰度图像在黑色与白色之间还有许多级的颜色深度。灰度图像经常是在单个电磁波频谱如可见光内测量每个像素的亮度得到的,用于显示的灰度图像通常用每个采样像素8位的非线性尺度来保存,这样可以有256级灰度。

2、 最大值法

对于彩色图像的每个像素,它会从R、G、B三个通道的值中选出最大的一个,并将其作为灰度图像中对应位置的像素值。

例如某图像中某像素点的像素值如上图所示,那么在使用最大值法进行灰度化时,就会从该像素点对应的RGB通道中选取最大的像素值作为灰度值,所以在灰度图中的对应位置上,该像素点的像素值就是121。

  • pig[i,j,0] 表示 蓝色通道(B) 的值

  • pig[i,j,1] 表示 绿色通道(G) 的值

  • pig[i,j,2] 表示 红色通道(R) 的值

import cv2 as cv
import numpy as np  
#读取图像
pig = cv.imread("./images/pig.png")
shape = pig.shape #(h,w,c)
img=np.zeros((shape[0],shape[1]),dtype=np.uint8)
#循环遍历每一行 img[0,0,0]
for i in range(shape[0]):
    for j in range(shape[1]):
        img[i,j] = max(pig[i,j,0],pig[i,j,1],pig[i,j,2])
​
cv.imshow("img",img)
cv.waitKey(0)
cv.destroyAllWindows()

运行结果:

3、平均值法

对于彩色图像的每个像素,它会将R、G、B三个通道的像素值全部加起来,然后再除以三,得到的平均值就是灰度图像中对应位置的像素值。

例如某图像中某像素点的像素值如上图所示,那么在使用平均值进行灰度化时,其计算结果就是(91+121+46)/3=86(对结果进行取整),所以在灰度图中的对应位置上,该像素点的像素值就是86。

示例:

import cv2 as cv
import numpy as np  
#读取图像
pig = cv.imread("./images/pig.png")
shape = pig.shape #(h,w,c)
img=np.zeros((shape[0],shape[1]),dtype=np.uint8)
#循环遍历每一行 img[0,0,0]
for i in range(shape[0]):
    for j in range(shape[1]):
        #int():转为更大的数据类型,防止溢出
        img[i,j] = (int(pig[i,j,0])+int(pig[i,j,1])+int(pig[i,j,2]))//3
​
cv.imshow("img",img)
cv.waitKey(0)
cv.destroyAllWindows()

4、加权均值法

对于彩色图像的每个像素,它会按照一定的权重去乘以每个通道的像素值,并将其相加,得到最后的值就是灰度图像中对应位置的像素值。本实验中,权重的比例为: R乘以0.299,G乘以0.587,B乘以0.114,这是经过大量实验得到的一个权重比例,也是一个比较常用的权重比例。

所使用的权重之和应该等于1。这是为了确保生成的灰度图像素值保持在合理的亮度范围内,并且不会因为权重的比例不当导致整体过亮或过暗。

例如某图像中某像素点的像素值如上图所示,那么在使用加权平均值进行灰度化时,其计算结果就是10*0.299+121*0.587+46*0.114=79。所以在灰度图中的对应位置上,该像素点的像素值就是79。

数学表达式

灰度值计算公式为:

text

Gray = Wᵣ × R + Wg × G + Wb × B

其中:

  • Wᵣ、Wg、Wb 分别代表红、绿、蓝通道的权重系数

  • R、G、B 代表各通道的像素值

  • 通常 Wᵣ + Wg + Wb = 1(归一化)

示例:

import cv2 as cv
import numpy as np  
#读取图像
pig = cv.imread("./images/pig.png")
shape = pig.shape #(h,w,c)
img=np.zeros((shape[0],shape[1]),dtype=np.uint8)
#加权均值法 定义权重
wb,wg,wr=0.299,0.587,0.114
#循环遍历每一行 img[0,0,0]
for i in range(shape[0]):
    for j in range(shape[1]):
        img[i,j]=wb*pig[i,j,0]+wg*pig[i,j,1]+wr*pig[i,j,2]
      
​
cv.imshow("img",img)
cv.waitKey(0)
cv.destroyAllWindows()

5、极端的灰度值

在灰度图像中,“极端”的灰度值指的是亮度的两个极端:最暗和最亮的值。

  • 最暗的灰度值:0。这代表完全黑色,在灰度图像中没有任何亮度。

  • 最亮的灰度值:255。这代表完全白色,在灰度图像中具有最大亮度。

四、图像二值化处理

将某张图像的所有像素改成只有两种值之一。

二值图像

一幅二值图像的二维矩阵仅由0、1两个值构成,“0”代表黑色,“1”代白色。由于每一像素(矩阵中每一元素)取值仅有0、1两种可能,所以计算机中二值图像的数据类型通常为1个二进制位。二值图像通常用于文字、线条图的扫描识别(OCR)和掩膜图像的存储。

其操作的图像也必须是灰度图。也就是说,二值化的过程,就是将一张灰度图上的像素根据某种规则修改为0和maxval(maxval表示最大值,一般为255,显示白色)两种像素值,使图像呈现黑白的效果,能够帮助我们更好地分析图像中的形状、边缘和轮廓等特征。

  • 简便:降低计算量和计算需求,加快处理速度。

  • 节约资源:二值图像占用空间远小于彩色图。

  • 边缘检测:二值化常作为边缘检测的预处理步骤,因为简化后的图易于识别出轮廓和边界。

全局阈值法

全局阈值法是图像二值化处理中最基础且重要的方法,它通过设定一个全局的阈值将灰度图像转换为黑白二值图像。

  • _,binary = cv2.threshold(img,thresh,maxval,type)
    • img:输入图像,要进行二值化处理的灰度图。

    • thresh:设定的阈值。当像素值大于(或小于,取决于阈值类型)thresh时,该像素被赋予的值。

    • type:阈值处理的类型。

    • 返回值:

      • 第一个值(通常用下划线表示):计算出的阈值,若使用自适应阈值法,会根据算法自动计算出这个值。

      • 第二个值(binary):二值化后的图像矩阵。与输入图像尺寸相同。

示例:

import cv2 as cv
​
flower = cv.imread("./images/flower.png")
gray = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)
gray = cv.resize(gray, (360, 360))
cv.imshow("gray", gray)
​
# 全局阈值法
thresh, binary = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
cv.imshow("binary", binary)
​
cv.waitKey(0)
cv.destroyAllWindows()

1、阈值法(THRESH_BINARY)

阈值法就是通过设置一个阈值,将灰度图中的每一个像素值与该阈值进行比较,小于等于阈值的像素就被设置为0(通常代表背景),大于阈值的像素就被设置为maxval(通常代表前景)。对于我们的8位图像(0~255)来说,通常是设置为255。

示例:

import cv2 as cv
​
flower = cv.imread("./images/flower.png")
gray = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)
gray = cv.resize(gray, (360, 360))
cv.imshow("gray", gray)
​
# 全局阈值法
thresh, binary = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
cv.imshow("binary", binary)
​
cv.waitKey(0)
cv.destroyAllWindows()

如在灰度图中像素值较高的地方,如花瓣、花茎等地方的像素值比阈值高,那么在生成的二值化图中的对应位置的像素值就会被设置为255,也就是纯白色。

cv2.IMREAD_GRAYSCALE:OpenCV 中用于指定图像加载模式的一个标志,它告诉 cv.imread() 函数以灰度格式读取图像。

2、反阈值法(THRESH_BINARY_INV)

顾名思义,就是与阈值法相反。反阈值法是当灰度图的像素值大于阈值时,该像素值将会变成0(黑),当灰度图的像素值小于等于阈值时,该像素值将会变成maxval。

示例:

import cv2 as cv
​
flower = cv.imread("./images/flower.png")
gray = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)
gray = cv.resize(gray, (360, 360))
cv.imshow("gray", gray)
#反阈值法
_, binary_inv = cv.threshold(gray, 127, 255, cv.THRESH_BINARY_INV)
cv.imshow("binary_inv", binary_inv)
cv.waitKey(0)
cv.destroyAllWindows()

3、截断阈值法(THRESH_TRUNC)

截断阈值法,指将灰度图中的所有像素与阈值进行比较,像素值大于阈值的部分将会被修改为阈值,小于等于阈值的部分不变。

换句话说,经过截断阈值法处理过的二值化图中的最大像素值就是阈值。

示例:

import cv2 as cv
​
flower = cv.imread("./images/flower.png")
gray = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)
gray = cv.resize(gray, (360, 360))
cv.imshow("gray", gray)
_, trunc = cv.threshold(gray, 170, 255, cv.THRESH_TRUNC)
cv.imshow("trunc", trunc)
cv.waitKey(0)
cv.destroyAllWindows()

运行结果:

当截断阈值为255时,如上图所示,可以看到灰度图与二值化图没有任何的区别。

4、低阈值零处理(THRESH_TOZERO)

低阈值零处理,字面意思,就是像素值小于等于阈值的部分被置为0(也就是黑色),大于阈值的部分不变。

示例:

import cv2 as cv
​
flower = cv.imread("./images/flower.png")
gray = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)
gray = cv.resize(gray, (360, 360))
cv.imshow("gray", gray)
_, zero = cv.threshold(gray, 127, 255, cv.THRESH_TOZERO)
cv.imshow("zero", zero)
cv.waitKey(0)
cv.destroyAllWindows()

运行结果:

5、超阈值零处理(THRESH_TOZERO_INV)

超阈值零处理就是将灰度图中的每个像素与阈值进行比较,像素值大于阈值的部分置为0(也就是黑色),像素值小于等于阈值的部分不变。

示例:

import cv2 as cv
​
flower = cv.imread("./images/flower.png")
gray = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)
gray = cv.resize(gray, (360, 360))
cv.imshow("gray", gray)
_, zero_inv = cv.threshold(gray, 127, 255, cv.THRESH_TOZERO_INV)
cv.imshow("zero_inv", zero_inv)
cv.waitKey(0)
cv.destroyAllWindows()

如上图所示,在灰度图中较亮的部分,其像素值比阈值大,所以在二值化后其像素值会被置为0(也就是黑色),对应二值化图中的黑色部分。而灰度图中较暗的部分,也就是像素值较低的地方,由于像素值比阈值小,将不会发生改变。

以上介绍的二值化方法都需要手动设置阈值,但是在不同的环境下,摄像头拍摄的图像可能存在差异,导致手动设置的阈值并不适用于所有图像,这可能会导致二值化效果不理想。

因此,我们需要一种能自动计算每张图片阈值的二值化方法,能够根据每张图像的特点自动计算出适合该图像的二值化阈值,从而达到更好的二值化效果。这种二值化方法可以在不同环境下适用,提高图像处理的准确性和鲁棒性。

6、OTSU阈值法

cv2.THRESH_OTS 并不是一个有效的阈值类型或标。THRESH_OTSU 本身并不是一个独立的阈值化方法,而是与 OpenCV 中的二值化方法结合使用的一个标志。具体来说,THRESH_OTSU 通常与 THRESH_BINARYTHRESH_BINARY_INV 结合使用。在实际应用中,如果你使用 THRESH_OTSU 标志但没有指定其他二值化类型,默认情况下它会与 THRESH_BINARY 结合使用。也就是说,当你仅指定了 cv2.THRESH_OTSU,实际上等同于同时指定了 cv2.THRESH_BINARY + cv2.THRESH_OTSU

在介绍OTSU阈值法之前,我们首先要了解一下双峰图片的概念。

双峰图片就是指灰度图的直方图上有两个峰值,直方图就是对灰度图中每个像素值的点的个数的统计图,如下图所示。

  • 灰度图直方图的基础概念

  1. 灰度级

    • 在灰度图像中,每个像素的值代表其亮度,通常范围是 0 到 255(对于 8 位灰度图像)。

    • 0 表示黑色,255 表示白色,中间的值表示不同程度的灰色。

  2. 直方图定义

    • 直方图是一个柱状图,其中 x 轴表示灰度级(从 0 到 255),y 轴表示对应灰度级在图像中出现的次数(频率)。

    • 每个柱子的高度代表该灰度级在图像中出现的像素数量。

OTSU算法是通过一个值将这张图分前景色和背景色(也就是灰度图中小于这个值的是一类,大于这个值的是一类。例如,如果你设置阈值为128,则所有大于128的像素点可以被视作前景,而小于等于128的像素点则被视为背景。),通过统计学方法(最大类间方差)来验证该值的合理性,当根据该值进行分割时,使用最大类间方差计算得到的值最大时,该值就是二值化算法中所需要的阈值。通常该值是从灰度图中的最小值加1开始进行迭代计算,直到灰度图中的最大像素值减1,然后把得到的最大类间方差值进行比较,来得到二值化的阈值。以下是一些符号规定:

T:阈值

N_{0}:前景像素点数

N_{1}:背景像素点数

\omega_{0}:前景的像素点数占整幅图像的比例

\omega_{1}:背景的像素点数占整幅图像的比例

\mathcal{U_{0}}:前景的平均像素值

\mathcal{U_{1}}:背景的平均像素值

\mathcal{U}:整幅图的平均像素值

rows×cols:图像的行数和列数

下面举个例子,有一张大小为4×4的图片,假设阈值T为1,那么:

也就是这张图片根据阈值1分为了前景(像素为2的部分)和背景(像素为0)的部分,并且计算出了OTSU算法所需要的各个数据,根据上面的数据,我们给出计算类间方差的公式:

$$
g=\omega_{0}(\mu_{0}-\mu)^{2}+\omega_{1}(\mu_{1}-\mu)^{2}
$$

g就是前景与背景两类之间的方差,这个值越大,说明前景和背景的差别就越大,效果就越好。OTSU算法就是在灰度图的像素值范围内遍历阈值T,使得g最大,基本上双峰图片的阈值T在两峰之间的谷底。

通过OTSU算法得到阈值之后,就可以结合上面的方法根据该阈值进行二值化,在本实验中有THRESH_OTSU和THRESH_INV_OTSU两种方法,就是在计算出阈值后结合了阈值法和反阈值法。

注意:使用OTSU算法计算阈值时,组件中的thresh参数将不再有任何作用。

阈值法(THRESH_OTSU)

import cv2 as cv
​
flower = cv.imread("./images/flower.png")
gray = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)
gray = cv.resize(gray, (360, 360))
cv.imshow("gray", gray)
# OTSU 自动阈值
thresh1, otsu = cv.threshold(gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU)
cv.imshow("otsu", otsu)
cv.waitKey(0)
cv.destroyAllWindows()

反阈值法(THRESH_INV_OTSU)

import cv2 as cv
​
flower = cv.imread("./images/flower.png")
gray = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)
gray = cv.resize(gray, (360, 360))
cv.imshow("gray", gray)
# OTSU 反阈值
thresh2, otsu_inv = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV + cv.THRESH_OTSU)
cv.imshow("otsu_inv", otsu_inv)
cv.waitKey(0)
cv.destroyAllWindows()

7、自适应二值化

与二值化算法相比,自适应二值化更加适合用在明暗分布不均的图片,因为图片的明暗不均,导致图片上的每一小部分都要使用不同的阈值进行二值化处理,这时候传统的二值化算法就无法满足我们的需求了,于是就出现了自适应二值化。

自适应二值化方法会对图像中的所有像素点计算其各自的阈值,这样能够更好的保留图片里的一些信息。自适应二值化组件内容如下图所示:

cv2.adaptiveThreshold(image_np_gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 7, 10)

其中各个参数的含义如下:

maxval:最大阈值,一般为255

adaptiveMethod:小区域阈值的计算方式:

ADAPTIVE_THRESH_MEAN_C:小区域内取均值

ADAPTIVE_THRESH_GAUSSIAN_C:小区域内加权求和,权重是个高斯核

thresholdType:二值化方法,只能使用THRESH_BINARY、THRESH_BINARY_INV,也就是阈值法和反阈值法

blockSize:选取的小区域的面积,如7就是7*7的小块。

c:最终阈值等于小区域计算出的阈值再减去此值

下面介绍一下这两种方法。

1. 取均值

比如一张图片的左上角像素值如下图所示:

假如我们使用的小区域是3*3的,那么就会从图片的左上角开始(也就是像素值为162的地方)计算其邻域内的平均值,如果处于边缘地区就会对边界进行填充,填充值就是边界的像素点,如下图所示:

那么对于左上角像素值为162的这个点,161(也就是上图中括号内的计算结果,结果会进行取整)就是根据平均值计算出来的阈值,接着减去一个固定值C,得到的结果就是左上角这个点的二值化阈值了,接着根据选取的是阈值法还是反阈值法进行二值化操作。紧接着,向右滑动计算每个点的邻域内的平均值,直到计算出右下角的点的阈值为止。我们所用到的不断滑动的小区域被称之为核,比如3*3的小区域叫做3*3的核,并且核的大小都是奇数个,也就是3*3、5*5、7*7等。

自适应二值化(Adaptive Thresholding)的核心思想就是为图像中的每个像素点计算一个局部阈值。这种方法与全局阈值化不同,后者对整个图像使用同一个固定的阈值。而在自适应二值化中,每个像素的阈值是基于其周围邻域内的像素值动态确定的。

import cv2 as cv
​
flower = cv.imread("./images/flower.png")
gray = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)
gray = cv.resize(gray, (360, 360))
cv.imshow("gray", gray)
auto_mean = cv.adaptiveThreshold(
    gray, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 7, 10
)
cv.imshow("auto_mean", auto_mean)
cv.waitKey(0)
cv.destroyAllWindows()

运行结果:

2. 加权求和

对小区域内的像素进行加权求和得到新的阈值,其权重值来自于高斯分布。

高斯分布,通过概率密度函数来定义高斯分布,一维高斯概率分布函数为:

$$
p(y)={\frac{1}{\sigma{\sqrt{2\pi}}}}e^{{\frac{-(y-\mu)^{2}}{2\sigma^{2}}}}
$$

通过改变函数中和的值,我们可以得到如下图像,其中均值为\mu,标准差为\sigma^{2}。

此时我们拓展到二维图像,一般情况下我们使x轴和y轴的相等并且,此时我们可以得到二维高斯函数的表达式为:

$$
g(x,y)=\frac{1}{2\pi\sigma ^{2}}e^{-\frac{(x^{2}+y^{2})}{2\sigma^{2}}}
$$

高斯概率函数是相对于二维坐标产生的,其中(x,y)为点坐标,要得到一个高斯滤波器模板,应先对高斯函数进行离散化,将得到的值作为模板的系数。例如:要产生一个3*3的高斯权重核,以核的中心位置为坐标原点进行取样,其周围的坐标如下图所示(x轴水平向右,y轴竖直向上)

将坐标带入上面的公式中,即可得到一个高斯权重核。

而在opencv里,当kernel(小区域)的尺寸为1、3、5、7并且用户没有设置sigma的时候(sigma <= 0),核值就会取固定的系数,这是一种默认的值是高斯函数的近似。

kernel尺寸 核值
1 [1]
3 [0.25, 0.5, 0.25]
5 [0.0625, 0.25, 0.375, 0.25, 0.0625]
7 [0.03125, 0.109375, 0.21875, 0.28125, 0.21875, 0.109375, 0.03125]

比如kernel的尺寸为3*3时,使用

$$
\left[\begin{array}{c}{{0.25}}\\ {{0.5}}\\ {{0.25}}\end{array}\right]\times\left[0.25~~~~0.5~~~~0.25\right]
$$

进行矩阵的乘法,就会得到如下的权重值,其他的类似。

$$
kernel=\left[\begin{array}{c}{{0.0625~~~0.125~~~0.0625}}\\{{0.125~~~~0.25~~~~0.125}}\\ {{0.0625~~~0.125~~~0.0625}} \end{array}\right]
$$

通过这个高斯核,即可对图片中的每个像素去计算其阈值,并将该阈值减去固定值得到最终阈值,然后根据二值化规则进行二值化。

而当kernels尺寸超过7的时候,如果sigma设置合法(用户设置了sigma),则按照高斯公式计算.当sigma不合法(用户没有设置sigma),则按照如下公式计算sigma的值:

$$
\sigma=0.3*\big((k s i z e-1)*0.5-1\big)+0.8
$$

某像素点的阈值计算过程如下图所示:

首先还是对边界进行填充,然后计算原图中的左上角(也就是162像素值的位置)的二值化阈值,其计算过程如上图所示,再然后根据选择的二值化方法对左上角的像素点进行二值化,之后核向右继续计算第二个像素点的阈值,第三个像素点的阈值…直到右下角(也就是155像素值的位置)为止。

当核的大小不同时,仅仅是核的参数会发生变化,计算过程与此是一样的。

cv2.adaptiveThreshold参数解释:
​
1. image_np_gray: 输入图像,这里必须是灰度图像(单通道)。
​
2. 255: 输出图像的最大值。在二值化后,超过自适应阈值的像素会被设置为该最大值,通常为255表示白色;未超过阈值的像素将被设置为0,表示黑色。
​
3. cv2.ADAPTIVE_THRESH_GAUSSIAN_C: 自适应阈值类型。在这个例子中,使用的是高斯加权的累计分布函数(CDF),并添加一个常数 C 来计算阈值。另一种可选类型是 cv2.ADAPTIVE_THRESH_MEAN_C,它使用邻域内的平均值加上常数 C 计算阈值。
​
4. cv2.THRESH_BINARY: 输出图像的类型。这意味着输出图像将会是一个二值图像(binary image),其中每个像素要么是0要么是最大值(在这里是255)。另外还有其他选项如 cv2.THRESH_BINARY_INV 会得到相反的二值图像。
​
5. 7: blockSize 参数,表示计算每个像素阈值时所考虑的7x7邻域大小(正方形区域的宽度和高度),其值必须是奇数。
​
6. 10: C 参数,即上面提到的常数值,在计算自适应阈值时与平均值或高斯加权值相加。正值增加阈值,负值降低阈值,具体效果取决于应用场景。

五、图像翻转(图像镜像旋转)

在OpenCV中,图片的镜像旋转是以图像的中心为原点进行镜像翻转的。

  • cv2.flip(img,flipcode)

  • 参数

    • img: 要翻转的图像

    • flipcode: 指定翻转类型的标志

      • flipcode=0: 垂直翻转,图片像素点沿x轴翻转

      • flipcode>0: 水平翻转,图片像素点沿y轴翻转

      • flipcode<0: 水平垂直翻转,水平翻转和垂直翻转的结合

示例:

import cv2  as cv
face = cv.imread("./images/face.png")
cv.imshow("face",face)
#翻转 镜像旋转 以图像中心为原点cv.flip(img,flipCode)
#flipCode = 0 垂直翻转 沿x轴 上下翻转
flip_0=cv.flip(face,0)
cv.imshow("flip_0",flip_0)
#fipCode = 1 水平翻转 沿y轴 左右翻转
flip_1=cv.flip(face,1)
cv.imshow("flip_1",flip_1)
#flipCode = -1 垂直水平翻转 旋转180度
flip_2=cv.flip(face,-1)
cv.imshow("flip_2",flip_2)  
cv.waitKey(0)
cv.destroyAllWindows()

六、图像仿射变换

仿射变换(Affine Transformation)是一种线性变换,保持了点之间的相对距离不变。

  • 仿射变换的基本性质

    • 保持直线

    • 保持平行

    • 比例不变性

    • 不保持角度和长度

  • 常见的仿射变换类型

    • 旋转:绕着某个点或轴旋转一定角度。

    • 平移:仅改变物体的位置,不改变其形状和大小。

    • 缩放:改变物体的大小。

    • 剪切:使物体发生倾斜变形。

  • 仿射变换的基本原理

    • 线性变换

    • 二维空间中,图像点坐标为(x,y),仿射变换的目标是将这些点映射到新的位置 (x', y')。

    • 为了实现这种映射,通常会使用一个矩阵乘法的形式:

      (类似于y=kx+b)

      • a,b,c,d 是线性变换部分的系数,控制旋转、缩放和剪切。

      • t_x,t_y 是平移部分的系数,控制图像在平面上的移动。

      • 输入点的坐标被扩展为齐次坐标形式[x,y,1],以便能够同时处理线性变换和平移

  • cv2.warpAffine()函数

    • 仿射变换函数

      cv2.warpAffine(img,M,dsize)
      • img:输入图像。

      • M:2x3的变换矩阵,类型为np.float32

      • dsize:输出图像的尺寸,形式为(width,height)

1、图像旋转

旋转图像可以将图像绕着某个点旋转一定的角度。

cv2.getRotationMatrix2D()函数

  • 获取旋转矩阵

    cv2.getRotationMatrix2D(center,angle,scale)
    • center:旋转中心点的坐标,格式为(x,y)

    • angle:旋转角度,单位为度,正值表示逆时针旋转负值表示顺时针旋转。

    • scale:缩放比例,若设为1,则不缩放。

    • 返回值M,2x3的旋转矩阵。

import cv2 as cv
#读图
cat=cv.imread("./images/1.jpg")
cat=cv.resize(cat,(520,520))
cv.imshow("cat",cat)
#获取旋转矩阵 cvv2.getRotationMatrix2D(center,angle,scale) 2x3
M=cv.getRotationMatrix2D((260,260),-45,1)
#反射变化函数 cv2.warpAffine(img,M,dsize) dsize = (width,height)
dst = cv.warpAffine(cat,M,(520,520))
cv.imshow("dst",dst)
cv.waitKey(0)
cv.destroyAllWindows()

2、 图像平移

移操作可以将图像中的每个点沿着某个方向移动一定的距离。

  • 假设我们有一个点 P(x,y),希望将其沿x轴方向平移t_x*个单位,沿y轴方向平移t_y个单位到新的位置P′(x′,y′),那么平移公式如下:

    x′=x+tx

    y′=y+ty

    在矩阵形式下,该变换可以表示为:

  • 这里的t_x和t_y分别代表在x轴和y轴上的

示例:

import cv2 as cv
import numpy as np
cat=cv.imread("./images/1.jpg")
cat=cv.resize(cat,(520,520))
cv.imshow("cat",cat)
#定义平移动量
tx=80
ty=120
#定义平移矩阵
M=np.float32([[1,0,tx],[0,1,ty]])
#仿射变换
dst = cv.warpAffine(cat,M,(520,520))
cv.imshow("dst",dst)
cv.waitKey(0)
cv.destroyAllWindows()

3、 图像缩放

缩放操作可以改变图片的大小。

  • 假设要把图像的宽高分别缩放为0.5和0.8,那么对应的缩放因子sx=0.5,sy=0.8。

  • 点P(x,y)对应到新的位置P'(x',y'),缩放公式为:

    x′=s_x*x

    y′=s_y*y

在矩阵形式下,该变换可以表示为:

相较于图像旋转中只能等比例的缩放,图像缩放更加灵活,可以在指定方向上进行缩放。sx和sy分别表示在x轴和y轴方向上的缩放因子。

示例:

import cv2 as cv
import numpy as np
cat=cv.imread("./images/1.jpg")
cat=cv.resize(cat,(520,520))
cv.imshow("cat",cat)
#定义缩放量
sx=0.6
sy=0.5
#定义缩放矩阵
M=np.float32([[sx,0,0],[0,sy,0]])
#仿射变换
dst = cv.warpAffine(cat,M,(520,520))
cv.imshow("dst",dst)
cv.waitKey(0)
cv.destroyAllWindows()

4、 图像剪切

剪切操作可以改变图形的形状,以便其在某个方向上倾斜,它将对象的形状改变为斜边平行四边形,而不改变其面积

  • 想象我们手上有一张矩形纸片,如果你固定纸片的一边,并沿着另一边施加一个平行于该边的力,这张纸片就会变形为一个平行四边形。这就是剪切变换的一个直观解释。

  • 对于二维空间中的点P(x,y),对他进行剪切变换:

    沿x轴剪切:x'=x+sh_y*y y'=y

    沿y轴剪切:x'=x y'=sh_x*x+y

  • 当需要同时沿两个方向进行剪切时,x'=x+sh_y*y , y'=sh_x*x+y

  • 在矩阵形式下,该变换可以表示为:

  • 来一个图理解一下:

示例:

shy和shx分别对应沿x轴和y轴方向上的剪切因子。

import cv2 as cv
import numpy as np
cat=cv.imread("./images/1.jpg")
cat=cv.resize(cat,(520,520))
cv.imshow("cat",cat)
#定义剪切因子
shx=0.5
shy=0.5
#定义剪切矩阵
M=np.float32([[1,0,0],[0,1,0]])
#剪切
height, width = cat.shape[:2]
#确定剪切后的图像尺寸
new_width = int(width + abs(shy * height))
new_height = int(height + abs(shx * width))
dst = cv.warpAffine(cat,M,(new_width,new_height))
dst = cv.resize(dst,(520,520))
cv.imshow("dst",dst)
cv.waitKey(0)
cv.destroyAllWindows()
  • 可以理解为,x不变,y偏移

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐