@TOC
1.数字图像处理基础
1.1 人眼图像的形成
- 光线进入眼睛:当光线从一个物体反射或散射出来,进入人的眼睛时,它们通过角膜和晶状体进入眼球内部。
- 聚焦光线:角膜和晶状体将光线聚焦在视网膜上。晶状体可以通过调整其形状来调节聚焦距离,使物体的图像清晰地映射在视网膜上。
- 光敏细胞感受光线:视网膜是一层包含光敏细胞的组织,分为两种类型的细胞:锥状细胞和杆状细胞。锥状细胞负责颜色和明亮度感知,杆状细胞则负责在低光条件下感知。
- 神经信号传递:光敏细胞受到光线刺激时,会产生神经信号,这些信号随后传递到视神经和大脑。在视神经和视皮层中,这些信号被进一步处理和解释,以形成我们所看到的视觉图像。
1.1 图像数字化
图像数字化是将图像转换为数字信号的过程。数字化图像通常由数字矩阵组成,每个元素表示图像上的一个像素,并用数字表示该像素的颜色和亮度。
数字化图像的过程通常包括以下步骤:
- 采集:采集图像需要使用一种数字化设备,例如数码相机、扫描仪或摄像机。数字化设备将图像转换为数字信号,这些信号可以由计算机处理。
- 采样:采样是将连续图像转换为离散像素的过程。数字化设备将图像分成网格,每个网格称为像素,采集每个像素的颜色和亮度信息,例如:一幅640480分辨率的图像,表示这幅图像是由640480=307200个点组成的。
- 量化:量化是将每个像素的颜色和亮度值转换为数字值的过程。在量化过程中,将连续信号转换为离散信号。量化级别决定了数字图像中可以显示的颜色和亮度的数量。例如:一幅8位的图像,表示每个采样点有=256级,从最暗到最亮,可以分辨为256个级别
- 编码:编码是将数字化的像素值储存为数字格式的过程。编码格式通常包括JPEG、PNG、BMP等
对于一张彩色图片,这张图片的内容是由分辨率(示例:1920x1080)数量级的像素组成。类似于淘宝卖的钉子画,就是由1920乘以1080个钉子组成的画,其中每个钉子的颜色,是由三个通道(RGB)共同组成(三种分别叫R、G、B的钉子组成),
这三个通道像是我们学水彩绘画的中心三颜色(红黄蓝),通过这三种颜色可以调出不同的颜色 也可以理解为,三种通道为三种图层,图层与图层之间组成的颜色。
每一个通道下的每一个钉子的颜色,在计算机视角下就表示为一个0~255的值
既然知道了,在计算机科学的视角下,图片就是数值,那所谓的Opencv图像处理,P图,美颜等等功能,其实就是数值的变化,明白其中的数学公式和逻辑,Opencv的常用算法函数就清晰了。所以本文主要从数学线性代数的角度讲解算法方法。
1.2 图像的种类
按照图像在视觉或设备中的成像效果,可以将图像分为:
- 灰度图:也就是常说的黑白照片,单通道
- 彩色图像:RGB、HSV、YUV、CMYK、Lab
通常我们做特定颜色检测的时候,一般选择用HSV空间的图像,下面一个实例,可以自我调整HSV的数值,来获取在图像中自己想要的颜色。
1.2.1 颜色分割
做颜色特征检测,需要将我们的图片RGB模式转化为HSV模式,H:色彩,S:饱和度,V:明度
下面的代码,我们可以通过调节滑块中HSV的值来观察图片中的颜色变化。
createTrackbar是Opencv中的API,其可在显示图像的窗口中快速创建一个滑动控件,用于手动调节阈值,具有非常直观的效果 cv2.createTrackbar(trackbarName, windowName, value, count, onChange)创建滑动条函数
- trackbarName:滑动空间的名称;
- windowName:滑动空间用于依附的图像窗口的名称;
- value:初始化阈值;
- count:滑动控件的刻度范围;最小值默认为0。
- onChange:回调函数(所谓回调函数即每次修改滑动条后,需要传入新变量的函数)的名称
cv2.getTrackbarPos获取滑动条位置处的值
import cv2
import numpy as np
#定义HSV滑块的值
def empty(a):
h_min = cv2.getTrackbarPos("Hue Min","TrackBars")
h_max = cv2.getTrackbarPos("Hue Max", "TrackBars")
s_min = cv2.getTrackbarPos("Sat Min", "TrackBars")
s_max = cv2.getTrackbarPos("Sat Max", "TrackBars")
v_min = cv2.getTrackbarPos("Val Min", "TrackBars")
v_max = cv2.getTrackbarPos("Val Max", "TrackBars")
print(h_min, h_max, s_min, s_max, v_min, v_max)
return h_min, h_max, s_min, s_max, v_min, v_max
#图片拼接,将4张图片拼接到一起
def stackImages(scale,imgArray):
rows = len(imgArray)
cols = len(imgArray[0])
rowsAvailable = isinstance(imgArray[0], list)
width = imgArray[0][0].shape[1]
height = imgArray[0][0].shape[0]
if rowsAvailable:
for x in range ( 0, rows):
for y in range(0, cols):
if imgArray[x][y].shape[:2] == imgArray[0][0].shape [:2]:
imgArray[x][y] = cv2.resize(imgArray[x][y], (0, 0), None, scale, scale)
else:
imgArray[x][y] = cv2.resize(imgArray[x][y], (imgArray[0][0].shape[1], imgArray[0][0].shape[0]), None, scale, scale)
if len(imgArray[x][y].shape) == 2: imgArray[x][y]= cv2.cvtColor( imgArray[x][y], cv2.COLOR_GRAY2BGR)
imageBlank = np.zeros((height, width, 3), np.uint8)
hor = [imageBlank]*rows
hor_con = [imageBlank]*rows
for x in range(0, rows):
hor[x] = np.hstack(imgArray[x])
ver = np.vstack(hor)
else:
for x in range(0, rows):
if imgArray[x].shape[:2] == imgArray[0].shape[:2]:
imgArray[x] = cv2.resize(imgArray[x], (0, 0), None, scale, scale)
else:
imgArray[x] = cv2.resize(imgArray[x], (imgArray[0].shape[1], imgArray[0].shape[0]), None,scale, scale)
if len(imgArray[x].shape) == 2: imgArray[x] = cv2.cvtColor(imgArray[x], cv2.COLOR_GRAY2BGR)
hor= np.hstack(imgArray)
ver = hor
return ver
path = '1.jpg'
cv2.namedWindow("T
# 创建一个窗口,放置6个滑动条rackBars")
cv2.resizeWindow("TrackBars",640,240)
cv2.createTrackbar("Hue Min","TrackBars",0,179,empty)
cv2.createTrackbar("Hue Max","TrackBars",19,179,empty)
cv2.createTrackbar("Sat Min","TrackBars",110,255,empty)
cv2.createTrackbar("Sat Max","TrackBars",240,255,empty)
cv2.createTrackbar("Val Min","TrackBars",153,255,empty)
cv2.createTrackbar("Val Max","TrackBars",255,255,empty)
while True:
img = cv2.imread(path)
imgHSV = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
# 调用回调函数,获取滑动条的值
h_min = cv2.getTrackbarPos("Hue Min","TrackBars")
h_max = cv2.getTrackbarPos("Hue Max", "TrackBars")
s_min = cv2.getTrackbarPos("Sat Min", "TrackBars")
s_max = cv2.getTrackbarPos("Sat Max", "TrackBars")
v_min = cv2.getTrackbarPos("Val Min", "TrackBars")
v_max = cv2.getTrackbarPos("Val Max", "TrackBars")
lower = np.array([h_min,s_min,v_min])
upper = np.array([h_max,s_max,v_max])
# 获得指定颜色范围内的掩码
mask = cv2.inRange(imgHSV,lower,upper)
# 对原图图像进行按位与的操作,掩码区域保留
imgResult = cv2.bitwise_and(img,img,mask=mask)
# cv2.imshow("Original",img)
# cv2.imshow("HSV",imgHSV)
# cv2.imshow("Mask", mask)
# cv2.imshow("Result", imgResult)
imgStack = stackImages(0.6,([img,imgHSV],[mask,imgResult]))
cv2.imshow("Stacked Images", imgStack)
cv2.waitKey(1)
1.3 像素之间的关系
数字图像在计算机视觉下,实际上是一个矩阵的形状
像素的下标又被称为坐标(x,y),我们可以从坐标的信息中发现像素与像素之间存在着一些空间位置的关系
1.3.1 领域
- 4-领域 :对于坐标(x,y)的像素P,P有四个水平垂直的相邻像素,称为4-领域
- 对角领域 :P有四个对角相邻像素,
- 8-领域:4-领域和对角领域合称为像素的8-领域
1.3.2 连接和连通
如果两个像素不仅空间上位置上领接,并且其他像素值也符合相似准则,则二个像素是连接的。 像素相似准则,指像素的灰度值相等,或者说像素值都在一个灰度集合中v中。
举例说明,8级灰度,像素值的范围在0~256(),7级灰度的像素值范围在0~128,那7级到8级这128~256范围的像素值就是一个灰度集合。
- 4-连接 :像素p,q都在集合v中的取值,并且q,p互为4-领域
- 8-连接 :像素p,q都在集合v中的取值,并且q,p互为8-领域
- 像素连通:就是在连接的基础上,增加的概念。如果说对于同个像素点存在:p和q连接、q和r连接,r和s连接,s和t连接,则p和t连通。
如果说连通的线,形成闭合环的,也可以叫连通域。
图像处理的算法技术,正是运用了像素之间的空间位置关系和像素数值,结合多种数学、线性代数逻辑实现,图像效果变化的。
2.图像预处理技术
图像处理的输入和输出形式,有以下几种形式:
| 输入 | 输出 |
|---|---|
| 单幅图像 | 单幅图像 |
| 多幅图 | 单幅图像 |
| 单幅图 | 数字或符号等内容 |
| 多幅图 | 数字或符号等内容 |
图像预测处理主要目的是消除图像中无关的信息,提取出有用信息(很像做结构数据的特征提取),来增加有关信息的可检测性,最大限度地简化数据,从而改进特征提取,图像分割、匹配和识别的可靠性,并应用到深度学习分析预测中,具体流程可以如下
图像预测处理流程主要包括:灰度变换、几何变换、图像增强、图像滤波等等
2.1 灰度变换
灰度变换是指将一幅图像的像素灰度值进行一定的映射变换,使得图像的亮度、对比度或颜色得到调整,以达到某种特定的视觉效果。
我们记录像素点的原始值为s,灰度变换的映射函数为T(s),变换后的像素点为d,即: 下面介绍几种常见的图像处理灰度变换方法:
2.1.1 线性变换
线性变换是一种简单的灰度变换方法,它将图像的灰度值进行线性映射,通常用下面的公式来表示:
其中,表示原图像的灰度值,表示变换后的灰度值,a和b是常数,可以通过调整它们来控制变换的幅度和方向。
2.1.2 对数变换
对数变换可以增强图像的暗部细节,通常用下面的公式来表示:
其中,表示原图像的灰度值,表示变换后的灰度值,是常数,可以通过调整它来控制变换的幅度。
2.1.3 幂律变换
幂律变换可以增强图像的亮部细节,通常用下面的公式来表示:
其中,表示原图像的灰度值,表示变换后的灰度值,和是常数,可以通过调整它们来控制变换的幅度和方向。
2.1.4 反转
反转是一种简单的灰度变换方法,它可以将图像中亮度值较高的区域变暗,将亮度值较低的区域变亮,从而实现对比度的增强。反转的实现方法很简单,只需要将每个像素的灰度值取反即可。例如,原图像中的像素灰度值为g,反转后的像素灰度值为255-g。
2.1.5 对比度增强
对比度增强是一种将图像中的灰度值重新映射到更广的范围内,从而增加图像的对比度的方法。对比度增强的实现方法有很多种,其中一种常用的方法是灰度拉伸,具体来说:
假设原图像的像素值范围为[a,b],将其线性拉伸到[0,255]的范围内,拉伸函数可以表示为:
其中,为原图像的像素值,为拉伸后的像素值。
在OpenCV中,也可以使用LUT(查找表)来实现灰度拉伸。
具体步骤如下: (1)计算拉伸函数,其中,和分别为原图像的最小像素值和最大像素值。 (2)创建一个个元素的查找表,其中表示原图像中像素值为的像素在拉伸后的像素值。 (3)遍历原图像的每个像素,查找表中查找对应的新像素值,将其赋值给输出图像。
下面是使用LUT实现灰度拉伸的代码示例:
import cv2
import numpy as np
# 读取原图像
img = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)
# 计算拉伸函数
a = np.min(img)
b = np.max(img)
g = lambda x: (x-a)*255/(b-a)
# 创建查找表
lookup = np.zeros(256, dtype=np.uint8)
for i in range(256):
lookup[i] = np.clip(g(i), 0, 255)
# 使用查找表进行灰度拉伸
img_stretched = cv2.LUT(img, lookup)
# 显示原图像和拉伸后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Stretched Image', img_stretched)
cv2.waitKey(0)
cv2.destroyAllWindows()
在这个示例中,我们使用np.clip()函数将像素值限制在范围内,以避免输出像素值超出范围的问题。
2.1.6 对比度压缩
对比度压缩是一种将图像中的灰度值重新映射到更窄的范围内,从而减少图像的对比度的方法。对比度压缩的实现方法也有很多种,其中一种常用的方法是对数变换。具体来说,对数变换将图像中的灰度值取对数后再缩放到[0,255]范围内,公式如下:
其中,为原图像的像素值,为压缩后的像素值。
在OpenCV中,可以使用LUT(查找表)来实现对比度压缩。具体步骤如下: (1)计算压缩函数,其中,和分别为压缩后的最小像素值和最大像素值。 (2)创建一个个元素的查找表,其中表示原图像中像素值为的像素在压缩后的像素值。 (3)遍历原图像的每个像素,查找表中查找对应的新像素值,将其赋值给输出图像。
下面是使用LUT实现对比度压缩的代码示例:
import cv2
import numpy as np
# 读取原图像
img = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE)
# 计算压缩函数
a = 50
b = 200
g = lambda x: (x-a)*255/(b-a)
# 创建查找表
lookup = np.zeros(256, dtype=np.uint8)
for i in range(256):
lookup[i] = np.clip(g(i), 0, 255)
# 使用查找表进行对比度压缩
img_compressed = cv2.LUT(img, lookup)
# 显示原图像和压缩后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Compressed Image', img_compressed)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.1.7 伽马矫正
伽马矫正是一种通过对图像中的灰度值进行非线性变换,从而调整图像亮度的方法。伽马矫正的原理是通过一个非线性函数来映射原图像中的灰度值,从而使得亮度值更低的区域变暗,亮度值更高的区域变亮。具体来说,伽马矫正使用下面的公式进行灰度值变换:
其中,g表示原图像中的像素灰度值,g'表示伽马矫正后的像素灰度值,A和γ是参数。A控制了灰度值的幅度,通常情况下A=1,γ控制了灰度值变化的速度,通常情况下γ取值范围在[0.5, 2.5]之间。当γ小于1时,图像中的亮度值较低的区域将被放大,从而提高了图像的对比度;当γ大于1时,图像中的亮度值较高的区域将被放大,从而使图像更加明亮。
反转、对比度增强、对比度压缩和伽马矫正是常用的图像灰度变换方法,它们可以用于调整图像的亮度、对比度等属性。根据实际需要选择合适的灰度变换方法可以改善图像的视觉效果,提高图像分析和处理的效果。
2.2 图像直方图
图像直方图:直方图,是指对整个图像在灰度范围内的像素值(0~255)统计出现频率次数,据此生成的直方图,称为图像直方图。直方图反映了图像灰度的分布情况。是图像的统计学特征,
如果我们使用RBG分别对三个通道实现直方图,就那这三个直方图就代表这个图像的特征。
2.2.1 直方图均衡化
直方图均衡化是一种更为高级的增强图像对比度的方法,其基本思想是通过对图像的像素值进行变换,使得像素值在整个灰度范围内分布均匀,从而增强图像的对比度。
均衡化转化
可以看到均衡化,是将直方图中的内容左右拉伸了一下。(让图像中像素领域之间差异显得不大,平衡整个图片的色彩,使我们观察图片,不会发现某个地方色彩对比其他位置突出)
直方图均衡化:直方图均衡化是一种常见的图像增强方法,它通过对图像的灰度直方图进行均衡化,使得图像的亮度分布更加均匀,从而增强图像的对比度和细节。具体实现方法可以参考以下步骤:
(1)计算原图像的灰度直方图; (2)计算灰度直方图的累积分布函数; (3)根据累积分布函数对原图像进行灰度值映射; (4)得到均衡化后的图像。
具体而言,假设原图像的像素值范围为[0,255],其灰度直方图为,CDF为,均衡化后的像素值为,则有:C(i)=\sum_{j=0}^i H(j)$$$$g(i)=\lfloor \frac{255 \times C(i)}{MN} \rfloor其中,和分别为原图像的宽度和高度。在OpenCV中,可以使用equalizeHist()函数来实现直方图均衡化。需要注意的是,直方图均衡化有时会导致图像的噪声增强,因此在实际应用中需要谨慎使用。
2.3 空间滤波
空间滤波是一种基于图像局部邻域像素的图像处理方法,它通过对图像像素周围的邻域像素进行加权平均或其他数学运算来改变图像的特征。
空间滤波在图像去噪、边缘检测、图像增强等方面有着广泛的应用。
常见的空间滤波算法包括均值滤波、中值滤波、高斯滤波等。
- 均值滤波:将像素点周围的邻域像素的灰度值进行平均,用来减少图像中的噪声。
- 中值滤波:用邻域像素的中值来代替当前像素值,可以有效地去除图像中的椒盐噪声等非线性噪声。
- 高斯滤波:将邻域像素的灰度值按照一定的权值进行加权平均,其中权值由高斯函数计算得到,可以有效地平滑图像并保留较好的图像细节。
空间滤波的一般步骤如下:
- 定义一个固定大小的滤波器(也称为卷积核或模板),滤波器通常是一个矩阵。
- 将滤波器中心对准当前像素,将滤波器中的所有元素与当前像素的邻域像素进行加权或其他数学运算,得到当前像素的输出值。
- 移动滤波器,重复步骤2,直到所有像素都被处理过。
使用不同的滤波(卷积核也就是矩阵)来实现图像像素的改变,其中的主要有三功能分别是图像的模糊/去噪、图像梯度/边缘发现、图像锐化/凸图像增强,我这里都把这些功能都看成是图像增强, 因为这些操作都是修改了图像的像素。
使用滤波增强强调图像中感兴趣的部分,增强图像的高频成分,可以使图像中物体的轮廓清晰,细节清晰; 增强低频分量可以降低图像中噪声的影响,(对图像中的像素值进行处理),也可以是使图像变得模糊。
2.3.1 均值滤波
均值滤波是指用当前像素点周围N*N个像素点的均值来代替当前像素值
2.3.2 方框滤波
方框滤波不会计算像素均值,它可以自由选择是否对均值滤波的结果进行归一化,即可以自由选择滤波结果是邻域像素值之和的平均值,还是邻域像素值之和。
2.3.3 高斯滤波
在进行均值滤波与方框滤波时,其邻域内每个像素的权重是相等的。而高斯滤波会将中心点的权重加大,远离中心点的权重减小,以此来计算邻域内各个像素值不同权重的和。
2.3.4 中值滤波
用邻域内所有像素值的中间值来代替当前像素点的像素值。
2.3.5 双边滤波
双边滤波是一种非线性的滤波方法,它在平滑图像的同时保留了边缘信息。其核心思想是通过对像素点的空间位置和像素值之间的相似度进行加权平均,来达到滤波的效果。
双边滤波公式为:
其中,表示滤波后的像素值,表示邻域像素的灰度值,表示像素与中心像素的相似度,和分别表示空间权值和像素值权值,是归一化的权值之和,用于保证滤波后像素值的范围在之间。
在实际应用中,通常使用高斯函数来计算,空间权值和像素值权值也可以使用高斯函数来计算,它们的值都取决于两个参数,分别是空间域参数和灰度域参数。空间域参数决定了滤波器的半径,灰度域参数决定了滤波器对灰度差异的敏感程度。
2.3.6 边缘锐化
图像梯度计算的是图像变化的速度
对于图像的边缘部分,其灰度值变化较大,梯度值也较大;相反,对于图像中比较平滑的部分,其灰度值变化较小,相应的梯度值也较小。一般情况下,图像的梯度计算是图像的边缘信息。
其实梯度就是导数,但是图像梯度一般通过计算像素值的差来得到梯度的近似值,也可以说是近似导数。该导数可以用微积分来表示。
在线性代数微积分中,一维函数一阶微分定义:
在图像中就是一个二维函数,有二个方向,一个x方向一个y方向,因此需要做偏微分:
那个这个二维函数总的梯度就为:
每一个像素的梯度是由它周围8个像素共同确定的
要想计算出图像的边缘的基本特征,就需要类似的空间滤波,在这里空间滤波也叫它算子,主要用于计算边缘的算子有Sobel、Robort、Laplacian。
Sobel算子
Sobel X方向算子模版:
Sobel y方向算子模版:
Robort算子
Robort 算子模版
矩阵与矩阵相乘,比如 一个和一个一定要才能发生, 已知我们的算子是,对应到需要变化的图像上,也一定是取的形状,去算子相乘,但最好是与算子形状相同。
转
Laplacian算子
Laplacian算子是基于二阶微分计算的,其定义如下:
其中:
Laplacian算子模版
2.4 坐标变换
图像的坐标变换又被称为图像的几何计算,常见的基本变换有:图像平移、镜像、缩放、旋转、仿射
常用于深度学习。数据增强
cv2.warpAffine() 仿射变换
dst = cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])
- src:输入图像
- M:2*3 transformation matrix (转变矩阵)
- dsize:输出图像的大小,格式为(cols,rows),width 对应 cols,height 对应 rows
- flags: 可选参数,插值方法的组合(int 类型),默认值 INTER_LINEAR
- borderMode:可选参数,边界像素模式(int 类型),默认值 BORDER_CONSTANT
- borderValue:可选参数,边界填充值; 默认情况下,默认值 Scalar()即 0
将图像看成是一个矩阵 warpAffine(img,M,(rows,cols)) 实现基本的仿射变换效果,但是这种情况会出现 黑边 现象。最后一个参数为 borderValue,边界填充的颜色,默认为黑色,M为一个转换矩阵,Opencv函数通过图像矩阵
2.4.1 图像平移
在图像平移动中是一个转换矩阵:$$\left[ \begin{matrix} 1 & 0 & dx\ 0 & 1 & dy \end{matrix} \right] \tag{3}
2.4.3 缩放
在图像缩放中是一个转换矩阵:$$\left[ \begin{matrix} S_x & 0 & 0\ 0 & S_y & 0 \end{matrix} \right] \tag{3}
S(x)= \begin{cases} 1-2|x|^2+x^3,\quad |x|<1\ 4-8|x|+5|x|^2-|x|^3,\quad 1\leq |x|<2 \ 0, \quad |x|\geq 2 \end{cases} \tag{}