文章目录
- 前言
- 一、 图像颜色处理
- 1. 颜色加法
- 1.1 OpenCV加法
- 1.2 numpy加法
- 1.3 颜色加权加法
- 2.颜色空间
- 2.1 RGB颜色空间
- 2.2 HSV颜色空间
- 3. 颜色转换
- 3.1 读取的图片同时转换
- 3.2 对已有图片转换
- 4. 图像灰度化
- 4.1 灰度图概念
- 4.2 最大值灰度化
- 4.3 平均值灰度化
- 4.4 加权均值灰度化
- 5. 图像二值化处理
- 5.1 二值图像
- 5.2 阈值法(THRESH_BINARY)
- 5.3 反阈值法(THRESH_BINARY_INV)
- 5.4 截断阈值法(THRESH_TRUNC)
- 5.5 低阈值零处理(THRESH_TOZERO)
- 5.6 超阈值零处理(THRESH_TOZERO_INV)
- 5.7 OTSU阈值法
- 5.8 自适应二值化
- 5.8.1 取均值
- 5.8.2 加权求和(高斯法)
- 二、图像仿射变换
- 1. 图像翻转(图像镜像旋转)
- 2.仿射变换
- 2.1 仿射变换的基本原理 -- 单点旋转
- 2.2 仿射变换函数
- 总结
- --- opencv系列相关文章 ---
前言
昨天我们初步了解了opencv-python,学习了利用cv2的相关API对已知路径下的图片进行文件基本操作,最后还完成了对视频的操作。今天我们要学习图像颜色处理及图像仿射变换。
一、 图像颜色处理
1. 颜色加法
1.1 OpenCV加法
- opencv加法是
饱和操作
- 饱和的意思是若计算的结果超出当前上限,就以当前上限值作为最终结果。
- 在opencv中,颜色值默认是np.unit8类型,上限为255
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 读取图像cao = cv.imread('../images/cao.png')pig = cv.imread('../images/pig.png')# 饱和操作 -- 范围是np.uint8, 超出上限的取255dst1 = cv.add(cao, pig)print(cao[500,500,:])print(pig[500,500,:])print('cv饱和运算\n',dst1[500,500,:])cv.imshow('dst1', dst1)# 设定显示等待cv.waitKey(0)# 释放内存cv.destroyAllWindows()
1.2 numpy加法
- 利用numpy加法的前提是进行加法运算的两张图片的形状大小要一样
- numpy加法是模运算
- 取模意思是用计算结果对255取模
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 读取图像cao = cv.imread('../images/cao.png')pig = cv.imread('../images/pig.png')# 饱和操作 -- 范围是np.uint8, 超出上限的取255dst2 = cao + pigprint(cao[500,500,:])print(pig[500,500,:])print('numpy取模运算\n',dst2[500,500,:])cv.imshow('dst1', dst2)# 设定显示等待cv.waitKey(0)# 释放内存cv.destroyAllWindows()
1.3 颜色加权加法
同样满足饱和运算
基本格式:
cv2.addWeighted(src1,alpha,src2,deta,gamma)
参数说明:
src1
、src2
:输入图像。alpha
、beta
:两张图象权重。gamma
:亮度调整值。gamma > 0
,图像会变亮。gamma < 0
,图像会变暗。gamma = 0
,则没有额外的亮度调整。
示例
import cv2 as cv
# import numpy as np
if __name__ == '__main__':# 读图cao = cv.imread('../images/cao.png')pig = cv.imread('../images/pig.png')# cv2.addWeighted(src1,alpha,src2,deta,gamma) dst3 = cv.addWeighted(cao,0.3,pig,0.7,100)print(cao[500,500,:])print(pig[500,500,:])print('cv加权运算\n',dst3[500,500,:])# 显示图片cv.imshow('dst3',dst3)cv.waitKey(0)# 释放内存cv.destroyAllWindows()
2.颜色空间
2.1 RGB颜色空间
在图像处理中,最常见的就是RGB颜色空间。RGB颜色空间是我们接触最多的颜色空间,是一种用于表示和显示彩色图像的一种颜色模型。RGB
代表红色(Red)
、绿色(Green)
和蓝色(Blue)
,这三种颜色通过不同强度的光的组合来创建其他颜色,广泛应用于我们的生活中,比如电视、电脑显示屏以及上面实验中所介绍的RGB彩色图。
RGB颜色模型基于笛卡尔坐标系,如下图所示,RGB原色值位于3个角上,二次色青色、红色和黄色位于另外三个角上,黑色位于原点处,白色位于离原点最远的角上。因为黑色在RGB三通道中表现为(0,0,0),所以映射到这里就是原点;而白色是(255,255,255),所以映射到这里就是三个坐标为最大值的点。
RGB颜色空间可以产生大约1600万(2553255^32553)种颜色,几乎包括了世界上的所有颜色,也就是说可以使用RGB颜色空间来生成任意一种颜色。
注意:
在OpenCV中,颜色是以BGR
的方式进行存储的,而不是RGB,这也是上面红色的像素值是(0,0,255)而不是(255,0,0)的原因。
2.2 HSV颜色空间
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颜色空间,然后根据颜色区间来识别目标颜色。
3. 颜色转换
3.1 读取的图片同时转换
基本格式:
cv2.imread(path [,读取方式])
参数说明:
昨天我们讲了path,而读取方式有很多种,默认是BGR形式
常见 mode 参数对照表:
模式值(flag) | 常量名 | 功能说明 |
---|---|---|
1 | cv2.IMREAD_COLOR (默认) | 以彩色方式读取图像,忽略透明通道 |
0 | cv2.IMREAD_GRAYSCALE | 以灰度模式读取图像(单通道) |
-1 | cv2.IMREAD_UNCHANGED | 以原始方式读取图像,保留 alpha 通道 |
2 | cv2.IMREAD_ANYDEPTH | 若图像是 16/32 位深度,将按原深度读取 |
4 | cv2.IMREAD_ANYCOLOR | 以任意颜色格式读取(不强制转换 BGR) |
8 | cv2.IMREAD_IGNORE_ORIENTATION | 忽略图像的 EXIF 方向元数据 |
示例
import cv2 as cv
# import numpy as np
if __name__ == '__main__':# 读取图像为灰度图cat = cv.imread('../images/1.jpg',cv.IMREAD_GRAYSCALE)# 显示图像cv.imshow('cat', cat)cv.waitKey(0)cv.destroyAllWindows()
3.2 对已有图片转换
基本格式:
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
# import numpy as np
if __name__ == '__main__':# 读取图像cat = cv.imread('../images/1.jpg')# 颜色转换 cv2.cvtColor(img, code)# 转灰度gray_ = cv.cvtColor(cat, cv.COLOR_BGR2GRAY)# 转HSVhsv = cv.cvtColor(cat, cv.COLOR_BGR2HSV)# 显示图片cv.imshow('cat', cat)cv.imshow('gray',gray_)cv.imshow('hsv',hsv)cv.waitKey(0)cv.destroyAllWindows()
4. 图像灰度化
4.1 灰度图概念
每个像素只有一个采样颜色的图像,这类图像通常显示为从最暗的黑色到最亮的白色
的灰度
,尽管理论上这个采样可以任何颜色的不同深浅,甚至可以是不同亮度上的不同颜色。灰度图像与黑白图像不同,在计算机图像领域中黑白图像只有黑色与白色两种颜色;但是,灰度图像在黑色与白色之间还有许多级的颜色深度。灰度图像经常是在单个电磁波频谱如可见光内测量每个像素的亮度得到的,用于显示的灰度图像通常用每个采样像素8位的非线性尺度来保存,这样可以有256级灰度。
原图 | 灰度图 |
---|---|
![]() | ![]() |
4.2 最大值灰度化
对于彩色图像的每个像素,它会从R、G、B三个通道的值中选出最大的一个,并将其作为灰度图像中对应位置的像素值。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 读取图像img_0 = cv.imread('../images/pig.png')shape = img_0.shape # (h, w, c)# 创建一个新图用于存放灰度像素img_1 = []# 最大值法(选取三通道中的最大值)for i in img_0:for j in i:img_1.append(max(j))img_2 = np.array(img_1).reshape(shape[0:2])cv.imshow('old',img_0)cv.imshow('gray', img_2)cv.waitKey(0)cv.destroyAllWindows()
4.3 平均值灰度化
对于彩色图像的每个像素,它会将R、G、B三个通道的像素值全部加起来,然后再除以三,得到的平均值就是灰度图像中对应位置的像素值。
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 读取图像img_0 = cv.imread('../images/1.jpg')shape = img_0.shape # (h, w, c)# 创建一个新图用于存放灰度像素img_1 = []# 平均值法for i in img_0:for j in i:tmp = np.mean(j).astype(np.uint8)img_1.append(tmp) img_2 = np.array(img_1).reshape(shape[0:2])cv.imshow('old',img_0)cv.imshow('gray', img_2)cv.waitKey(0)cv.destroyAllWindows()
4.4 加权均值灰度化
对于彩色图像的每个像素,它会按照一定的权重去乘以每个通道的像素值,并将其相加,得到最后的值就是灰度图像中对应位置的像素值。本实验中,权重的比例为: R乘以0.299
,G乘以0.587
,B乘以0.114
,这是经过大量实验得到的一个权重比例,也是一个比较常用的权重比例
。
所使用的权重之和应该等于1。这是为了确保生成的灰度图像素值保持在合理的亮度范围内,并且不会因为权重的比例不当导致整体过亮或过暗。
opencv中的灰度化默认使用的加权均值
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 读取图像img_0 = cv.imread('../images/1.jpg')shape = img_0.shape # (h, w, c)# 创建一个新图用于存放灰度像素img_1 = []# 权重weights = np.array([1/4,1/2,1/4])# 加权均值法for i in img_0:for j in i:tmp = np.average(j,weights=weights).astype(np.uint8)img_1.append(tmp)img_2 = np.array(img_1).reshape(shape[0:2])cv.imshow('old',img_0)cv.imshow('gray', img_2)cv.waitKey(0)cv.destroyAllWindows()
5. 图像二值化处理
将某张图像的所有像素改成只有两种值之一。
5.1 二值图像
一幅二值图像的二维矩阵仅由0、1两个值构成,“0”代表黑色,“1”代白色。由于每一像素(矩阵中每一元素)取值仅有0、1两种可能,所以计算机中二值图像的数据类型通常为1个二进制位。二值图像通常用于文字、线条图的扫描识别(OCR)和掩膜图像的存储。
其操作的图像也必须是灰度图。也就是说,二值化的过程,就是将一张灰度图上的像素根据某种规则修改为0和maxval(maxval表示最大值,一般为255,显示白色)两种像素值,使图像呈现黑白的效果,能够帮助我们更好地分析图像中的形状、边缘和轮廓等特征。
-
特点和功能:
- 简便:降低计算量和计算需求,加快处理速度。
- 节约资源:二值图像占用空间远小于彩色图。
- 边缘检测:二值化常作为边缘检测的预处理步骤,因为简化后的图易于识别出轮廓和边界。
全局阈值法基本格式:
_,binary = cv2.threshold(img,thresh,maxval,type)
参数说明:
img
:输入图像,要进行二值化处理的灰度图。thresh
:设定的阈值。当像素值大于(或小于,取决于阈值类型)thresh
时,该像素被赋予的值。type
:阈值处理的类型。- 返回值:
- 第一个值(通常用下划线表示):计算出的阈值,若使用自适应阈值法,会根据算法自动计算出这个值。
- 第二个值(binary):二值化后的图像矩阵。与输入图像尺寸相同。
5.2 阈值法(THRESH_BINARY)
阈值法就是通过设置一个阈值,将灰度图中的每一个像素值与该阈值进行比较,小于等于阈值
的像素就被设置为0(通常代表背景)
,大于阈值
的像素就被设置为maxval(通常代表前景
)。对于我们的8位图像(0~255)来说,通常是设置为255。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 读取图像flower = cv.imread('../images/flower.png')# 调整图片大小flower = cv.resize(flower,(360, 360))# 灰度化处理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# 二值化 阈值法thresh, binary = cv.threshold(gray_, 127, 255, cv.THRESH_BINARY)print(thresh)# 显示效果cv.imshow('binary', binary)cv.waitKey(0)cv.destroyAllWindows()
5.3 反阈值法(THRESH_BINARY_INV)
顾名思义,就是与阈值法相反
。反阈值法是当灰度图的像素值大于阈值时,该像素值将会变成0(黑),当灰度图的像素值小于等于阈值时,该像素值将会变成maxval。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 读取图像flower = cv.imread('../images/flower.png')# 调整图片大小flower = cv.resize(flower,(360, 360))# 灰度化处理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# 二值化 反阈值法thresh, binary_inv = cv.threshold(gray_, 127, 255, cv.THRESH_BINARY_INV)print(thresh)# 显示效果cv.imshow('binary_inv', binary_inv)cv.waitKey(0)cv.destroyAllWindows()
5.4 截断阈值法(THRESH_TRUNC)
截断阈值法,指将灰度图中的所有像素与阈值进行比较,像素值大于阈值的部分将会被修改为阈值,小于等于阈值的部分不变。
经过截断阈值法
处理过的二值化图中的最大像素值就是阈值
,此时参数maxval不起作用
。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 读取图像flower = cv.imread('../images/flower.png')# 调整图片大小flower = cv.resize(flower,(360, 360))# 灰度化处理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# 二值化 截断阈值法_, trunc = cv.threshold(gray_,127,255, cv.THRESH_TRUNC)# 显示效果cv.imshow('trunc', trunc)cv.waitKey(0)cv.destroyAllWindows()
5.5 低阈值零处理(THRESH_TOZERO)
低阈值零处理,字面意思,就是像素值小于等于阈值的部分被置为0(也就是黑色),大于阈值的部分不变。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 读取图像flower = cv.imread('../images/flower.png')# 调整图片大小flower = cv.resize(flower,(360, 360))# 灰度化处理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# 低阈值零处理_, tozero = cv.threshold(gray_,127,255, cv.THRESH_TOZERO)# 显示效果cv.imshow('tozero', tozero)cv.waitKey(0)cv.destroyAllWindows()
5.6 超阈值零处理(THRESH_TOZERO_INV)
超阈值零处理就是将灰度图中的每个像素与阈值进行比较,像素值大于阈值的部分置为0(也就是黑色),像素值小于等于阈值的部分不变。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 读取图像flower = cv.imread('../images/flower.png')# 调整图片大小flower = cv.resize(flower,(360, 360))# 灰度化处理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# 超阈值零处理_, tozero_inv = cv.threshold(gray_,127,255, cv.THRESH_TOZERO_INV)# 显示效果cv.imshow('tozero_inv', tozero_inv)cv.waitKey(0)cv.destroyAllWindows()
5.7 OTSU阈值法
cv2.THRESH_OTS 并不是一个有效的阈值类型或标。THRESH_OTSU
本身并不是一个独立的阈值化方法,而是与 OpenCV 中的二值化方法结合使用的一个标志。具体来说,THRESH_OTSU
通常与 THRESH_BINARY
或 THRESH_BINARY_INV
结合使用。在实际应用中,如果你使用 THRESH_OTSU
标志但没有指定其他二值化类型,默认情况下它会与 THRESH_BINARY
结合使用。也就是说,当你仅指定了 cv2.THRESH_OTSU
,实际上等同于同时指定了 cv2.THRESH_BINARY + cv2.THRESH_OTSU
。
在介绍OTSU阈值法之前,我们首先要了解一下双峰图片的概念。
双峰图片就是指灰度图的直方图上有两个峰值,直方图就是对灰度图中每个像素值的点的个数的统计图,如下图所示。
- 灰度图直方图的基础概念
- 灰度级:
- 在灰度图像中,每个像素的值代表其亮度,通常范围是 0 到 255(对于 8 位灰度图像)。
- 0 表示黑色,255 表示白色,中间的值表示不同程度的灰色。
- 直方图定义:
- 直方图是一个柱状图,其中 x 轴表示灰度级(从 0 到 255),y 轴表示对应灰度级在图像中出现的次数(频率)。
- 每个柱子的高度代表该灰度级在图像中出现的像素数量。
OTSU算法是通过一个值将这张图分前景色和背景色(也就是灰度图中小于这个值的是一类,大于这个值的是一类。例如,如果你设置阈值为128,则所有大于128的像素点可以被视作前景,而小于等于128的像素点则被视为背景。),通过统计学方法(最大类间方差)来验证该值的合理性,当根据该值进行分割时,使用最大类间方差计算得到的值最大时,该值就是二值化算法中所需要的阈值。通常该值是从灰度图中的最小值加1开始进行迭代计算,直到灰度图中的最大像素值减1,然后把得到的最大类间方差值进行比较,来得到二值化的阈值。以下是一些符号规定:
T:阈值
N0N_{0}N0:前景像素点数
N1N_{1}N1:背景像素点数
ω0\omega_{0}ω0:前景的像素点数占整幅图像的比例
ω1\omega_{1}ω1:背景的像素点数占整幅图像的比例
U0\mathcal{U_{0}}U0:前景的平均像素值
U1\mathcal{U_{1}}U1:背景的平均像素值
U\mathcal{U}U:整幅图的平均像素值
rows×cols:图像的行数和列数
下面举个例子,有一张大小为4×4的图片,假设阈值T为1,则:
也就是这张图片根据阈值1分为了前景(像素为2的部分)和背景(像素为0)的部分,并且计算出了OTSU算法所需要的各个数据,根据上面的数据,我们给出计算类间方差的公式:
g=ω0(μ0−μ)2+ω1(μ1−μ)2g=\omega_{0}(\mu_{0}-\mu)^{2}+\omega_{1}(\mu_{1}-\mu)^{2} g=ω0(μ0−μ)2+ω1(μ1−μ)2
g就是前景与背景两类之间的方差,这个值越大,说明前景和背景的差别就越大,效果就越好。OTSU算法就是在灰度图的像素值范围内遍历阈值T,使得g最大,基本上双峰图片的阈值T在两峰之间的谷底。
通过OTSU算法得到阈值之后,就可以结合
上面的方法根据该阈值进行二值化,在本实验中有THRESH_OTSU
和THRESH_INV_OTSU
两种方法,就是在计算出阈值后结合了阈值法和反阈值法。
THRESH_OTSU | THRESH_INV_OTSU |
---|---|
![]() | ![]() |
注意:
使用OTSU算法计算阈值时,组件中的thresh参数将不再有任何作用。
THRESH_OTSU
默认是cv2.THRESH_BINARY
+ cv2.THRESH_OTSU
示例
otsu反阈值法
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 读取图像flower = cv.imread('../images/flower.png')# 调整图片大小flower = cv.resize(flower,(360, 360))# 灰度化处理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# OTSU结合反阈值_, otsu_inv = cv.threshold(gray_,127,255, cv.THRESH_OTSU + cv.THRESH_BINARY_INV)# 显示效果cv.imshow('otsu_inv', otsu_inv)cv.waitKey(0)cv.destroyAllWindows()
5.8 自适应二值化
与二值化算法相比,自适应二值化更加适合用在明暗分布不均的图片,因为图片的明暗不均,导致图片上的每一小部分都要使用不同的阈值进行二值化处理,这时候传统的二值化算法就无法满足我们的需求了,于是就出现了自适应二值化。
自适应二值化方法会对图像中的所有像素点计算其各自的阈值,这样能够更好的保留图片里的一些信息。自适应二值化组件内容如下图所示:
基本格式:
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
:最终阈值等于小区域计算出的阈值再减去此值
5.8.1 取均值
假如我们使用的小区域是3*3的,那么就会从图片的左上角开始(也就是像素值为162的地方)计算其邻域内的平均值,如果处于边缘地区就会对边界进行填充,填充值就是边界的像素点,如下图所示:
那么对于左上角像素值为162的这个点,161(也就是上图中括号内的计算结果,结果会进行取整)就是根据平均值计算出来的阈值,接着减去一个固定值C,得到的结果就是左上角这个点的二值化阈值了,接着根据选取的是阈值法还是反阈值法进行二值化操作。紧接着,向右滑动计算每个点的邻域内的平均值,直到计算出右下角的点的阈值为止。我们所用到的不断滑动的小区域被称之为核,比如3*3的小区域叫做3*3的核,并且核的大小都是奇数
个,也就是3*3、5*5、7*7等。(取奇数个是为了能有中心)
自适应二值化(Adaptive Thresholding)的核心思想就是为图像中的每个像素点计算一个局部阈值。这种方法与全局阈值化不同,后者对整个图像使用同一个固定的阈值。而在自适应二值化中,每个像素的阈值是基于其周围邻域内的像素值动态确定的。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 读取图像flower = cv.imread('../images/flower.png')# 调整图片大小flower = cv.resize(flower,(360, 360))# 灰度化处理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# 自适应二值化adaption_mean = cv.adaptiveThreshold(gray_, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY,3,2)# 显示效果cv.imshow('adaption_mean', adaption_mean)cv.waitKey(0)cv.destroyAllWindows()
5.8.2 加权求和(高斯法)
对小区域内的像素进行加权求和得到新的阈值,其权重值来自于高斯分布。
高斯分布,通过概率密度函数来定义高斯分布,一维高斯概率分布函数为:
p(y)=1σ2πe−(y−μ)22σ2p(y)={\frac{1}{\sigma{\sqrt{2\pi}}}}e^{{\frac{-(y-\mu)^{2}}{2\sigma^{2}}}} p(y)=σ2π1e2σ2−(y−μ)2
通过改变函数中和的值,我们可以得到如下图像,其中均值为μ\muμ,标准差为σ2\sigma^{2}σ2。
此时我们拓展到二维图像,一般情况下我们使x轴和y轴的相等并且,此时我们可以得到二维高斯函数的表达式为:
g(x,y)=12πσ2e−(x2+y2)2σ2g(x,y)=\frac{1}{2\pi\sigma ^{2}}e^{-\frac{(x^{2}+y^{2})}{2\sigma^{2}}} g(x,y)=2πσ21e−2σ2(x2+y2)
高斯概率函数是相对于二维坐标产生的,其中(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时,使用
[0.250.50.25]×[0.250.50.25]\left[\begin{array}{c}{{0.25}}\\ {{0.5}}\\ {{0.25}}\end{array}\right]\times\left[0.25~~~~0.5~~~~0.25\right] 0.250.50.25×[0.25 0.5 0.25]
进行矩阵的乘法,就会得到如下的权重值,其他的类似。
kernel=[0.06250.1250.06250.1250.250.1250.06250.1250.0625]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] kernel=0.0625 0.125 0.06250.125 0.25 0.1250.0625 0.125 0.0625
通过这个高斯核,即可对图片中的每个像素去计算其阈值,并将该阈值减去固定值得到最终阈值,然后根据二值化规则进行二值化。
而当kernels尺寸超过7的时候,如果sigma设置合法(用户设置了sigma),则按照高斯公式计算.当sigma不合法(用户没有设置sigma),则按照如下公式计算sigma的值:
σ=0.3∗((ksize−1)∗0.5−1)+0.8\sigma=0.3*\big((k s i z e-1)*0.5-1\big)+0.8 σ=0.3∗((ksize−1)∗0.5−1)+0.8
某像素点的阈值计算过程如下图所示:
首先还是对边界进行填充,然后计算原图中的左上角(也就是162像素值的位置)的二值化阈值,其计算过程如上图所示,再然后根据选择的二值化方法对左上角的像素点进行二值化,之后核向右继续计算第二个像素点的阈值,第三个像素点的阈值…直到右下角(也就是155像素值的位置)为止。
当核的大小不同时,仅仅是核的参数会发生变化,计算过程与此是一样的。
示例
import cv2 as cv
import numpy as np
if __name__ == '__main__':# 读取图像flower = cv.imread('../images/flower.png')# 调整图片大小flower = cv.resize(flower,(360, 360))# 灰度化处理gray_ = cv.cvtColor(flower, cv.COLOR_BGR2GRAY)# 自适应二值化--高斯核gauss = cv.adaptiveThreshold(gray_, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY,3,2)# 显示效果cv.imshow('gauss', gauss)cv.waitKey(0)cv.destroyAllWindows()
二、图像仿射变换
1. 图像翻转(图像镜像旋转)
在OpenCV中,图片的镜像旋转是以图像的中心为原点
进行镜像翻转的。
基本格式:
cv2.flip(img,flipcode
参数说明:
- im.\ 要翻转的图像
- flipcode: 指定翻转类型的标志
- flipcode=0: 垂直翻转,图片像素点沿x轴翻转
- flipcode>0: 水平翻转,图片像素点沿y轴翻转
- flipcode<0: 水平垂直翻转,水平翻转和垂直翻转的结合
示例
import cv2 as cvif __name__ == '__main__':# 读取图片face = cv.imread('../images/face.png')# flipcode = 0 垂直翻转,图片像素点沿x轴翻转flip_0 = cv.flip(face, 0)# flipcode >0 水平翻转,图片像素点沿y轴翻转flip_1 = cv.flip(face, 2)# flipcode <0 水平垂直翻转,水平翻转和垂直翻转的结合flip__1 = cv.flip(face, -1)# 显示效果cv.imshow('face', face)cv.imshow('flip_0', flip_0)cv.imshow('flip_1', flip_1)cv.imshow('flip_-1', flip__1)cv.waitKey(0)cv.destroyAllWindows()
2.仿射变换
仿射变换(Affine Transformation)是一种线性变换,保持了点之间的相对距离不变。
-
仿射变换的基本性质
- 保持直线
- 保持平行
- 比例不变性
- 不保持角度和长度
-
常见的仿射变换类型
- 旋转:绕着某个点或轴旋转一定角度。
- 平移:仅改变物体的位置,不改变其形状和大小。
- 缩放:改变物体的大小。
- 剪切:使物体发生倾斜变形。
2.1 仿射变换的基本原理 – 单点旋转
首先我们以最简单的一个点的旋转为例子,且以最简单的情况举例,令旋转中心为坐标系中心O(0,0)O(0,0)O(0,0),假设有一点P0(x0,y0)P_{0}(x_{0},y_{0})P0(x0,y0),P0P_{0}P0离旋转中心OOO的距离为rrr,OP0OP_{0}OP0与坐标轴x轴的夹角为α\alphaα,P0P_{0}P0绕O顺时针旋转θ\thetaθ角后对应的点为P(x,y)P(x,y)P(x,y)。
那么我们可以得到如下关系:
x0=r×cosαx_{0}=r\times\cos\alpha x0=r×cosα
y0=r×sinαy_{0}=r\times\sin\alpha y0=r×sinα
x=r×cos(α−θ)=rcosαcosθ+rsinαsinθ=x0cosθ+y0sinθx=r\times\cos(\alpha-\theta)=r\cos\alpha\cos\theta+r\sin\alpha\sin\theta=x_{0}\cos\theta+y_{0}\sin\theta x=r×cos(α−θ)=rcosαcosθ+rsinαsinθ=x0cosθ+y0sinθ
y=r×sin(α−θ)=rsinαcosθ−rcosαsinθ=−x0sinθ+y0cosθy=r\times\sin(\alpha-\theta)=r\sin\alpha\cos\theta-r\cos\alpha\sin\theta=-x_{0}\sin\theta+y_{0}\cos\theta y=r×sin(α−θ)=rsinαcosθ−rcosαsinθ=−x0sinθ+y0cosθ
用矩阵来表示就是
[xy]=[cosθsinθ−sinθcosθ]∗[x0y0]\left[\begin{array}{l l}{{x}}\\{{y}}\end{array}\right]=\left[\begin{array}{l l}{{\cos\theta~~~~\sin\theta}}\\{{-\sin\theta~~~~\cos\theta}}\\ \end{array}\right]*\left[\begin{array}{c}{{x_{0}}}\\{{y_{0}}}\end{array}\right] [xy]=[cosθ sinθ−sinθ cosθ]∗[x0y0]
然而,在OpenCV中,旋转时是以图像的左上角为旋转中心,且以逆时针为正方向,因此上面的例子中其实是个负值,那么该矩阵可写为:
[xy]=[cosθ−sinθsinθcosθ]∗[x0y0]\left[\begin{array}{l l}{{x}}\\{{y}}\end{array}\right]=\left[\begin{array}{l l}{{\cos\theta~~~~-\sin\theta}}\\{{\sin\theta~~~~\cos\theta}}\\ \end{array}\right]*\left[\begin{array}{c}{{x_{0}}}\\{{y_{0}}}\end{array}\right] [xy]=[cosθ −sinθsinθ cosθ]∗[x0y0]
其中,
[cosθ−sinθsinθcosθ]\left[\begin{array}{l l}{{\cos\theta~~~~-\sin\theta}}\\{{\sin\theta~~~~\cos\theta}}\\ \end{array}\right] [cosθ −sinθsinθ cosθ]
也被称作旋转矩阵。然而我们所要的不仅仅是可以围绕图像左上角进行旋转,而是可以围绕任意点进行旋转。那么我们可以将其转化成绕原点的旋转,其过程为:
- 首先将旋转点移到原点
- 按照上面的旋转矩阵进行旋转得到新的坐标点
- 再将得到的旋转点移回原来的位置
也就是说,在以任意点为旋转中心时,除了要进行旋转之外,还要进行平移操作。那么当点经过平移后得到P点时,如下图所示:
那么我们就可以得到:
x=x0+txx=x_{0}+t_{x} x=x0+tx
y=y0+tyy=y_{0}+t_{y} y=y0+ty
写成矩阵的形式为:
[xy1]=[10tx01ty001]∗[x0y01]\left[\begin{array}{l l l}{{x}}\\{{y}}\\{1}\end{array}\right]=\left[\begin{array}{c}{{1~~~~0~~~~t_{x}}}\\{{0~~~~1~~~~t_{y}}}\\{{0~~~~0~~~~1}} \end{array}\right]*\left[\begin{array}{c}{{x_0}}\\{{y_0}}\\{1}\end{array}\right] xy1=1 0 tx0 1 ty0 0 1∗x0y01
于是
[10tx01ty001]\left[\begin{array}{l l l}{{1~~~~0~~~~t_{x}}}\\{{0~~~~1~~~~t_{y}}}\\{{0~~~~0~~~~1}} \end{array}\right] 1 0 tx0 1 ty0 0 1
也被叫做平移矩阵,相反的,从P移到点时,其平移矩阵为:
[10−tx01−ty001]\left[\begin{array}{l l l}{1}&{0}&{-\,t_{x}}\\ {0}&{1}&{-\,t_{y}}\\ {0}&{0}&{1}\end{array}\right] 100010−tx−ty1
我们将原始的旋转矩阵也扩展到3*3的形式:
[xy1]=[cosθ−sinθ0sinθcosθ0001]∗[x0y01]\begin{array}{l l l}{{\left[\begin{array}{c}{{x}}\\{{y}}\\{1} \end{array}\right]=\left[\begin{array}{c c c}{{\cos\theta}}&{{-\sin\theta}}&{{0}}\\ {{\sin\theta}}&{{\cos\theta}}&{{0}}\\ {{0}}&{{0}}&{{1}}\end{array}\right]*\left[\begin{array}{c}{{x_{0}}}\\{{y_{0}}}\\{{1}}\end{array}\right]}}\end{array} xy1=cosθsinθ0−sinθcosθ0001∗x0y01
从平移和旋转的矩阵可以看出,3x3矩阵的前2x2部分是和旋转相关的,第三列与平移相关。有了上面的表达式之后,我们就可以得到二维空间中绕任意点旋转的旋转矩阵了,只需要将旋转矩阵先左乘
[10tx01ty001]\left[\begin{array}{l l l}{{1~~~~0~~~~t_{x}}}\\{{0~~~~1~~~~t_{y}}}\\{{0~~~~0~~~~1}} \end{array}\right] 1 0 tx0 1 ty0 0 1
,再右乘
[10−tx01−ty001]\left[\begin{array}{l l l}{1}&{0}&{-\,t_{x}}\\ {0}&{1}&{-\,t_{y}}\\ {0}&{0}&{1}\end{array}\right] 100010−tx−ty1
即可得到最终的矩阵其结果为:( 知道就好!!!)
M=[cosθ−sinθ(1−cosθ)tx+ty∗sinθsinθcosθ(1−cosθ)ty+tx∗sinθ001]M=\left[\begin{array}{l l l}{{\cos\theta~~-\sin\theta~~(1-\cos\theta)t_{x}+t_{y}*\sin\theta}}\\{{\sin\theta~~~\cos\theta~~~~~(1-\cos\theta)t_{y}+t_{x}*\sin\theta}}\\{{0~~~~~~~~~~~~~0~~~~~~~~~~~~~1}} \end{array}\right] M=cosθ −sinθ (1−cosθ)tx+ty∗sinθsinθ cosθ (1−cosθ)ty+tx∗sinθ0 0 1
于是我们就可以根据这个矩阵计算出图像中任意一点绕某点旋转后的坐标了,这个矩阵学名叫做仿射变换矩阵,而仿射变换是一种二维坐标到二维坐标之间的线性变换,也就是只涉及一个平面内二维图形的线性变换,图像旋转就是仿射变换的一种。
仿射变换(Affine Transformation)是一种线性变换,保持了点之间的相对距离不变。
2.2 仿射变换函数
cv2.warpAffine(img,M,dsize)
参数说明:
-
img:输入图像。
-
M:2x3的变换矩阵,类型为
np.float32
。 -
dsize:输出图像的尺寸,形式为
(width,height)
。
示例
- 旋转
import cv2 as cv
import numpy as np# 利用仿射变换矩阵
if __name__ == '__main__':# 读取图片old = cv.imread('../images/1.jpg')shape = old.shape# 获取旋转的仿射矩阵M = cv.getRotationMatrix2D((shape[0]//2, shape[1]//2),-45, 1)# 旋转new_rotation = cv.warpAffine(old, M, (shape[1],shape[0]))# 显示对象cv.imshow('new_1', new_rotation)cv.waitKey(0)cv.destroyAllWindows()
- 平移
import cv2 as cv
import numpy as np# 利用仿射变换矩阵
if __name__ == '__main__':# 读取图片old = cv.imread('../images/1.jpg')shape = old.shape# 获取旋转的仿射矩阵tx = -100ty = -50M = np.float32([[1, 0, tx],[0, 1, ty]])# 平移new_pingyi = cv.warpAffine(old, M, (shape[1],shape[0]))# 显示对象cv.imshow('new_2', new_pingyi)cv.waitKey(0)cv.destroyAllWindows()
- 缩放
import cv2 as cv
import numpy as np# 利用仿射变换矩阵
if __name__ == '__main__':# 读取图片old = cv.imread('../images/1.jpg')shape = old.shape# 获取旋转的仿射矩阵Sx = 0.5 Sy = 1.2M = np.float32([[ 1.5, 0, 0],[ 0, 0.5, 0]])# 缩放new_pingyi = cv.warpAffine(old, M, (shape[1],shape[0]))# 显示对象cv.imshow('new_2', new_pingyi)cv.waitKey(0)cv.destroyAllWindows()
总结
今天我们学习了图像颜色处理及图像仿射变换,其中颜色处理重点掌握二值化的OTSU阈值法和自适应二值化,仿射变换用到了旋转角和矩阵,总的来说知识与线性代数结合很紧密,需要有一定的数学基础。
让我们下期再见!
— opencv系列相关文章 —
opencv–day01–opencv基础知识及基础操作