OpenCV 笔记
OpenCV 笔记
https://docs.opencv.org/4.9.0/d6/d00/tutorial_py_root.html
笔记介绍 OpenCV 在 Python 中的应用, 使用如下指令通过 Python 安装
pip install opencv-python
笔记中默认导入模块
import cv2 as cvimport numpy as npimport matplotlib.pyplot as plt
基础使用
图像访问与简单操作
https://docs.opencv.org/4.9.0/d3/df2/tutorial_py_basic_ops.html
图像读取可使用函数 img = cv.imread(filename, flags)
img = cv.imread(filename)将图像读取为 Numpy 数组filename图像文件路径flags读取标志设置, 通过|拼接标志, 常用的如下cv.IMREAD_GRAYSCALE总是将图像读取为灰度图cv.IMREAD_COLOR总是将图像读取为 BGR 三色通道cv.IMREAD_ANYDEPTH以原始色深读取图片, 否则总是读取为 8 位色深cv.IMREAD_UNCHANGED尽可能读取图片的所有原始信息
- 当读取失败时, 返回
None
对于图像读取结果
- 在 Python 中, 图像读取的结果为一个
dtype = uint8的 Numpy 数组 - 数组具有形状
width x height x 3, 三个维度分别为图片的宽, 高以及像素在色彩空间的坐标, 默认的色彩空间为 BGR - 可对数组直接赋值与索引完成对图片的修改与访问, 更多操作可见 numpy 笔记, 例如
- 通过切片索引
img[:, :, x]可提取图片色彩空间中的特定通道 - 通过赋值
img[a:b, c:d] = img2可将另一张图片覆盖到原图片上
- 通过切片索引
- 与一般 Numpy 数组不同
- 图片数组的切片索引将直接创建图片的新副本, 而不是原图片的引用
- 可以对图片数组直接代数运算, 但更推荐使用
np.float64(img), 创建图片以浮点数表示的拷贝再进行代数运算
- 函数
img = cv.merge(channels)可将不同单通道的图片重新组合为一张图片channels元组, 元组中为任意个大小相同的二维数组, 表示生成图片各个通道的值
- 由于图片各个像素通道类型为
uint8, 取值只有 0 到 255, 如果希望表示负数, 小数, 需要使用np.float64(img)创建以浮点数表示通道值的, 图片数组的拷贝 - 索引数组时为先行后列, 即
img[i, j]将索引图片的第i行第j像素img.shape[0]为图片的行数, 即图片的高;img.shape[1]为图片的列数, 即图片的宽- 图像处理中, 习惯以左上角为原点, x 轴正反向水平向左, y 轴正方向竖直向下
- 因此索引图片
x, y处的像素需要使用img[y, x]
色彩空间变换与图片绘制
https://docs.opencv.org/4.9.0/df/d9d/tutorial_py_colorspaces.html
OpenCV 提供了函数 cv.cvtColor() 用于变换图片的色彩空间
img = cv.cvtColor(src, code)变换图像的色彩空间src表示图片的数组code变换模式, 常用的有cv.COLOR_BGR2RGB将 BGR 色彩空间的图片转为 RGBcv.COLOR_BGR2GRAY将 BGR 色彩空间的图片转为灰度图片 (注意灰度图片为 ``width x height` 的数组)cv.COLOR_BGR2HSV将 BGR 色彩空间的图片转为 HSV- 对于其他转换也有类似的命名方式
- 返回值为变换完成的图片数组
可通过 matplotlib 绘制图片
plt.imshow(X, cmap, vmin=0, vmax=255)可将给定的图片绘制为 Matplotlib 图像X图片数组, 可以是二维 (灰度或单通道) 或三维 (一般图片)cmap单通道图片着色器, 常用的有 (对于三维, 将默认视为 RGB 图片)gray表示灰度图Reds使用红色着色, 表示红色通道Greens使用绿色着色, 表示绿色通道Blues使用蓝色着色, 表示蓝色通道
vmin, vmax数组值范围, 由于该函数默认会根据数组的值归一化导致图像颜色失真, 为了避免失真, 建议启用此参数, 并以 8 位颜色单通道的最小值与最大值作为参数传入 (对于 RGB 图片不需要)
图像的代数与掩膜叠加
https://docs.opencv.org/4.9.0/d0/d86/tutorial_py_image_arithmetics.html
OpenCV 中有如下常用掩膜制作函数 (二值化函数)
- 函数
ret, img = cv.threshold(src, threshold, maxval, type)可对图像进行简单二值化处理, 可用于获取掩膜src被二值化的图片数组, 建议传入灰度图或单通道图片 (多通道图片将对各个通道分别二值化)threshold比较阈值maxval当像素点满足阈值要求时, 设置为的值, 一般取255(不满足时则取为0)type阈值比较方式, 常用的有THRESH_BINARY检测像素点是否大于阈值 (偏亮的区域在二值化后变为白色maxval, 否则变为黑色0)THRESH_BINARY_INV检测像素点是否小于阈值 (与上述方式相反)THRESH_OTSU阈值选择标记, 表示使用大津法选择阈值, 通过|与以上方式结合
- 返回值
img为变换完成的图片数组,ret在以上介绍的方法中没有意义
- 函数
img = cv.inRange(src, lowerb, upperb)根据图片各像素的通道值是否在指定范围内对图片进行二值化src被二值化的图片数组, 可以是单通道或多通道的图片, 均将得到单通道的掩膜lowerb要求通道值的下界, 当传入多通道图片时, 则传入数组表示各个通道的下界upperb要求通道值的上界, 当传入多通道图片时, 则传入数组表示各个通道的下界- 返回值为二值化的单通道图片数组, 当像素点所有通道均在要求的范围内是, 该像素点将被二值化为
255, 否则为0
注意, 图像数组与 Numpy 数组不同, 无法直接完成运算, 需要使用 OpenCV 提供的函数完成代数混合 (注意混合时, 两张给出的图片至少要具有相同的大小)
- 函数
img = cv.addWeighted(src1, alpha, src2, beta, gamma)完成两图片的线性混合src1, src2两张色彩空间与大小相同的图片alpha, beta混合系数, 可以是任意实数gamma底部亮度值, 可以是任意整数- 返回值为变换完成的图片数组
- 具体混合公式为
- 当结果超出 8 位的
0~255范围时, 将修改为0或255
- 函数
img = cv.bitwise_not(src)对图像个像素按值取反src被变换的图片数组- 该函数通常可用于翻转已有的掩膜
- 函数
img = cv.bitwise_add(src1, src2, *, mask)对图像个像素按值与src1, src2参与运算的图片数组, 需要有相同的大小mask掩膜, 即一个单通道的图片数组, 掩膜中取值不为0的像素将参与运算, 掩膜为0的像素将取0- 返回值为变换完成的图片数组
- 类似的还有
cv.bitwise_or, cv.bitwise_xor, cv.add函数, 分别完成按位或, 按位异或与相加运算 (超出八位数值范围时将视为255)
以下例子为通过二值化提取图章图片 seal 中灰度大于 10 的部分, 并覆盖到被修改图片上 (OpenCV 有提供专门的掩膜图片覆盖函数 cv.CopyTo(), 以下仅为示例)
import matplotlib.pyplot as plt
import cv2 as cv
# 被覆盖图片
img1 = cv.imread("./images/roi.jpg")
img1 = cv.cvtColor(img1, cv.COLOR_BGR2RGB)
# 图章图片
seal = cv.imread("./images/b2.jpg")
seal = cv.cvtColor(seal, cv.COLOR_BGR2RGB)
# 截取被覆盖图片中, 图章覆盖位置的, 与图章大小相同的子图片
img_operated = img1[0 : seal.shape[0], 0 : seal.shape[1], :]
# 通过二值化处理, 提取图章用于覆盖部分的掩膜 mask
seal_gray = cv.cvtColor(seal, cv.COLOR_BGR2GRAY)
_, mask = cv.threshold(seal_gray, 10, 255, cv.THRESH_BINARY)
mask_inv = cv.bitwise_not(mask)
# 此处的 bitwise_and 并不改变图像, 更多是利用掩膜将图像中的特定区域置为 0
# 通过掩膜提取图章中的色块, 将图章中非色块位置的像素置为 0
img_bg = cv.bitwise_and(seal, seal, mask = mask)
# 通过掩膜将被覆盖图片中, 掩膜色块位置的像素置为 0
img_fg = cv.bitwise_and(img_operated, img_operated, mask = mask_inv)
# 将两张图片叠加, 两张图片对应位置的像素已经置 0, 因此不会产生其他额外作用
img_res = cv.add(img_fg, img_bg)
# 将原始图片提取区域使用被添上图章的子图片覆盖
img1[0 : seal.shape[0], 0 : seal.shape[1], :] = img_res
plt.imshow(img1)
plt.show()
图像几何变换
https://docs.opencv.org/4.9.0/da/d6e/tutorial_py_geometric_transformations.html
OpenCV 中有函数 cv.resize 可实现基本的图像缩放
- 函数
mat = cv.resize(src, dsize, *, fx, fy, interpolation)src被变换的图片数组dsize二元素元组或None, 表示图像缩放后的宽与高fx图片宽度缩放比例, 当dsize为 None 时有效fy图片长度缩放比例, 当dsize为 None 时有效interpolation图片插值算法, 常用的算法有cv.INTER_LINEAR默认算法, 速度快cv.INTER_CUBIC推荐放大算法cv.INTER_AREA推荐缩小算法
为了同时表示图像的缩放, 切变, 旋转与平移等变换, 需要使用图像的仿射变换, 即将原图像中的像素的 映射到新的像素点
通常使用二维的齐次矩阵 表示仿射变换, 有
但在 OpenCV 中的仿射变换矩阵则使用如下形式
注意此时为输入目标图像像素点位置 , 通过 映射为输入图像的像素点位置 , 并取该点的颜色作为目标图像的颜色
因此对于理论变换 , 输入的变换矩阵应满足
并提供了如下一系列有关函数
- 函数
cv.wrapAffine(src, M, dsize, *, flags, borderMode, borderValue)将仿射变换作用到图像上src被变换的图片数组M2 x 3 的 Numpy 数组, 即仿射变换矩阵, 应保证该矩阵的dtype为np.float32或np.float64dsize二元素元组, 表示变换结果的宽与高flags图片插值算法与变换标志 (通过|运算结合)- 插值算法见函数
cv.resize的介绍 - 标志
cv.WARP_INVERSE_MAP表示反向变换, 此时输入的仿射变换矩阵即表示输入图像到输出图像的变换
- 插值算法见函数
borderMode额外边缘处理方式, 即图像在变形后, 不一定能填满整个矩形图像额留下未定义的部分, 该参数规定这些未定义部分的处理, 常用的有cv.BORDER_CONSTANT默认, 使用指定的颜色填充未定义部分cv.BORDER_TRANSPARENT使用透明色填充未定义部分
borderValue指定未定义部分的填充颜色- 返回值为变换完成的图片数组
- 函数
cv.getRotationMatrix2D(center, angle, scale)生成除切变外的仿射变换矩阵center二元素元组, 表示变换中心点的坐标angle旋转角度, 单位为角度, 以逆时针方向为正scale缩放大小- 返回值为输出图像到输入图像的仿射变换矩阵, 即
cv.wrapAffine一般情况下接收的矩阵, 但函数所描述的是输入图像到输出图像的变换
- 函数
cv.getAffineTransform(src, dst)根据三点获取仿射变换矩阵src3 x 2 的 Numpy 数组, 表示变换前的三个点坐标dst3 x 2 的 Numpy 数组, 表示变换后的三个点坐标- 返回值为一个满足以上映射关系的仿射变换矩阵
- 函数
cv.warpPerspective(src, M, dsize, *, flags, borderMode, borderValue)将齐次变换作用到图像上 (即最后一行不为 的齐次矩阵, 此时还将包含投影变换)src被变换的图片数组M3 x 3 的 Numpy 数组, 即齐次变换矩阵, 应保证该矩阵的dtype为np.float32或np.float64dsize二元素元组, 表示变换结果的宽与高flags图片插值算法与变换标志, 参数与wrapAffine相同borderMode与borderValue参见cv.wrapAffine的介绍- 返回值为变换完成的图片数组
- 函数
cv.getPerspectiveTransform(src, dst)根据四点获取齐次变换矩阵src4 x 2 的 Numpy 数组, 表示变换前的四个点坐标dst4 x 2 的 Numpy 数组, 表示变换后的四个点坐标- 返回值为一个满足以上映射关系的齐次变换矩阵
卷积运算
https://docs.opencv.org/4.9.0/d4/d13/tutorial_py_filtering.html
OpenCV 提供了用于卷积运算的函数, 以及预设的图片模糊处理函数
cv.filter2D(src, ddepth, kernel, *, anchor, delta, borderType)src用于卷积的图片数组ddepth变换结果的色深, 常用的值有-1与输入相同cv.CV_8U使用 8 位无符号整型表示像素cv.CV_32F使用 32 位浮点数表示像素cv.CV_64F使用 64 位浮点数表示像素
kernel卷积掩膜, 通常为一个 n x n 的np.float32或np.float64的 Numpy 数组anchor二元素元组, 表示单个卷积运算结果存放的相对于掩膜的 x, y 坐标, 传入(-1, -1)表示将结果存放在掩膜中心的像素delta在卷积结果加上的偏置量, 默认为 0borderType边缘处理方法, 即在图像的边缘卷积时, 如何处理图像外的像素, 常用的有cv.BORDER_CONSTANT将图像外的像素视为 0 处理cv.BORDER_REFLECT_101近似对称处理 (默认)cv.BORDER_REPLICATE取最近的边缘像素代替
- 返回值为卷积完成的图片数组
cv.blur(src, ksize, *, anchor, borderType)对图像进行均值滤波src用于滤波的图片数组ksize二元素元组, 表示掩膜的宽与高anchor, borderType与函数cv.filter2D中的同名参数含义相同- 返回值为滤波完成的图片数组
cv.GaussianBlur(src, ksize, sigmaX, *, sigmaY, anchor, borderType)对图像进行均值滤波src用于滤波的图片数组ksize二元素元组, 表示掩膜的宽与高sigmaX高斯滤波器在 X 方向的标准差, 越大越模糊, 默认为 0.5sigmaY高斯滤波器在 Y 方向的标准差, 默认与sigmaX相同anchor, borderType与函数cv.filter2D中的同名参数含义相同- 返回值为滤波完成的图片数组
cv.medianBlur(src, ksize)对图像进行中值滤波src用于滤波的图片数组ksize整数, 表示掩膜的大小- 返回值为滤波完成的图片数组
cv.Sobel(src, ddepth, dx, dy, *, ksize, delta, borderType)对图形使用 Sobel 算子作卷积, 获取方向偏导以提取边缘src用于卷积的图片数组ddepth变换结果的色深, 由于图片梯度一般为浮点数, 因此推荐使用cv.CV_64Fdx, dy通常 x 方向的偏导则取dx = 1, dy = 0, y 方向的偏导类似ksizeSobel 算子大小, 只能取 1, 3, 5, 7delta在卷积结果加上的偏置量, 默认为 0borderType边缘处理, 见函数cv.filter2D介绍
cv.Laplacian(src, ddepth, *, ksize, delta, borderType)对图像使用拉普拉斯算子作卷积, 获取二阶差分, 用于提取边缘- 参数含义与
cv.Sobel基本相同
- 参数含义与
傅里叶变换
https://docs.opencv.org/4.9.0/de/dbc/tutorial_py_fourier_transform.html
cv.dft(src, *, flags)对图像进行离散傅里叶变换src进行变换的图像数组, 对于一般的 8 位色深图像, 需要通过np.float32()等函数, 将图像数组转换为浮点类型flags设置标志 (可通过|拼接多个标志), 常用的有cv.DFT_COMPLEX_OUTPUT进行傅里叶变换并输出复数谱, 此时输出结果为一个与输入图像大小相同的图像数组, 该图像上位于u, v的像素值具有两个浮点数通道, 分别表示变换结果的实部与虚部 (一般进行正变换时应该使用此标志)cv.DFT_INVERSE进行傅里叶逆变换并输出单通道的原始图像 (一般进行逆变换时应该使用此标志)cv.DFT_REAL_OUTPUT仅输出变换结果的实部 (用于抛弃逆变换结果的虚部, 以获取原始图像)cv.DFT_SCALE对变换结果除以参与变换的点数, 得到归一化的结果 (如果希望能完成逆变换, 则正变换与逆变换中至少有一处使用此标志)
- 返回变换结果, 具体与
flag有关
cv.mulSpectrums(a, b, flag)令两个复数谱相乘a,b相乘的两个大小相同的复数谱flag一般取0即可- 返回结果为两个复数谱的相乘结果
- 如果需要对图像进行卷积运算, 更推荐使用 cv.filter2D
cv.magnitude(x, y)将复数谱转换为幅值谱x, y两个大小相同的图像数组, 分别表示实数谱与虚数谱, 可通过切片索引spe[:, :, 0]提取实部, 虚部类似- 返回值为一个单通道的图像数组, 即幅值谱
- 绘制图像的幅值谱图片
- 对于复数谱无法用图像表示, 需要先使用
cv.magnitude()得到幅值谱, 此外还应当对幅值谱各点运算20 * np.log(mag)将幅值单位转变为分贝 - 一般 DFT 结果中, 低频分量位于两侧, 而高频分量位于中间, 不利于观察, 可通过
np.fft.fftshift(spe), 可将高频分量移动到两侧, 低频分量移动到中心, 实现中心化 (同样有逆过程np.fft.ifftshift(spe)) - 最后使用 plt.imshow 绘制幅值谱图片
- 对于复数谱无法用图像表示, 需要先使用
对于幅值谱图片绘制有例子
spe = cv.dft(np.float32(img), flags = cv.DFT_COMPLEX_OUTPUT)
spe = np.fft.fftshift(spe)
mag = 20 * np.log(cv.magnitude(spe[:, :, 0], spe[:, :, 1]))
plt.imshow(mag, 'gray')
对于滤波器设计有例子
def img_corr(img: np.ndarray):
res = np.zeros(img.shape, np.uint8)
max_p = np.max(img)
min_p = np.min(img)
rate_p = 255 / (max_p - min_p)
for i in np.arange(img.shape[0]):
for j in np.arange(img.shape[1]):
res[i, j] = np.round((img[i, j] - min_p) * rate_p)
return res
# 原始图像的中心化复数谱
fspe = np.fft.fftshift(cv.dft(np.float64(img), flags = cv.DFT_COMPLEX_OUTPUT | cv.DFT_SCALE)) # type: ignore
# 滤波器的中心化复数谱
gspe = ...
# 将原图像的复数谱与滤波器相乘, 并去中心化
res_spe = np.fft.ifftshift(cv.mulSpectrums(fspe, gspe, 0))
# 将反变换结果还原为 8 位色深的灰度图
res = img_corr(cv.dft(res_spe, flags = cv.DFT_INVERSE | cv.DFT_REAL_OUTPUT))
直方图分析
https://docs.opencv.org/4.9.0/de/db2/tutorial_py_table_of_contents_histograms.html
cv.calcHist(images, channels, mask, histSize, ranges)对图片进行直方图统计images一个列表 (对于单张图片应传入[img]), 包含了多个用于统计的图片数组, 各个图片应具有相同的色深, 大小与通道channels一个列表, 表示统计的通道索引, 对于灰度图或仅统计单通道至少要传入[0]或[x]mask掩膜, 即一个取值为0或255的与传入图片大小相同的图片数组, 仅取值为255的部分将进入统计, 如果不需要掩膜则传入NonehistSize一个列表, 表示各个通道统计分组数, 对于 8 位色深的灰度图, 一般传入[256]ranges一个二维列表, 表示各个通道统计的颜色值范围, 对于 8 位色深的灰度图, 一般传入[[0, 256]]- 返回值为一个 histSize_i x n 的数组, n 与统计的通道数有关
cv.equalizeHist(src)对图片进行直方图均衡化src用于均衡化的八位色深灰度图 (二维数组)- 返回值为均衡化的灰度图
对于直方图绘制有例子
# 注意, matplotlib 中, hist 函数需要传入原始数据, 对于统计完成的结果应使用 Axes.stairs() 绘制
axe.stairs(cv.calcHist([img], [0], None, [256], [0, 256]).flatten(), fill = True)
形态学
https://docs.opencv.org/4.9.0/d9/d61/tutorial_py_morphological_ops.html
形态学一般应用于二值化 (仅包含 255 与 0) 的图片, 可使用函数 cv.threshold 对图片进行二值化处理
cv.erode(src, kernel, *, anchor, iterations)对图片进行腐蚀处理src被处理的二值化图片数组kernel结构元数组, 可以是任意形状的二维数组, 一般要求dtype=np.uint8, 且仅含有0与1anchor二元素元组, 表明结构元锚点坐标, 默认传入(-1, -1)表明以中心为锚点iterations循环运算次数, 默认为 1- 返回值为腐蚀后的二值化图片
cv.dilate(src, kernel, *, anchor, iterations)对图片进行膨胀处理- 参数含义与
cv.erode基本相同
- 参数含义与
cv.morphologyEx(src, op, kernel, *, anchor, iterations)对图片进行形态学运算op形态学操作, 常用的的操作有cv.MORPH_OPEN形态学开启, 即先腐蚀后膨胀, 用于消除噪点cv.MORPH_CLOSE形态学闭合, 即先膨胀后腐蚀, 用于填充空洞cv.MORPH_GRADIENT形态学边缘提取, 即计算膨胀与腐蚀结果之间的差cv.MORPH_HITMISS击中与未击中处理, 参考 https://homepages.inf.ed.ac.uk/rbf/HIPR2/hitmiss.htm
- 其余参数含义与
cv.erode基本相同
cv.getStructuringElement(shape, ksize)形态学结构元构造shape形状标识, 常用如下cv.MORPH_RECT矩形结构元, 即使用1填充整个数组cv.MORPH_ELLIPSE椭圆结构元cv.MORPH_CROSS十字结构元, 即中心行与列元素为1
ksize结构元的大小- 导出 ksize x ksize 的二维结构元数组, 由
0与1组成
角点检测
https://docs.opencv.org/4.9.0/dc/d0d/tutorial_py_features_harris.html
OpenCV 提供以下函数用于简单的角点检测
cv.cornerHarris(src, blockSize, ksize, k, *, borderType)计算 Harris 响应src被检测的图片数组, 要求时单通道的一般为灰度图blockSize整数, 即检测窗口的大小ksize整数, 即 Sobel 算子大小, 只能取 1, 3, 5, 7, 一般取 3kHarris 响应函数中的系数k取值, 一般为0.04borderType边缘处理方法, 参见函数 cv.filter2D 介绍- 返回值为一个保存了检测图片各像素点对应 Harris 响应函数值的二维数组, 元素类型为
CV_32FC1 - 对于 Shi-Tomasi 响应函数, 类似的有
cv.cornerMinEigenVal(src, blockSize, ksize, *, borderType)
cv.goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance, *, mask, blockSize, gradientSize, useHarrisDetector)图像角点提取image被检测的图片数组, 要求时单通道的一般为灰度图maxCorners最大搜索的角点数, 如果可能角点多于该参数, 将给出可能性最大的几个qualityLevel角点质量要求等级, 即仅当该参数乘以图片角点响应函数的最大值作为响应函数的阈值, 一般可取 0.01minDistance角点间的最小欧几里得距离, 一般可取10mask掩膜, 要求是一个单通道的图片数组且色深为CV_8UC1, 仅非零位置的像素会用于检测blockSize整数, 即检测窗口的大小gradientSize整数, 即 Sobel 算子大小useHarrisDetector布尔值, 是否使用 Harris 响应函数, 默认为False, 使用 Shi-Tomasi 响应函数, 即以最小的特征量作为响应函数值- 返回值为一个 n x 1 x 2 的浮点型 Numpy 数组, 包含了所有提取到的角点坐标, 一般使用
corner = np.int32(corner[:, 0, :])将其转化为一个按行保存坐标的整数数组
Canny 边缘提取
https://docs.opencv.org/4.9.0/da/d22/tutorial_py_canny.html
OpenCV 提供以下函数用于 Canny 边缘提取
cv.Canny(image, threshold1, threshold2, *, apertureSize, L2gradient)image被检测的图片数组, 要求时单通道的一般为灰度图threshold1浮点数, 梯度下阈值, 值越小边缘越连续, 但可能引入噪声, 对于 8 位色深一般取 100threshold2浮点数, 梯度上阈值, 值越大噪声越少, 但可能会缺少提取到的边缘, 对于 8 位色深一般取 200apertureSize整数, 即 Sobel 算子大小L2gradient布尔值, 计算梯度的范数, 默认为False, 使用 p-1 范数即梯度为两个方向偏导绝对值之和,True时使用 p-2 范数即梯度为两个方向导数的平方和再开根号- 返回值为 8 位单通道图片, 其中非边缘点像素值为 0, 边缘点像素值为 255, 与输入图片大小相同
霍夫变换
https://docs.opencv.org/4.9.0/d6/d10/tutorial_py_houghlines.html
https://docs.opencv.org/4.9.0/da/d53/tutorial_py_houghcircles.html
OpenCV 提供以下函数用于霍夫 (Hough) 变换
cv.HoughLines(image, rho, theta, threshold)霍夫直线变换image用于提取直线的 8 位单通道图片, 通常经过 Canny 边缘提取的结果rho距离分辨率, 单位为像素, 一般为 1theta角度分辨率, 单位为弧度, 一般为 , 即threshold标记阈值, 仅当直线上的像素个数超过此阈值才认为是直线, 一般为 200- 返回值为一个元组
((rho, theta), votes)组成的列表 (注意元组的第一个元素也是元组), 当没有匹配结果时返回Nonerho直线到原点 (左上角) 的垂直距离theta直线到原点垂线与 X 轴的夹角votes直线上的像素数- 因此匹配直线的表达式为
cv.HoughLinesP(image, rho, theta, threshold)基于概率的霍夫变换, 具有更快的速度- 参数含义与霍夫变换相同, 但一般需要调小阈值参数
threshold - 返回值为一个元组
((x1, y1, x2, y2))表示提取到直线的起点与终点 (不一定是完整直线)
- 参数含义与霍夫变换相同, 但一般需要调小阈值参数
cv.HoughCircles(image, method, dp, minDist, *, param1, param2, minRadius, maxRadius)霍夫圆变换image用于提取圆的 8 位单通道图片, 可以直接传入灰度图使用内置的 Canny 边缘提取method霍夫变换匹配方法, 可用值如下cv.HOUGH_GRADIENT标准的霍夫圆变换, 一般使用此方法即可cv.HOUGH_GRADIENT_ALT改进型霍夫圆变换, 具有更高的准确率
dp圆心位置分辨率相对原始大小的除数, 因此取 1 时分辨率为图上每个像素, 取 2 时仅由原图一半, 一般取 1minDist允许两个圆圆心的最近距离, 过小将降低准确率, 过大将导致部分圆被忽略, 一般取 20param1第一边缘检测参数, 即边缘上阈值, 对于cv.HOUGH_GRADIENT一般取 100, 对于cv.HOUGH_GRADIENT_ALT一般取 300param2第二边缘检测参数- 对于
cv.HOUGH_GRADIENT, 为圆上的像素个数阈值, 取值越小速度越快但可能误差越大, 一般取 50 - 对于
cv.HOUGH_GRADIENT_ALT为 0 到 1 的浮点数, 即要求匹配圆的圆度, 一般取 0.9 或 0.8 (匹配小圆)
- 对于
minRadius圆的最小半径, 默认为 0maxRadius圆的最大半径, 当传入负数时,cv.HOUGH_GRADIENT方法将仅寻找原点而不寻找半径- 返回值为列表
[[(x, y, radius), (x, y, radius), ...]], 注意实际结果在列表的第二层中, 因此一般使用circles[0]提取实际结果
各条结果中x, y即圆心位置,radius即半径, 当没有匹配结果时返回None
通常绘制霍夫直线变换结果直线的代码如下
lines = cv.HoughLines(edge, 1, np.pi / 180, 200)
back = 100
forward = 1000
for it in lines:
rho, theta = it[0]
a = np.cos(theta)
b = np.sin(theta)
# 找出直线与原点的垂足
x0 = rho * a
y0 = rho * b
# 由垂足分别向前后延伸指定距离
x1 = int(x0 - b * back)
x2 = int(x0 + b * forward)
y1 = int(y0 + a * back)
y2 = int(y0 - a * forward)
axe.plot((x1, x2), (y1, y2))
plt.show()
通常绘制霍夫圆变换结果圆的代码如下
# 使用 matplotlib.patches 绘制圆
from matplotlib import patches
circles = cv.HoughCircles(img, cv.HOUGH_GRADIENT_ALT, 1, 20,
param1 = 300,
param2 = 0.9)
for it in circles[0]:
x, y, r = it
p = patches.Circle((x, y), r, color = 'b', fill = False)
axe.add_patch(p)