目录
一、OpenCV简介
1.1 OpenCV是什么
OpenCV是一种开源的计算机视觉库,提供了广泛的图像处理和计算机视觉算法的实现。它最初是由英特尔开发的,现在已经成为计算机视觉领域最流行和广泛使用的开源库之一。OpenCV支持多种编程语言,包括C++、Python和Java等。它的主要特点是它提供了许多预先实现的算法,包括特征检测、图像处理、目标跟踪、人脸识别、运动估计、3D重建和深度学习等领域。
1.2 安装及使用
- python 3.x
- Jupyter
pip install jupyter
- opencv-python
pip install opencv-python #或opencv-contrib-python
# opencv-python包含基本的opencv
# opencv-contrib-python是高配版,带一些收费或者专利的算法,还有一些比较新的算法的高级版本,这些算法稳定之后会加入opencv-python。
已经安装opencv,查看opencv库的安装路径
- 方法一:用
__file__
属性
import cv2
cv2.__file__
# 'XXX\\Python\\Python310\\lib\\site-packages\\cv2\\__init__.py'
- 方法二:用
pip show opencv-python
C:\Users\Administrator> pip show opencv-python
# 输出示例:
# Name: opencv-python
# Version: 4.7.0.72
# Summary: Wrapper package for OpenCV python bindings.
# Home-page: https://github.com/opencv/opencv-python
# Author:
# Author-email:
# License: Apache 2.0
# Location: xxx\python310\lib\site-packages // 此处即为所求安装路径
# Requires: numpy, numpy, numpy, numpy
# Required-by:
源码github地址:this
使用OpenCV进行图像处理的接口源地址:here
二、图像的基础
2.1 成像原理
以电磁波谱辐射为基础的图像我们最为熟悉,某个物体发出电磁波被其他物体所接受从而形成图像,常见的由X射线和可见光波段的图像。
- 伽马射线【Gamma rays】:是从原子核内部发出来的,穿透力很强,对生物的破坏力很大。
- X射线【X-rays】
- 应用:CT影像
- 原理:利用不同密度对X射线的吸收率不一样,从而得到不同的衰减以成像。
- 紫外线波段成像【Ultraviolet】有显著的化学效应和荧光效应
- 可见光波段成像【Visible】
- 人类能看到的所有物体都是可见光波段成像,原理是:光线照射到物体上,反射到人眼中从而形成图像。
- 红外线波段成像【Infrared】
- 自然界中,一切物体都可以辐射红外线。
- 利用探测仪测量目标本身与背景间的红外线差可以得到不同的红外图像。
- 微波波段成像【Microwaves】
- 应用:雷达
- 无线电波段成像【Radio waves】
- 应用1:电视、手机、无线电广播等
- 应用2:磁共振成像(MRI)
2.2 图像格式
- BMP:位图图像
- JPEG:互联网上常用,压缩较大
- GIF:可以是动图
- PNG:支持Alpha通道调整图像的透明度
- TIFF:信息丰富,有利于原稿的复制
2.3 颜色空间
颜色空间又称为彩色模型,在某些标准下对彩色加以说明,常见的颜色空间有:
- RGB,主要用于计算机图形学中,根据人眼识别的颜色进行创建
- HSV[色调、饱和度、明度],根据颜色的直观特性创建
- HSI【色调、饱和度、强度】,反应人感知颜色的基本属性,与人感知颜色的结果一一对应
- CMYK【青色、品红、黄色、黑色】,应用于印刷业
三、OpenCV基础操作
3.1 图像的读取、显示、保存
- 读取图像
import cv2
img = cv2.imread("图像路径", "读取方式")
# cv2.IMREAD_COLOR: 默认值,加载一张彩色图像,忽视透明度,数字表示1
# cv2.IMREAD_GRAYSCALE: 加载一张灰度图,数字表示0
# cv2.IMREAD_UNCHANGED: 加载图像,包括它的Alpha通道,数字表示-1
print(img.shape) # img按照BGR的格式进行存储
- 显示图像
cv2.imshow("窗口名", img)
# cv2.waitKey()是一个键盘绑定函数,单位是毫秒,0代表等待键盘输入
k = cv2.waitKey(0)
if k == 27: # 输入ESC键退出
cv2.destroyAllWindows()
- 保存图像
cv2.imwrite("保存路径+名称", img)
3.2 通道转换
图像分辨率,每英寸图像内的像素点数,分辨率越高,像素点密度越高,图像越清晰。
图像的位深度[8位、24位、32位],是指描述图像每个像素点数值所占的二进制位数。如8bit只能表示灰度图像,每个点的值的范围为0-255【\(2^8\)】,24bit可以表示RGB三通道的图像,32bit可以表示RGB+Alpha四通道的图像。也就是说,位深度越大则图像能表示的颜色数就越多,色彩越丰富逼真。
- 通道转换
# 3==>1: GRAY = R * 0.114 + G * 0.587 + R * 0.299
# 1==>3: R = G = B = GRAY, A = 0
cv2.cvtColor(img, flag)
# img为待转换的图像,flag为转换模式
# cv2.COLOR_BGR2GRAY,彩色转灰度
# cv2.COLOR_GRAY2BGR,灰度转彩色
# cv2.COLOR_BGR2RGB, BGR格式转为RGB格式,opencv读入的图像是BGR格式的
# 注意:matplotlib.pyplot中使用的是RGB格式,需要进行转换后再使用
import matplotlib.pyplot as plt
img = cv2.imread("test.png", cv2.IMREAD_COLOR)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
plt.subplot(1, 2, 1)
plt.imshow(img) # 会发现此图像有色差
plt.subplot(1, 2, 2)
plt.imshow(img_rgb) # 正常
- 通道分离
将彩色图像,分成b, g, r
三个单通道图像。
import cv2
img = cv2.imread("orig.png")
b, g, r = cv2.split(img)
- 通道合并
可是使用split
对图像进行通道分离后,对单独通道进行修改,然后再合并为彩色图像。
import cv2
import numpy as np
img = cv2.imread("orig.png")
b, g, r = cv2.split(img)
b[:] = 0
img_merge = cv2.merge([b, g, r])
zeros = np.zeros(image.shape, dtype="uint8")
cv2.imshow("GREEN", cv2.merge([zeros, g, zeros]))
- 图像直方图
图像直方图(Image Histogram)用来表示数字图像中亮度分布的直方图,描述的是每个亮度值的像素数,能够反映图像亮度的分布情况。直方图常被用于图像的二值化。
import cv2
img = cv2.imread("test.png")
# cv2.calcHist([img], channels, mask, histSize, ranges)
hist = cv2.calcHist([img, [0], None, [256], [0, 255]])
# channels: 待计算的通道
# histSize:表示直方图分成多少份
三、OpenCV常见图像处理
3.1 在图像上绘制几何图像及添加文字
import cv2
img = cv2.imread("test.png")
# 绘制线段
cv2.line(img, (0, 0), (511, 511), (255, 0, 0), 5, cv2.LINE_AA)
# 待绘制的图像,起点,终点,线段颜色,线宽, linetype线条的类型
# 矩形绘制
# cv2.rectangle(待绘制图像, 左上角坐标, 右下角坐标,颜色,线宽,线型)
cv2.rectangle(img, (384, 0), (510, 128), (0, 255, 255), -1)
# 线宽为-1表示区域填充
# 圆绘制
# cv2.circle(待绘制图像, 圆心,半径,颜色,线宽,线型)
cv2.circle(img, (447, 63), 63, (0, 0, 255), -1)
# 椭圆绘制
# cv2.ellipse(待绘制图像,中心点坐标,(长轴长度、短轴长度), 旋转角, 起始角度,终止角,颜色、线宽、线型)
cv2.ellipse(img, (256, 256), (100, 50), 0, 0, 360, (255, 0, 0), -1)
# 起始角和终止角控制了,是画一整个椭圆,还是椭圆的一部分
# 多边形绘制
# cv2.polylines(img, 点对,线段是否闭合,颜色,线宽、线型)
pts = np.array([[10, 5], [50, 10], [70, 20], [20, 30]])
pts = pts.reshape((-1, 1, 2)) # 将点对转换为1行两列
cv2.polylines(img, [pts], True, (0, 255, 255))
# 添加文字
# cv2.putText(img, 要添加的文本, 文字的起始坐标[左下角的起点], 字体,文字缩放比例,颜色,线宽,线型)
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img, "OpenCV", (50, 200), font, 3, (0, 255, 255), 5, cv2.LINE_AA)
3.2 图像的几何变换
关于图像几何变换的接口的官方说明,点这里。
图像的几何变换包括平移、缩放、旋转、镜像、仿射变换、透视变换等。
# 仿射变换函数
cv2.warpAffine(img, M, dsize, flags, borderMode, borderValue)
# 输入图像,变换矩阵,输出图像尺寸,插值方法,边界像素模式,边界填充值
# 插值方法有四种:
# cv2.INTER_NEAREST(最近邻插值,默认,速度最快)、
# cv2.LINEAR(线性插值)
# cv2.INTER_AREA(区域插值)
# cv2.INTER_CUBIC(三次样条插值)
# cv2.INTER_LANCZOS4(Lancozos插值)
平移:将图像上所有的点按照指定的平移量水平或垂直移动。
\(x_1 = x_0 + t_x\)
\(y_1 = y_0 + t_y\)
import cv2
import numpy as np
img = cv2.imread("test.png")
# 构造移动变换矩阵
# 在x方向移动50,y方向移动25
H = np.float32([[1, 0, 50], [0, 1, 25]])
rows, cols = img.shape[:2]
res = cv2.warpAffine(img, H, (cols, rows))
# 注意,这里的dsize是先列后行
缩放:缩小图像称为下采样【down-Sampling】,放大图像称为上采样【up-Sampling】
import cv2
img = cv2.imread("test.png")
# cv2.resize(src, dsize=None, fx, fy, interpolation)
# 待缩放图像,输出图像尺寸[与比例因子二选一],fx沿水平轴的比例因子,fy沿y轴的比例因子,插值方法
cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
旋转:以某点为中心,旋转一定的角度,也就是说将图像上所有像素点旋转一个相同的角度。
—- 用旋转来扩充数据集,因为图像具有旋转不变性,旋转前后类别一致。
假设原来的坐标为:
\(x_0 = r cos(\alpha), y_0 = r sin(\alpha)\)
旋转后的坐标为:
\(x = rcos(\alpha + \theta) = rcos{\alpha}cos{\theta} – rsin{\alpha}sin{\theta} = x_0cos{\theta} – y_0 sin{\theta}\)
\(y = rsin(\alpha + \theta) = rsin{\alpha}cos{\theta} – rcos{\alpha}sin{\theta} = y_0cos{\theta} + x_0 sin{\theta}\)
注意:
- 图像旋转之前,为了避免信息丢失,一定要有坐标平移
- 旋转后会有空洞,对这些空洞要进行填充
import cv2
# 旋转矩阵:图像的旋转中心,旋转角度,缩放比例[0.5 正表示逆时针旋转并将结果缩放为原来的0.5]
# M = cv2.getRotationMatrix2D(center, angle, scale)
img = cv2.imread("test.png")
rows, cols = img.shape[:2]
M = cv2.getRotationMatrix2D((cols/2, rows/2), 45, 2)
dst = cv2.warpAffine(img, M, (cols, rows), borderVal=(0, 255, 255))
仿射变换:通过仿射变换对图像进行平移、旋转、缩放、剪切、反射等操作,以达到数据增强的效果。
# 求仿射变换矩阵, pos1表示变换前的位置,pos2表示变换后的位置
# M = cv2.getAffineTransform(pos1, pos2)
import cv2
import numpy as np
img = cv2.imread("test.png")
pos1 = np.float32([[50, 50], [200, 50], [50, 200]])
pos2 = np.float32([[10, 100], [200, 50], [100, 250]])
M = cv2.getAffineTransform(pos1, pos2)
res = cv2.warpAffine(img, M, (cols, rows))
透视变换:本质是将图像投影到一个新的视平面上。
# pos1表示透视变换前的4个点对应的位置
# pos2表示透视变换后的4个点对应的位置
# M = cv2.getPerspectiveTransform(pos1, pos2)
import cv2
import numpy as np
img = cv2.imread("test.png")
pos1 = np.float32([[114, 82], [287, 156], [8, 100], [143, 177]])
pos2 = np.float32([[0, 0], [188, 0], [0, 262], [188, 262]])
M = cv2.getAffineTransform(pos1, pos2)
res = cv2.warpAffine(img, M, (cols, rows))
3.3 图像滤波
关于平滑图像的接口的官方说明。
滤波,就是过滤掉某些信号。在图像处理领域,图像可以看作是一个二维信号,其中像素点的灰度值表示信号的强弱;像素值变化剧烈的地方称为高频区域,像素值变化缓慢、平坦的地方称为低频区域。
根据过滤内容,可以将滤波器分为高通滤波器和低通滤波器,高通滤波器过滤低频信号,通过高频信号,从而可以检测尖锐、变化明显的地方,常用于图像的边缘检测;低通滤波器过滤高频信号,放行低频信号,可以让图像变得平滑,主要用于图像的平滑去噪。
按照滤波器的实现方式,可以将滤波器分为线性滤波器和非线性滤波器。常见的线性滤波器有方框滤波、均值滤波、高斯滤波等;非线性滤波器有中值滤波、双边滤波等。
- 线性滤波:
使用领域内像素点值的加权和计算当前像素点的结果
\(O(x, y) = \sum{f(x + i, y + j) * k(i, j)}\)
方框滤波:
\[ kernel = \alpha \begin{bmatrix} 1&1\cdots&1\\ 1&1\cdots&1\\ \vdots&\vdots&\vdots \\ 1&1\cdots&1 \\ \end{bmatrix}, \alpha = \left\{ \begin{matrix} {1 \over {height * width}}, normalize=True \\ 1, normalize = False \end{matrix} \right. \]
均值滤波:
\[ kernel = {1 \over {height*width}} \begin{bmatrix} 1&1\cdots&1\\ 1&1\cdots&1\\ \vdots&\vdots&\vdots \\ 1&1\cdots&1 \end{bmatrix} \]
高斯滤波:可消除高斯噪声,广泛用于图像处理的降噪过程。kernel的权重由中心向周边递减,中心权重最大,越远离中心的像素点权重越低。
kernel的定义可以参考这里。
在C++接口中,接口中可以定义\(\sigma_x\)和\(\sigma_y\);Python接口中,只传入\(\sigma_x\),y轴的\(\sigma\)由内部核大小计算得出。
import cv2
# boxFilter
# cv2.boxFilter(src, depth, ksize, normalize)
img = cv2.imread("test.png")
cv2.boxFilter(img, -1, (3, 3), normalize=True)
# 均值滤波
# cv2.blur(src, ksize)
cv2.blur(img, (3, 3))
# 高斯滤波
# cv2.Guassianblur(src, ksize, std)
# std为标准差,调整kernel的下降速度,std越大下降越慢[高斯曲线越矮胖,远离中心点的像素对中心像素的影响越大],滤波结果越平滑。
blur = cv2.GaussianBlur(img, (5, 5), 0)
- 非线性滤波:
中值滤波:利用邻域内像素点的中值作为目标像素点的值【kernel区域内像素值排序取中值】。可用于去除椒盐噪声和斑点噪声。
双边滤波:同时考虑图像的空间邻近度和像素值相似性,达到保边去噪的目的。
\(I^{filter}(x) = {1 \over W_p}\sum_{x_i}I(x_i)f_r(||I(x_i) – I(x)||)g_s(||I(x_i) – I(x)||)\)
# 中值滤波
# cv2.medianBlur(img, ksize)
median = cv2.medianBlur(img, 5)
# 双边滤波
# cv2.bilatralFilter(src, d, sigmaColor, sigmaSpace)
# d邻域直径,sigmaColor灰度值相似性高斯标准差,sigmaSpace空间邻近高斯标准差
blur = cv2.bilateralFilter(img, 9, 75, 75)
3.4 图像增强
- 直方图均衡化:
将图像通过某种变换,得到一幅灰度值直方图均匀分布的新图像。可用于消除过度曝光,也可以提升暗部细节,计算方式为:
- 统计原图中每个灰度级出现的次数
- 计算累积归一化直方图
- 重新计算像素点的像素值
import cv2
# 直方图均衡化,接收单通道的图像, 如果是彩色图像,需要将其进行分离然后处理再合并
# cv2.equalizeHist(img)
img = cv2.imread("test.png")
b, g , r = cv2.split(img)
bH = cv2.equalizeHist(b)
gH = cv2.equalizeHist(g)
rH = cv2.equalizeHist(r)
imgH = cv2.merge((bH, gH, rH))
- Gamma变换:
对输入图像灰度值进行非线性操作,使得输出图像的灰度值与输入图像的灰度值呈指数关系,矫正过曝或过暗的图片。
\(V_{out} = A V_{in}^\gamma\)
原理及实现可参考Python-OpenCV中的Gamma变换(校正)。
3.5 形态学变换
当我们想要对数字图像进行处理和分析时,形态学运算是一种非常有用的工具。它主要用于图像处理领域中的形态学分析,例如形状检测、边缘检测、特征提取等。
形态学运算是一种针对二值图像(即黑白图像)进行的基本操作,它可以通过对图像中的像素进行特定的处理,来改变或增强图像的形态结构。
以下是形态学运算中最基本的两个操作:
- 膨胀(Dilation):将一个结构元素(通常是正方形或圆形)放在图像的某个像素上,如果该像素为白色(值为1),则该结构元素内的所有像素都被标记为白色(1),从而扩大了图像中的白色区域。膨胀操作可以用来填充小的空洞或连接不连通的区域。
- 腐蚀(Erosion):将一个结构元素放在图像的某个像素上,如果该像素为黑色(值为0),则该结构元素内的所有像素都被标记为黑色(0),从而缩小了图像中的白色区域。腐蚀操作可以用来去除小的噪点或分离连接的物体。
这两种基本的形态学运算通过重复执行多次,可以实现复杂的图像处理操作,如开运算、闭运算、梯度等。
- 开运算(Opening):先对图像进行腐蚀操作,然后再进行膨胀操作,它可以用来去除图像中的小噪点和连接不紧密的区域。
- 闭运算(Closing):先对图像进行膨胀操作,然后再对图像进行腐蚀操作,它可以用来填充图像中的小孔或连接不完整的区域。
- 梯度(Gradient):对图像进行膨胀和腐蚀操作,然后将两幅图像相减,可以得到原始图像中物体的边缘。
- 顶帽(Top Hat):原图与开运算图的差值,突出原图像中比周围区域亮的区域
- 黑帽(Black Hat):闭操作图像-原图像,突出原图像中比周围暗的区域
形态学运算,可以凸显目标对象最本质的形状特征,如边界、连通区域等。
# 获取结构元素
# kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7)) # 矩形
# kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (7, 7)) # 十字形
# kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7)) # 椭圆
# 腐蚀操作
cv2.erode(src, kernel, anchor, iterations)
# element: 腐蚀操作的内核,默认为一个简单的3x3矩阵
# anchor: 内核中心点,默认为Point(-1, -1)
# iterations:腐蚀次数,默认为1
# 膨胀操作
cv2.dilate(src, kernel, iterations=1)
# cv2.morphologyEx(img, MorphTypes, kernel)
# MorphTypes包含如下类型:
# cv2.MORPH_ERODE、cv2.MORPH_DILATE
# cv2.MORPH_OPEN # dst = dilate(erode(src, element))
# cv2.MORPH_CLOSE # dst = erode(dilate(src, element))
# cv2.MORPH_GRADIENT # dst = dilate(src, element) - erode(src, element)
# cv2.MORPH_TOPHAT # dst = tophat(src, element) = src - open(src, element)
# cv2.MORPH_BLACKHAT # dst = blackhat(src, element) = close(src, element) - src
# 开运算
open_img = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
# 闭运算
close_img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)