143. opencv
常用变量
img
1 | img = cv2.imread(filename) |
图片相关
读图:imread
定义
1 | imread(<文件名>, flag) |
imread 第二个参数: flag
| imread 第二个参数 | 含义 |
|---|---|
| cv2.IMREAD_COLOR | 缺省方式,读取图像为 BGR 8-bit 格式. |
| cv2.IMREAD_UNCHANGED | 图像格式不做任何改变,可用在读取带 alpha 通道的图片 |
| cv2.IMREAD_GRAYSCALE | 读取图像为转换为灰度图 |
显示图:imshow
定义
1 | imshow(<窗口名称>, < 图像实例 >) |
waitkey
waitKey() 传入的参数如果为 0,会无限等待直到任何按键按下,或者传入其他数值参数表示等待时长,单位为 ms,时长结束后显示图像窗口会关闭。
| 参数 | 作用 |
|---|---|
| 0 | 无限等待直到任何按键按下 |
| 传入其他数值参数 | 表示等待时长 (单位: ms),时长结束后显示图像窗口会关闭。 |
返回值
返回: 键入的值
使用: 检查按键,键入 q or Q 退出
1 | key = waitKey(20) & 0xff |
waityKey() 返回的数值和 0xff 相与后再和字符的 ord() 值比较,是为了规避某些系统中 waitKey() 返回的数值在高字节为非 0 值的情况。
调整窗口
使用 imshow() 方法显示图像时,默认是以图像的像素大小显示的,可以通过 nameWindow(窗口名称, cv2.WINDOW_NORMAL)命名窗口,再使用resizeWindow(窗口名称, 窗口宽度, 窗口高度) 缩放窗口,最后使用 imshow() 显示图像,注意三者都 * 在同一个窗口名称上 * 操作。
销毁窗口
destroyWindow(窗口名称): 单独关闭某个显示窗口destroyAllWindows(): 关闭所有显示窗口。
写入图片: imwrite
定义
1 | imwrite(<写入的文件名称>, < 图像实例 >) |
注:文件名称的后缀决定了图片文件的格式
视频相关
常用函数
| 方法 | 含义 |
|---|---|
cap = cv2.VideoCapture(‘文件名称’) |
构建视频文件的 cap 实例 |
cap.read() |
逐帧提取视频,每一帧为一幅图像 < br /> 方法返回的是一个二元组: 下标 0 的元素值为 True 或 False 如果为 Flase 表示读取文件完成。 下标 1 的元素为图像对象,也是一个 numpy 数组类型的数据。 |
cap.isOpened() |
检查 cap 实例是否已经打开 |
cap.release() |
释放实例 |
从视频文件获取图像
1 | import cv2 |
从相机获取图像
打开相机需要用相机的设备编号(数值型整数)作为入参传入 VideoCapture(相机编号),比如 cap = cv2.VideoCapture(0) 构建编号为 0 的相机访问实例,第 2 台相机则传入 1,以此类推,后续步骤的处理方法和读取视频文件一样。
1 | import cv2 |
写入视频文件
VideoWriter 对象
参数
- 文件名称
- 编码方式,其中编码方式和文件名称后缀有对应关系
- 每秒写入的帧数,参考数值为 25,符合人眼习惯
- 图像大小,int 类型
常用的文件名称后缀和编码方式
| 文件后缀 | 编码方式 |
|---|---|
| avi | XVID |
| avi | MJPG |
| avi | mp4v(小写) |
| mp4 | mp4v(小写) |
使用:创建 VideoWriter_fourcc 对象
1 | # 方法 1 |
图像大小获取
cat.get(propId) 方法获取,但是该方法获取的是 float 类型,需要转换为 int 类型再传入 VideoWriter。
举例
1 | import cv2 |
输出文字
乱码问题 (还是不要用中文吧)
**1、指定已有字体 **
查看字体
1 | fontList = [i for i in dir(cv2) if (i.startswith("FONT"))] |
2、使用 pillow
1 | import cv2 |
图像属性
shape 属性
行列数
- 行对应
img.shape[0] - 列对应
img.shape[1]
通道数
- 必须是非灰度图才有通道数 *,对应
img.shape[2]
其他属性
| 属性 | 含义 |
|---|---|
| ndim | 维度 <=> len(img.shape) |
| itemsize | 单个数据长度 |
| size | 总长度:有多少个数据 |
| nbytes | 占用的内存空间 = $itemsize \times size$ |
| shape | 形状 (type: tuple) |
| data | 数据 buffer |
dtype 属性
可以通过 img.astype(<np.uint32>) 转换得到,进行转换后看到 dtype 的变化,以及因为 dtype 变化引起 itemsize 相关属性的变化。
像素操作
通道
三通道: RGB
四通道: RGB + ahpha(透明度)
通道分离
方法 1
使用 cv2.split(img)
方法 2
使用索引方式(切片),如:r = img[:, :, 0]
通道合并
方法 1
使用 cv2.merge((r, g, b))
方法 2
1 | newImg = np.zeros(img.shape, dtype=np.uint8) |
- 注 *:np.zeros 一定要指定 dtype 为
np.uint8
图像的加法
add()
cv2.add(img1, img2): 超过阈值会截断
+
img1 + img2: 超过阈值会溢出,也就是说对 255 取模
addWeighted()
参数
- 第 1 个参数是图像 1
- 第 2 个参数是图像 1 的权值
- 第 3 个参数为图像 2
- 第 4 个参数为图像 2 的权值
- 第 5 个参数附加数值 gamma,单个数值,即使是多通道图像也使用单个数值;
- 第 6 个参数可选,返回图像的实例,等同于函数返回结果
- 第 7 个参数可选,dtype,表示像素值的数据类型
图像的减法
** 目的:** 比较 2 幅图像的差异,比如判断前后 2 个时间点的图像是否发生了变化
subtract()
subtract(img1, img2) : img1 - img2,但是如果小于 0,那么就会截断为 0
-
计算结果对 256 求模运算
absdiff()
绝对值减法
图像和标量加减
图像之间的加减运算可以看成是 2 个向量之间的加减,将相同下标之间的元素值进行加减运算,如果和标量进行加减,则是将图像的每一个元素都和这个标量值进行加减。
当标量值只是 1 个数值时,只会对多通道图像的第 1 个通道进行运算,如果要进行多通道运算,标量值则使用一个包含 4 个数值的元组表示,即使是 3 通道的彩色图像也要用 4 元组表示这个标量值。
图像的乘法
multiply()
定义
dst=cv2.multiply(src1, src2[, dst[, scale[, dtype]]])
参数
- src1 和 src2 为图像对象
- 可选参数 scale 为放大倍数
- 结果 dst 等于 $saturate(scale \times src1 \times src2)$
规则
multiply() 遵循 * 饱和运算规则 *,比如 uint8 类型的数据如果超过 255 会被截断到 255。
符号乘法 *
实际上就是 Numpy 数组的乘法, 和用 +/- 做 numpy 加减法一样, 在数值类型表示范围的上限加 1 取模,比如 uint8 类型的数据对 256 取模
图像的除法
divide()
用法
** 用法 1**
1 | dst = cv2.divide(src1, src2[, dst[, scale[, dtype]]]) |
- src1 和 src2 都是图像对象
- scale: 制定 src1 的方法倍数
- $dst = saturate(src1 \times scale \div src2)$
** 用法 2**
1 | dst = cv2.divide(scale, src2[, dst[, dtype]]) |
- scale: 数值类型
- src2: 图像对象
- $dst = saturate(scale \div src2)$
补充
- uint8 等整数类型的除法,运算后的结果会做四舍五入取整
- divide() 遵守 * 饱和运算规则 *
/
对应元素直接进行数学运算
divide() 除法中的 0
当有元素为 0 且作为被除数时,divide() 计算仍然是有实际意义的
scale 参数
scale 参数的用法比较特殊,要想实现 $scale \div src2$ 的用法,必须显式地声明形参的名称。
图像位运算
按位取反 bitwise_not()
将数值根据每个 bit 位 1 变 0,0 变 1,比如 0xf0 按位取反就变成了 0x0f
如果是 uint8 类型的数据,取反前后的数据相加结果为 0xff(255)
按位与 bitwise_and()
1 | dst = cv2.bitwise_or(src1, src2[, dst[, mask]]) |
按位或 bitwise_or()
1 | dst = cv2.bitwise_or(src1, src2[, dst[, mask]]) |
按位异或 bitwise_nor()
1 | dst = cv2.bitwise_or(src1, src2[, dst[, mask]]) |
注意
- 2 个图像的按位操作和算术运算一样,也要求 2 个图像的大小一样,通道数一样。不同于算术运算数据类型不一样时通过 dtype 声明新生成图像的数据类型,按位运算的接口中根本就没有 dtype 参数,所以位运算中 2 个图像的数据类型也必须一致。
- 处理标量数据时不会自动填充第 4 通道为 0 而直接报错了,所以在处理 4 通道图像时则必须使用四元组。一个好的编程习惯是不管图像是多少通道的都使用四元组表示这个标量,如果不想对某些通道进行位运算,则用相应的全 0 或全 f 代替,比如一个 3 通道的 uint8 类型的图像,只需要对 2 通道和 0x33 相与,构造的四元组就是(0xff,0x33,0xff,0xff)。
色彩空间变换
cvtColor()
原型
1 | dst=cv2.cvtColor(src, code[, dst[, dstCn]]) |
遍历所有色彩空间转换名称
1 | colors = [i for in dir(cv) if (i.startswith('COLOR_'))] |
applyColorMap()
原型
1 | cv2.applyColorMap(src, colormap[, dst]) |
src 为输入图像,可以是单通道或 3 通道的 8bit 图像。
colormap 为颜色图模式,可以传入整数 0~21 对应各种不同的颜色图,或者用 cv2.COLORMAP_AUTUMN(等价于 0)、cv2.COLORMAP_BONE(等价于 1)等方式传入,OpenCV 源码头文件中定义的 22 种模式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26//! GNU Octave/MATLAB equivalent colormaps
enum ColormapTypes
{
COLORMAP_AUTUMN = 0,
COLORMAP_BONE = 1,
COLORMAP_JET = 2,
COLORMAP_WINTER = 3,
COLORMAP_RAINBOW = 4,
COLORMAP_OCEAN = 5,
COLORMAP_SUMMER = 6,
COLORMAP_SPRING = 7,
COLORMAP_COOL = 8,
COLORMAP_HSV = 9,
COLORMAP_PINK = 10,
COLORMAP_HOT = 11,
COLORMAP_PARULA = 12,
COLORMAP_MAGMA = 13,
COLORMAP_INFERNO = 14,
COLORMAP_PLASMA = 15,
COLORMAP_VIRIDIS = 16,
COLORMAP_CIVIDIS = 17,
COLORMAP_TWILIGHT = 18,
COLORMAP_TWILIGHT_SHIFTED = 19,
COLORMAP_TURBO = 20,
COLORMAP_DEEPGREEN = 21
};![img]()
几何空间变换
缩放
原型
1 | dst=cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]]) |
参数
- src:源图像;
- dsize:缩放后目标图像的尺寸,如果设置为 0,目标图像则使用源图像的尺寸乘以 fx 和 fy 得到;dsize 优先级高于 fx 和 fy,如果设置了 dsize,后面的 fx 和 fy 设置无效;
- fx 和 fy:dsize 未设置的情况下,使用 fx 和 fy 分别作为宽度和高度的放大倍数;
- interpolation:插值方法,默认使用双线性插值 cv2.INTER_LINEAR;
转置
作用
实现像素下标的 x 和 y 轴坐标进行对调:dst(i,j)=src(j,i)
原型
1 | dst = cv2.transpose(src[, dst]) |
注意
不可以使用 numpy 的 transpose() 转置和 T 属性,会出现数组下标访问越界
翻转
作用
实现水平翻转、垂直翻转和双向翻转
原型
1 | dst = cv2.flip(src, flipCode[, dst]) |
参数含义
- src: 源图像
- flipCode: 翻转方式,其中:0,水平轴翻转;大于 0 为垂直轴翻转;小于 0 作双向翻转
仿射变换
原型
1 | dst=cv2.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]]) |
- src: 输入图像。
- M: 2×3 2 行 3 列变换矩阵。
- dsize: 输出图像的大小。
- dst: 可选,输出图像,由 dsize 指定大小,type 和 src 一样。
- flags: 可选,插值方法
- borderMode: 可选,边界像素模式
- borderValue: 可选,边界填充值; 默认为 0。
平移
手动指定算子 M=[[1,0,X],[0,1,Y]] 可以实现图像的移位
- X 表示向图像 x 方向(向右)移动的像素值
- Y 表示图像向 y 方向(向下)移动的像素值
旋转
步骤
用 getRotationMatrix2D() 方法构造出 warpAffine() 的算子 M
getRotationMatrix2D() 原型
1 | retval=cv2.getRotationMatrix2D(center, angle, scale) |
- center:旋转中心位置
- angle:旋转角度
- scale:缩放比例,不缩放时为 1
矫正
概念
因为拍摄角度或成像设备的原因发生畸变,可以用仿射变换进行矫正
步骤
使用 getAffineTransform() 构建 M 算子,入参为变换前和变化后的 2 组坐标点,每组坐标点包含 3 个位置参数
**getAffineTransform() 例子 **
1 | pts1 = np.float32([[10,71],[37,320],[550,240]]) |
例子
1 | import matplotlib.pyplot as plt |
旋转
原型
1 | cv2.rotate(src, rotateCode[, dst]) -> dst |
参数
src: 源图像
rotateCode: 可选 3 种参数
rotateCode 含义 cv2.ROTATE_90_CLOCKWISE 顺时针旋转 90 度 cv2.ROTATE_180 旋转 180 度,不区分顺时针或逆时针,效果一样 cv2.ROTATE_90_COUNTERCLOCKWISE 逆时针旋转 90 度,等同于顺时针 270 度
本质
是对 flip() 和 transpose() 的封装实现的
透视变换
概念
和仿射变换一样,透视变换也需要先构建变换 kernel,不过仿射变换只需要 3 个点构建,透视变换则需要找到 4 个点构建 kernel。
原型
1 | cv2.warpPerspective(src,M,dsize[,dst[,flags[,borderMode[,borderValue]]]])->dst |
参数
- src: 输入图像。
- M: 3×3 3 行 3 列变换矩阵。
- dsize: 输出图像的大小。
- dst: 可选,输出图像,由 dsize 指定大小,数据类型和 src 一样。
- flags: 可选,插值方法
- borderMode: 可选,边界像素模式
- borderValue: 可选,边界填充值; 默认为 0。
例
1 | import matplotlib.pyplot as plt |
阈值化
概念
概念
有些场合也称二值化,是图像分割的一种
作用
一般用于将感兴趣区域从背景中区分出来
方法
将每个像素和阈值进行对比,分离出来需要的像素设置为特定白色的 255 或者黑色 0,具体看实际的使用需求而定
threshold()
原型
1 | cv2.threshold(src, thresh, maxval, type[, dst]) -> retval, dst |
返回值
该方法返回 2 个值
- 第 1 个值 retval 为阈值
- 第 2 个值 dst 为阈值化后的图像
参数
src:源图像,8bit 或者 32bit 浮点类型,当 type 没有使用
cv2.THRESH_OTSU或cv2.THRESH_TRIANGLE标志时可以是多通道图像thresh:比较的阈值;
maxval:阈值化方法为
THRESH_BINARY和THRESH_BINARY_INV时单个像素转换后的最大值;type:阈值化类型;
标志 标志值 dst(x,y) 取值 条件 备注 cv2.THRESH_BINARY 0 maxval if src(x,y)>thresh; 取值只有 2 种,真正意义的 “二值化” 0 otherwise cv2.THRESH_BINARY_INV 1 0 if src(x,y)>thresh; maxval otherwise cv2.THRESH_TRUNC 2 threshold if src(x,y)>thresh; 最后的取值有多种 src(x,y) otherwise cv2.THRESH_TOZERO 3 src(x,y) if src(x,y)>thresh; 0 otherwise cv2.THRESH_TOZERO_INV 4 0 if src(x,y)>thresh; src(x,y) otherwise cv2.THRESH_MASK 7 / / cv2.THRESH_OTSU 8 / 标志位,使用大津法选择最佳阈值 cv2.THRESH_TRIANGLE 16 / 标志位,使用三角算法选择最佳阈值 注意
cv2.THRESH_OTSU 或 cv2.THRESH_TRIANGLE 的使用比较特殊,并不能单独使用,需要和其他类型的数值按位或一起传入。
比如这样使用
type=cv2.THRESH_BINARY | cv2.THRESH_OTSU。当然在实际使用中也常见将cv2.THRESH_OTSU或cv2.THRESH_TRIANGLE这 2 种取值之一和前 5 种 type 相加,比如type=cv2.THRESH_BINARY + cv2.THRESH_OTSU,这是因cv2.THRESH_OTSU或cv2.THRESH_TRIANGLE和其他 5 种type按位或和相加得到的值是一致的。从上面的对照表也可以看到,如果使用cv2.THRESH_BINARY和cv2.THRESH_BINARY_INV得到的图像像素值只有 2 种,要么是maxval,要么是 0,可以称得上真正意义上的 “二值化”。
自适应阈值
原型
1 | cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[, dst]) -> dst |
参数
- src:源图像;
- maxValue:单个像素转换后的最大值;
- adaptiveMethod:自适应方法:cv2.ADAPTIVE_THRESH_MEAN_C(平均法)或 cv2.ADAPTIVE_THRESH_GAUSSIAN_C(高斯法)
- cv2.ADAPTIVE_THRESH_MEAN_C(平均法):这时阈值等于窗口大小为 blockSize 的临近像素点的平均值减去 C;
- cv2.ADAPTIVE_THRESH_GAUSSIAN_C(高斯法):阈值等于窗口大小为 blockSize 的临近像素点高斯窗口互相关加权和减去 C。
- thresholdType:阈值化类型,THRESH_BINARY 或 THRESH_BINARY_INV 二选一;
- blockSize:计算某个像素使用的阈值时采用的窗口大小,奇数值;
- C:平均值或加权平均值减去的常量值;可以为正值,0 或负值;
总结
- threshold() 方法使用大津法和三角法时不需要指定阈值,会自动根据像素值求出阈值,其他方法则需要指定阈值的大小。
- threshold() 方法使用全局单一阈值,而 adaptiveThreshold() 方法使用局部阈值,所以在处理光线不均匀图片时能取得更好的
平滑处理
目的
图像在生成、传输或存储过程中可能因为外界干扰产生噪声,从而使图像在视觉上表现为出现一些孤立点或者像素值突然变化的点,图像平滑处理的目的就是 ==为了消除图像中的这类噪声==。
滑动窗口
定义
也叫滤波器模板、kernel。用这个 ksize=3×3 的窗口作用于原始图像上的每一个像素,被这个窗口覆盖的 9 个像素点都参与计算,这样在该像素点上就会得到一个新的像素值,当窗口沿着图像逐个像素进行计算,就会得到一幅新的图像。
滤波器模板的不同就构成了滤波算法的差异:
- 均值平滑算法中滑动窗口中各个像素点的系数均为 1/(窗口高 * 窗口宽)
- 高斯平滑中系数和中心点的距离满足高斯分布。
边沿处理
填 0、填 1、复制边沿等
均值平滑
原型
1 | dst=cv2.blur(src, ksize[, dst[, anchor[, borderType]]]) |
参数
- src:源图像,通道数不限,数据类型必须为 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
- ksize:kernel 尺寸、窗口大小,二元组类型,元素值可以是偶数或奇数;
- anchor:锚点,默认为(-1,-1),作用于滑动窗口的中心点;
- borderType:边界处理类型;
效果
ksize 越大,图像越模糊,清晰度越低。
中值平滑
概念
中值平滑和均值平滑一样也用到了滑动窗口,但是它并不是计算滑动窗口中的某种加权和,而是使用原图像滑动窗口中所有像素值排序后的中值作为新图像的像素值。
原型
1 | dst=cv2.medianBlur(src, ksize[, dst]) |
参数
- src:源图像,通道数可以是 1,3 或 4,当 ksize 为 3 或者 5 时,数据类型可以是 CV_8U, CV_16U, CV_32F,当使用更大的 ksize 时,数据类型只能是 CV_8U;
- ksize:kernel 尺寸、窗口大小,整数型,大于 1 的奇数值;
像素值对比
中值后模糊原因
均值和中值都会降低图像变化的程度
小结
平滑处理是图像滤波的一种,可以看做是低通滤波,它会 == 消除图像的高频 “信号”==,让图像看起来更模糊、平滑,通过将变化前后的图像像素值绘制曲线可以更形象地观察到这种平滑效果。
高斯平滑
概念
高斯平滑则 ==根据距离中心点的间距远近其权重会不同==,这种方式看起来更符合” 惯例”:身边的人对你影响会更大。
高斯分布:正态分布
滑动窗口的权重: 正态分布的归一化
原型
1 | dst=cv2.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]]) |
参数
- src:通道数任意,实际处理是分通道处理;图像深度只能是 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
- ksize:元组类型,窗口大小,宽度和高度可以不一样,但是必须是正的奇数;如果设置为 0,则根据 sigma 计算得到。
- sigmaX:图像 X 方向的标准差,对应前述二维高斯分布的σ1;
- sigmaY:图像 Y 方向的标准差,对应前述二维高斯分布的σ2,如果传入 0,会等于 sigmaX,如果 sigmaX 和 sigmaY 都传入 0,sigmaX 和 sigmaX 则根据 ksize 计算;
- borderType:边界处理方式;
注意
因为都是以滑动窗口中心点为原点,为了保证中心点 (x,y)=(0,0) 的权重为最大值,所以在 OpenCV 的高斯平滑中 μ1 和μ2 都设置为 0,这样在调用高斯平滑函数时只需要传入σ1(sigmaX)和σ2(sigmaY)。
效果
- ksize 越大,图像越模糊
- ksize 保持不变,sigma 越大时,原点的取值越小,周围点的取值更大,对应到图像上中心点的权重越低,周围点权重越高,所以 sigma 越大图像越模糊。
双边平滑
背景
均值、中值、高斯平滑的去躁是一种 “无差别攻击”,所有的像素都受到同一个加权系数的影响,所以在平滑过程中也会影响到图像的边沿(像素值突变的地方 ==???==)
双边滤波则可以在 ==去除噪声的同时又能保持图像的边沿==,也就是传说中的” 去噪保边”。
加权系数
函数原型
1 | dst=cv2.bilateralFilter(src, d, sigmaColor, sigmaSpace[, dst[, borderType]]) |
参数
- src:8bit 或浮点类型;1 或 3 通道;
- d:窗口大小,如果为非正数,根据 sigmaSpace 计算;d>5 时速度会比较慢,当噪声比较严重时可以选择 d>=9,但是此时不适合对时间敏感的处理;
- sigmaColor:亮度差的 sigma 参数;
- sigmaSpace:空间距离的 sigma 参数,同时作用于图像的 X 和 Y(行、列)2 个方向;
- borderType:边界处理方式;
总结
高斯平滑对比均值和中值平滑其取值更符合 “惯例”,在空间距离上距离越近的像素用来计算新像素的值其权重越大。均值平滑、中值平滑和高斯平滑会对整幅图像实现无差别的平滑,一个固定系数的滑动窗口作用于整个图像,所以平滑后的图像虽然处理掉了噪声,但是边沿部分也会被削弱。而双边平滑在高斯平滑使用的系数基础上乘以像素差值的高斯函数,和中心点像素差值越大整个系数值越小,最后就能达到去躁保边的效果。
形态学变换
形态学概念
形态学变换是基于图像形状的变换过程,通常用来处理二值图像,当然也可以用在灰度图上。
OpenCV 中的形态学变换同平滑处理一样也是基于一种 “滑动窗口” 的操作,不过在形态学变换中 “滑动窗口” 有一个更专业的名词:“结构元”,也可以像平滑处理那样称呼为 kernel。
结构元的形状有方形、十字形、椭圆形等,其形状决定了形态学变换的特点。形态学变换主要有腐蚀、膨胀、开操作、闭操作等等。
结构元生成
结构元生成函数用来生成形态学变换的 kernel 参数。
函数原型
1 | cv2.getStructuringElement(shape, ksize[, anchor]) ->retval |
参数
shape:结构元 (kernel) 的形状;
shape 属性 形状 cv2.MORPH_RECT 方形,所有的数值均为 1 cv2.MORPH_CROSS 十字交叉形,在锚点坐标的水平和竖直方向的元素为 1,其他为 0 cv2.MORPH_ELLIPSE 椭圆形 ksize:结构元 (kernel) 的大小;
anchor:锚点,默认使用 (-1,-1) 表示中心点;
不同 kernel 的影响
以膨胀为例来看
- 使用方形 MORPH_RECT 的结构元时,新图像的边界看起来仍然是方方正正的
- 使用十字形 MORPH_CROSS 和椭圆形 MORPH_ELLIPSE 的结构元时,边界要显得 “圆滑” 的多。
腐蚀
概念
腐蚀操作可以将边界的白色(前景)像素 “腐蚀” 掉,但仍能保持大部分白色。类似平滑处理的滑动窗口,用某种结构元在图像上滑动,当结构元覆盖原始图像中的所有像素都为 “1” 时,新图像中该像素点的值才为“1”(CV8U 为 255)。腐蚀可以用来 == 去除噪声、去掉“粘连”==。
函数原型
1 | cv2.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst |
参数
- src:通道数任意;图像深度只能是 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
- kernel:可以由 getStructuringElement() 构建;
- dst:输出图像,通道数和数据类型同 src;
- anchor:锚点,默认使用 (-1,-1) 表示中心点;
- iterations:腐蚀次数;
- borderType:边界类型;
- borderValue:边界值;
效果
kernel 的 ksize 越大,iterations 次数越多,图像看起来越 “廋”。
膨胀
概念
膨胀是腐蚀的逆操作,可以将边界的白色(前景)像素 “生长” 扩大。滑动窗口经过白色像素时,只要结构元中有 1 个像素为 “1” 时,新图像中该像素点的值就为“1”(CV8U 为 255)。
== 膨胀可以用来增强连接、填充凹痕。==
函数原型
1 | cv2.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst |
参数
- src:通道数任意;图像深度只能是 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;
- kernel:可以由 getStructuringElement() 构建;
- dst:输出图像,通道数和数据类型同 src;
- anchor:锚点,默认使用 (-1,-1) 表示中心点;
- iterations:膨胀次数;
- borderType:边界类型;
- borderValue:边界值;
效果
kernel 的 ksize 越大,iterations 次数越多,图像看起来越 “胖”。
morphologyEx() 函数
函数原型
1 | cv2.morphologyEx(src, op, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst |
参数
src:源图像,通道数任意;图像深度只能是 CV_8U, CV_16U, CV_16S, CV_32F or CV_64F;其中 op 为 cv2.MORPH_HITMISS 时仅支持 CV_8UC1;
op:变换方式;
kernel:可以由 getStructuringElement() 构建
op 为 cv2.MORPH_HITMISS 时则由子图构建;
dst:输出图像,通道数和数据类型同 src;
anchor:锚点,默认使用 (-1,-1) 表示中心点;
iterations:迭代次数;
borderType:边界类型;
borderValue:边界值;
op 值与 erode、dilate 的关系
| 形态学变换 | op 标志位 | 与 erode 和 dilate 关系 |
|---|---|---|
| 腐蚀 | cv2.MORPH_ERODE | dst=erode(src,element) |
| 膨胀 | cv2.MORPH_DILATE | dst=dilate(src,element) |
| 开操作 | 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=src−open(src,element) |
| 黑帽 | cv2.MORPH_BLACKHAT | dst=close(src,element)−src |
| 击中击不中 | cv2.MORPH_HITMISS | dst=erode(src,element) & erode( |
开操作
概念
开操作的实质是 ==先进行腐蚀再膨胀==,可以用来消除小于结构元大小的细小区域
示例
1 | img_open = cv2.morphologyEx(img_bin,cv2.MORPH_OPEN,kernel,iterations=1) |
闭操作
概念
闭操作实际上是 ==先进行膨胀再腐蚀==,因为膨胀可以用来填充孔洞、修复缺失的连接,但是同时也会导致白色轮廓增大,当用同样的结构元 (kernel) 再进行一次腐蚀操作后,就可以保持外形轮廓和原来的一致。
示例
1 | img_close = cv2.morphologyEx(img_bin,cv2.MORPH_CLOSE,kernel,iterations=1) |
形态学梯度
概念
形态学梯度操作是用膨胀图像减去腐蚀图像的结果,因为膨胀可以增大边沿,腐蚀会缩小边沿,所以形态学梯度变换就 ==能将轮廓提取出来==
示例
1 | img_gradient = cv2.morphologyEx(img_bin,cv2.MORPH_GRADIENT,kernel,iterations=1) |
顶帽
概念
顶帽变换是用原图减去开操作图像,因为开操作会去除小于结构元的小区域,原图减去开操作图像后,会 ==将开操作去除的小区域保留下来==
示例
1 | img_tophat = cv2.morphologyEx(img_bin,cv2.MORPH_TOPHAT,kernel,iterations=1) |
黑帽
概念
黑帽变换和顶帽变换则相反,是将闭操作后的图像减去原图,因为闭操作会填充孔洞(小的黑色区域),孔洞部分变成白色,而原图中仍然为黑色,这样就会 ==将原图中的孔洞保留下来并变为白色区域==。
示例
1 | img_blackhat = cv2.morphologyEx(img_bin,cv2.MORPH_BLACKHAT,kernel,iterations=1) |
击中不击中
概念
击中击不中变换可以 ==用来在原图中查找子图==,假设要查找的图像中包含了多种子图,可以利用某个子图构造出 kernel,经过击中击不中变换就能在该子图中心保留一个非零的点。
** 注意:** 这里构造 kernel 不再是使用 getStructuringElement(),而是需要用子图构造。
一个构造 kernel 的例子如下,首先从子图中读取图像,然后和要做变换的原图做一样的阈值化,接下来构造一个和子图大小一样类型为 np.int8 型的 kernel,其中子图阈值化后值为 255 的位置设置为 1,阈值化后值为 0 的位置设置为 - 1:
1 | #构建 kernel |
步骤
- 读取原图并进行阈值化
- 按照前面的方法构建 kernel
- 用 morphologyEx() 进行击中击不中变换
- 用 findNonZero() 查找非零点并用 circle() 绘图显示出来
示例
1 | ## 构建 kernel |
图像金字塔
概念
图像金字塔是一些列图像的几何,如下图所示。更高层图像尺寸更小,更底层图像尺寸更大,看起来就像是一个金字塔一样
pyrDown
概念
这里的 down 是指图像变小,所以原始图像在金字塔的底部。
步骤
将当前图像和高斯核卷积:
![img]()
【Note】:这个高斯核的尺寸为 5×5 大小,所有元素的值加起来正好为 256,最后再除以 256,得到的加权和正好为 1。其距离最中心越近数值越大,这正好和高斯平滑选择的高斯核类似。这个过程也类似于高斯平滑。
函数原型
1 | cv2.pyrDown(src[, dst[, dstsize[, borderType]]]) ->dst |
参数
- src:源图像;
- dst:目标图像;
- dstsize:缩放后目标图像的尺寸,必须满足 std::abs(dsize.width2 – ssize.width) <= 2 && std::abs(dsize.height2 – ssize.height) <= 2
- borderType:边界填充类型;
行为
经过 pyrDown() 处理的图像变得更加模糊 (平滑)。然后移除偶数行和偶数列,然后就能得到和原图相比是原图 1/4 大小的新的图像,在图像金字塔中就位于当前层的上一层。
pyrUp
概念
将图像的尺寸变大,所以原始图像位于图像金字塔的顶层。
函数原型
1 | cv2.pyrUp(src[, dst[, dstsize[, borderType]]]) ->dst |
参数
- src:源图像;
- dst:目标图像;
- dstsize:缩放后目标图像的尺寸,必须满足 std::abs(dsize.width – ssize.width2) ==dsize.width % 2 && std::abs(dsize.height – ssize.height2)== dsize.height % 2
- borderType:边界填充类型;
图像梯度
概念
高斯平滑、双边平滑和 均值平滑、中值平滑 介绍的平滑处理可以看做是图像的 “低通滤波”,它会滤除掉图像的“高频” 部分,使图像看起来更平滑。
图像梯度则可以看做是对图像进行 “高通滤波”,他会滤除图像中的低频部分,==为的是凸显图像的突变部分==
sobel
函数原型
1 | dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]]) |
参数
- src:源图像;
- ddepth:目标图像深度;
- dx:x 方向求导阶数;
- dy:y 方向求导阶数;
- dst:目标图像;
- ksize:kernel 尺寸,说明文档上指出必须是 1,3,5,7 中的一个,但是实验可以得到应该是小于 31 的正奇数;如果是 - 1 表示 scharr 滤波;
- scale:缩放比例,默认为 1;
- delta:叠加值,默认为 0;
- borderType:边界填充类型;
效果
dx 与 dy 取值不同的效果
| (dx, dy) | 效果 |
|---|---|
| (1, 0) | 凸显 x 方向的梯度变化 |
| (0, 1) | 凸显 y 方向的梯度变化 |
| (0.5, 0.5) | 凸显边沿 |
| (1, 1) | dx 和 dy 都为 1 表明是与的关系,只有 x 和 y 方向都有梯度的时候才能被检测出来。 |
ksize 的取值不同
- ksize 的值越大,梯度信息呈现的越多
- ksize 相同,dx 的值越小,梯度的细节越多
scharr
背景
当 kernel 的尺寸为 3×3 时,Sobel 计算的结果不是很精确,为了得到更精确的计算结果常采用下图所示的 Scharr kernel
概念
Scharr 变换可以看做是使用了 Scharr 核的 Sobel 变换,是一种经过改进的 Sobel 变换,同样也要区分 x 和 y 方向分开计算梯度。
函数原型
1 | dst = cv2.Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]]) |
参数
- src:源图像;
- ddepth:目标图像深度;
- dx:x 方向求导阶数;
- dy:y 方向求导阶数;
- dst:目标图像;
- scale:缩放比例,默认为 1;
- delta:叠加值,默认为 0;
- borderType:边界填充类型;
Notes
scharr()没有ksize参数,scharr的kernel大小固定为 $3 \times 3$dx 和 dy 要满足如下的关系,否则会抛异常
CV_Assert(dx>= 0 && dy >= 0 && dx + dy == 1 )也就是每次只能求 x 方向或者 y 方向单个方向的梯度,而且只能求一阶梯度,不像 Sobel() 中 dx 或 dy 可以设置为更大的值计算更高阶的梯度。
Laplacian
概念
Laplacian 变换是对图像求二阶导数,下图是 2 种 $3 \times 3$ 尺寸的 kernel,这里 ksize 是 Laplacian() 的入参名称
Notes
Laplacian() 变换不需要区分图像的 x 和 y 方向计算梯度,从上图的 2 种 kernel 也可以看到其 x 和 y 方向是对称的。
在 Laplacian() 变换中,==ksize 必须是小于 31 的正奇数 ==
当 ksize 等于 1 时,这时 kernel 的尺寸大小并非是 1,其实际尺寸仍然为 3×3 (看源码)
函数原型
1 | dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]]) |
参数
- src:源图像;
- ddepth:目标图像深度;
- dst:目标图像;
- ksize:kernel 尺寸,小于 31 的正奇数;如果为 1 仍然是一个 3×3 的 kernel;
- scale:缩放比例,默认为 1;
- delta:叠加值,默认为 0;
- borderType:边界填充类型;
Notes
Laplacian变换中没有 dx 或 dy 参数,因为Laplacian是对图像求二阶导数。
效果
Laplacian()中的ksize越大,梯度信息越丰富- 在相同的
ksize时,二阶Sobel变换和Laplacian变换对比看,Laplacian变换取得的梯度信息要更明显一些。
边沿检测
概念
图像梯度反应的是图像像素值的变化过程,不管变化大小都考虑在内,所以 Sobel, Laplacian 变换得到的是一个多级灰度图。
边沿洁厕也昆虫看做是图像梯度的一种延伸,不过边沿检测更注意图像的 “边沿部分”,图像梯度变化较小的部分会被忽略,只有较大变化的部分保留下来
Canny
概念
canny 边沿检测有低错误率、很好地定位边缘点、单一的边缘点响应等优点
步骤
- 高斯滤波器平滑输入图像;
- 计算梯度幅值图像和角度方向;
- 对梯度幅值图像应用非最大值抑制;
- 用双阈值处理和连接分析检测和连接边沿。
非极大值抑制
抑制非极大值的目标(去冗余),从而搜索出局部极大值的目标(找最优)
函数原型
函数原型1
1 | edges=cv2.Canny(image, threshold1, threshold1[, edges[, apertureSize[, L2gradient]]]) |
参数
- image:8bit源图像,可以是单通道或多通道;
- threshold1:迟滞阈值1;
- threshold2:迟滞阈值2,和threshold1没有大小要求,函数内部会调整交换;
- edges:目标图像,二值图像;
- apertureSize:kernel尺寸,默认为3;
- L2gradient:是否使用L2范式,如果设置为True,计算梯度时使用的是2个方向梯度的平方和开平方,如果设置为False,则使用2个方向梯度的绝对值的和;
函数原型2
1 | edges=cv2.Canny(dx, dy, threshold1, threshold2[, edges[, L2gradient]]) |
参数
- dx:源图像的16bit(CV_16SC1 or CV_16SC3) x方向梯度图像;
- dy:源图像的16bit(CV_16SC1 or CV_16SC3) y方向梯度图像;
- threshold1:迟滞阈值1;
- threshold2:迟滞阈值2;
- edges:目标图像;
- L2gradient:是否使用L2范式,如果设置为True,计算梯度时使用的是2个方向梯度的平方和开平方,如果设置为False,则使用2个方向梯度的绝对值的和;
注意
- 第2种接口形式和第1种实现边沿检测的效果是一样的
- 第2种形式需要先计算图像的x和y方向的梯度,所以计算梯度的kernel尺寸在第2种接口中就不需要了
ksize差异
相同的threshold值,ksize越大边沿细节越多
这点和Sobel(),Scharr(),Laplacian()计算图像梯度效果是一样的。
迟滞阈值
即: $\frac{threshold1}{2}$
2个迟滞阈值在函数内部会进行比较,较小者存入low_thresh
阈值宽度
即: $\left | threshold1 - threshold2 \right | $
效果
相同的ksize时,threshold1和threshold2的差值越小,边沿细节越多
像素值
- canny() 变换后的边沿图像用直方图显示,可以看到变换后的图像是一个二值图像, 像素的取值为0或者255
- Sobel()、Laplacian() 等梯度变换得到的是一个灰度图
小结
- Canny() 变换中相同的 threshold 值,ksize 越大边沿细节越多;
- threshold1 和 threshold2 的差值越小,边沿细节越多;
- Canny() 变换后得到的是一个二值图像;
- Canny() 第 2 种接口形式得到的图像效果和第 1 种相比几乎没有差别,因为需要先计算图像 x 和 y 方向的梯度图像,使用起来更繁杂些。
统计函数
非 0 值数量
函数原型
1 | cv2.countNonZero(src) -> retval |
参数
- src:输入图像,必须为单通道图像;
- retval:非零像素值个数
功能
countNonZero()用来统计元素值为非0值的像素点个数。
最小最大值及其位置
函数原型
1 | cv2.minMaxLoc(src[, mask])->minVal, maxVal, minLoc, maxLoc |
功能
minMaxLoc() 函数返回图像中的元素值的最小值和最大值,以及最小值和最大值的坐标。
参数
- src:输入图像,必须为单通道图像;
- mask:掩码;
- minVal, maxVal, minLoc, maxLoc:依次为最小值,最大值,最小值的坐标,最大值的坐标;
返回值
返回 minLoc 和 maxLoc 的坐标位置是以 OpenCV 中 (x,y) 的形式组织的,但是在 numpy 中下标访问是按照 array[行][列] 形式,类似于 array[y][x] 的形式,所以 minLoc 和 maxLoc 的坐标值不能直接用于 numpy 的下标访问,需要对调后才可以使用
注意
- minMaxLoc()内部是按照行扫描方式,如果找到一个最小值,后面没有比这个数值更小的数值,那最小值的位置就是最开始出现的那个位置,即使后面出现了这个最小数值相等的数值,找最大值也一样
元素值之和
函数原型
1 | cv2.sumElems(src) -> retval |
功能
sumElems() 统计所有元素值之和,如果有多通道,分通道计算,返回的是一个四元组,依次对应图像可能包含的第 0,1,2,3 通道,如果单通道图像则只有下标 0 对应的元素有意义,如果是 3 通道则只有前 3 个元素有意义。
参数
- src:输入图像,可以是单通道,3通道或4通道图像;
- retval:返回的是一个4元组,分别对应各通道元素的和。
平均值
函数原型
1 | cv2.mean(src[, mask]) ->retval |
功能
mean() 用来统计单个通道内像素值的平均值,如果有多个通道,分通道计算。
参数
- src:输入图像,可以是单通道,3 通道或 4 通道图像;
- mask:可选的掩码;
平均值与标准差
函数原型
1 | cv2.meanStdDev(src[, mean[, stddev[, mask]]]) ->mean, stddev |
功能
meanStdDev() 用来统计单通道内像素值的平均值和标准差,一次调用返回 2 个结果。
参数
- src:输入图像,必须为单通道图像;
- mask:可选的掩码;
- mean:平均值;
- stddev:标准差;
- meanStdDev() 返回的是一个元组,下标 0 为平均值 mean,下标 1 为标准差 stddev。
单行/列的极值、和、均值
函数原型
1 | cv2.reduce(src, dim, rtype[, dst[, dtype]]) ->dst |
功能
reduce 用来统计二维数组的每一行或每一列中的最小值、最大值、平均值、和。这里 reduce 的含义也可以理解为将二维矩阵压缩成一维向量,压缩后的值根据入参类型可以是最小值、最大值、平均值或者和。
参数
- src:源图像,可以是单通道也可以是多通道,多通道时分通道计算;
- dim:如果为 0 表示统计每列的数据等价于压缩成行(row),如果为 1 表示统计每行的数据等价于压缩成列(column);
- rtype:reduce 操作的类型;
- dst:目标图像;
- dtype:目标图像的类型,如果不指定默认为 - 1 表示用源图像 src 的数据类型;
- dim 参数的理解:如果为 0 表示生成新的数据将是一个行向量,所以是在每一列上操作,将单个的列压缩成一个数值从而组成一个行向量;如果为 1 则表示生成新的数据是一个列向量,在每一行上操作,将单个的行压缩成一个数值从而组成一个列向量。

