图像处理
一、基础概念
1.几何变换
图像的几何变换是改变图像的几何属性,如位置、大小、形状和方向等的变换。
常见的几何变换包括平移、旋转、缩放、翻转和错切等。
平移是将图像在方向上移动一定的距离。
旋转是以图像的中心或指定的点为中心,将图像旋转一定的角度。
缩放是改变图像的大小,可以沿着水平方向和垂直方向进行不同比例的缩放。
翻转是将图像在水平方向或垂直方向上进行对称变换。
错切则是在一定方向上拉伸或压缩图像。
仿射变换可以看作是平移、旋转、缩放、翻转和错切的综合,仿射变换保持了二维图形的“平直性”和“平行性”,即直线在变换后仍然是直线,平行线在变换后仍然是平行线。
透视变换是三维空间上的非线性变换,可以看作是仿射变换的更一般形式,透视变换通过一个3x3的变换矩阵将原图投影到一个新的视平面
2.图像滤波
图像滤波
图像滤波是图像预处理操作,简单的讲,就是一幅图像通过滤波器得到另一幅图像。
图像滤波目的是在尽量保留图像细节特征的条件下,对目标图像的噪声进行抑制。
常见的图像滤波方法包括均值滤波、中值滤波和高斯滤波等。
均值滤波是一种简单的线性滤波方法,通过对图像中的像素进行平均操作,实现图像的平滑。
中值滤波是一种非线性滤波方法,通过对图像中的像素进行排序,选择中间值作为输出值,实现图像的去噪。
高斯滤波是一种基于高斯函数的线性滤波方法,通过对图像中的像素进行加权平均操作,实现图像的平滑。
滤波器(wave filter)
在图像处理中,滤波器,也叫卷积核 Convolutional Kernel是一种用于平滑图像、消除噪声或提取特定特征的工具。滤波的过程,称为卷积convolution。它通过在原始图像上滑动并卷积,利用相邻像素的值来确定每个像素的最终输出。
卷积核的大小一般为奇数,如3 * 3,5 * 5,7 * 7。
一是为了保证锚点anchor(卷积核的中心),刚好在正中间,方便以模块中心为标准进行滑动而不会发生偏移。
二是为了保证填充padding时,图像的两边依然对称。
一般情况下,卷积核越大,感受野越大,看到的图片信息越多,所获得的全局特征越好。但大的卷积核会导致计算量的暴增,计算性能也会降低。
输出图片尺寸计算公式
计算公式:N = (W - F + 2P) / S + 1
N(New)表示输出图像大小
W(Width)表示原图宽度
F(Filter)表示卷积核大小
P(Padding)表示扩充尺寸
S(Step)表示步长大小,默认为1
3.形态学处理(Morphological Processing)
- 基于形状对图像进行处理的图像处理方法。
- 利用一种特殊的结构元素(可以理解为卷积核)来测量或提取输入图像中相应的形状或特征
- 针对二进制图像,因此需要对图片做二值化处理
- 形态学处理包括腐蚀、膨胀、开运算(先腐蚀后膨胀)和闭运算(先膨胀后腐蚀)
腐蚀操作:将一个对象的边界向内收缩,从而消除小物体或分离相连的物体。
膨胀操作:将一个对象的边界向外扩张,从而填补物体内部的空洞或连接相邻的物体。
开运算:可以用来消除小物体、在纤细点处分离物体、平滑较大物体的边界的同时并不明显改变其面积。
闭运算:可以用来填充物体内细小空洞、连接邻近物体、平滑其边界的同时并不明显改变其面积。
图像二值化:将图像中的每个元素均变为0或255
4.图像轮廓处理
图像轮廓是图像中不同颜色或亮度相邻区域之间的边界,也可以理解为将边缘连接起来形成的一个整体,本质上是具有相同颜色或强度的连续点的曲线。
查找图像轮廓前
- 需要对图像进行二值化处理或Canny边缘检测
- 需要对原图像进行深拷贝
- 尽量将图片做成黑底白字更易于轮廓的查找
5.特征检测
特征图像或视频中有意义的信息,如边缘、角点、斑点、纹理等。
特征检测的应用:图像搜索、图像拼接等。
角点:灰度梯度最大值对应的像素、线条交点、极值点
二、opencv-python几何变换函数
1.图像缩放
resize(src, dst, dsize, fx, fy, interpolation)
fx, fy 分别是 x, y 轴的缩放因子
interpolation | 插值算法 |
---|---|
INTER_NEAREST | 近邻算法,速度快,效果差 |
INTER_LINEAR | 近邻算法,速度快,效果差 |
INTER_CUBIC | 三次插值,原图中的16个点 |
INTER_AREA | 效果最好 |
# 用dsize
import cv2 as cv
img = cv.imread("D://dev/opencv/sources/samples/data/starry_night.jpg")
img_resize = cv.resize(img, (400, 400))
cv.imshow("img", img)
cv.imshow("img_resize", img_resize)
cv.waitKey(0)
# 用缩放因子fx,fy
import cv2 as cv
img = cv.imread("D://dev/opencv/sources/samples/data/starry_night.jpg")
img_resize = cv.resize(img, None, fx=0.3, fy=0.3)
cv.imshow("img", img)
cv.imshow("img_resize", img_resize)
cv.waitKey(0)
2.图像翻转
flip(img, flipCode)
flipCode | 翻转方向 |
---|---|
flipCode == 0 | 上下翻转 |
flipCode > 0 | 左右翻转 |
flipCode < 0 | 上下左右翻转 |
import cv2 as cv
img = cv.imread("D://dev/opencv/sources/samples/data/starry_night.jpg")
img_flip = cv.flip(img, 0)
cv.imshow("img", img)
cv.imshow("img_flip", img_flip)
cv.waitKey(0)
3.图像旋转
rotate(img, rotateCode)
rotateCode | 旋转角度 |
---|---|
ROTATE_90_CLOCKWISE | 逆向旋转90° |
ROTATE_180 | 旋转180° |
ROTATE_90_COUNTERCLOCKWISE | 正向旋转90° |
import cv2 as cv
img = cv.imread("D://dev/opencv/sources/samples/data/starry_night.jpg")
img_rotate = cv.rotate(starry_night, cv.ROTATE_90_CLOCKWISE)
cv.imshow("img", img)
cv.imshow("img_rotate", img_rotate)
cv.waitKey(0)
4.仿射变换
warpAffine(src, M, dsize, flags, borderMode, borderValue)
M:变换矩阵
flags:插值算法
后两个参数是原图移到边界之外的部分怎么处理
borderMode | 边界模式 |
---|---|
cv.BORDER_CONSTANT(default) | 常值模式 |
cv.BORDER_REPLICATE | 复制近邻元素 |
cv.BORDER_REFLECT | 镜像填充 |
borderValue(在常值模式时, (0, 0, 0)默认值)
import cv2 as cv
import numpy as np
img = cv.imread("D://dev/opencv/sources/samples/data/starry_night.jpg")
M = np.array([[1, 0, 100], [0, 1, 50]], dtype=np.float32)
h, w, ch = img.shape
img_Affine = cv.warpAffine(img, M, [w, h])
cv.imshow("img", img)
cv.imshow("img_Affine", img_Affine)
cv.waitKey(0)
5.获取旋转变换矩阵
getRotationMatrix2D(center, angle, scale)
center:旋转的中心点。
angle:旋转的角度,以度为单位。
scale:可选参数,缩放因子。如果提供了这个参数,那么在旋转的同时还会进行缩放。
import cv2 as cv
img = cv.imread("D://dev/opencv/sources/samples/data/starry_night.jpg")
M = cv.getRotationMatrix2D([100, 100], 45, 0.5)
h, w, ch = img.shape
img_Affine = cv.warpAffine(img, M, [w, h])
cv.imshow("img", img)
cv.imshow("img_Affine", img_Affine)
cv.waitKey(0)
retval, mat = cv.getAffineTransform(src, dst)
src:源图像中三角形顶点的坐标,这是一个点的数组。
dst:目标图像中相应三角形顶点的坐标,这也是一个点的数组。
import cv2 as cv
import numpy as np
src = np.array([[100, 100], [100, 200], [200, 200]], dtype=np.float32)
dst = np.array([[200, 200], [200, 300], [300, 300]], dtype=np.float32)
M = cv.getAffineTransform(src, dst)
print(M)
6.透视变换
warpPerspective(src, M, dsize, flags, borderMode, borderValue)
import cv2 as cv
import numpy as np
img = cv.imread("D://dev/opencv/sources/samples/data/right.jpg")
src_P = np.float32([[360, 105], [300, 315], [525, 345], [585, 120]])
dst_P = np.float32([[0, 0], [0, 459], [612, 459], [612, 0]])
M_P = cv.getPerspectiveTransform(src_P, dst_P)
img_Perspective = cv.warpPerspective(img, M_P, [612, 459])
cv.imshow("img", img)
cv.imshow("img_Perspective", img_Perspective)
cv.waitKey(0)
三、opencv-python滤波函数
1.滤波函数
filter2D(src, ddepth, kernel, anchor, delta, borderType)
src的深度 | ddepth(输出图像位深) |
---|---|
输出图像与输入图像具有相同的深度 | -1 |
CV_8U | -1、CV_16S、CV_32F、CV_64F |
CV_16U、CV_16S | -1、CV_32F、CV_64F |
CV_32F | -1、CV_32F、CV_64F |
CV_64F | -1、CV_64F |
anchor默认为(-1, -1),表示卷积核的中心是锚点。
delta是偏移量
# 低通滤波:去除噪声,平滑图像
# 高通滤波:边缘检测,识别轮廓
import cv2 as cv
import numpy as np
squirrel = cv.imread("D://dev/opencv/sources/samples/data/squirrel_cls.jpg")
# 均值滤波卷积核
kernal = np.ones((5, 5), dtype=np.float32) / 25
squi_filter = cv.filter2D(squirrel, -1, kernal)
cv.imshow("squirrel", squirrel)
cv.imshow("squi_filter", squi_filter)
cv.waitKey(0)
2.低通滤波
方盒滤波
boxFilter(src, ddepth, ksize, anchor, normalize, borderType)
blur(src, ksize, anchor, borderType)
ksize:滤波器的大小,必须是奇数
normalize == True 时, 滤波器是均值滤波器(默认值)
normalize == False时,滤波器是方盒滤波器
squi_filter = cv.blur(squirrel, (5, 5))
高斯滤波
GaussianBlur(src, ksize, sigmaX, sigmaY, borderType)
sigmaX:X方向上的标准差。
sigmaY:Y方向上的标准差,默认等于sigmaX。
# ksize越大,sigma越大,模糊处理越大
# 高斯滤波对高斯噪声滤波效果最好
squi_filter = cv.GaussianBlur(squirrel, (3, 3), sigmaX=1)
中值滤波(非线性)
medianBlur(src, ksize, borderType)
# 中值滤波对椒盐噪声效果很好
squi_filter = cv.medianBlur(squirrel, 5)
双边滤波(非线性)
bilateral(src, d, sigmaColor, sigmaSpace, borderType)
d:滤波模板的大小。
sigmaColor:颜色空间的滤波的标准差。
sigmaSpace:空间坐标的滤波的标准差。
# 双边滤波,可以保留边缘,同时平滑边缘内的信息
squi_filter = cv.bilateralFilter(squirrel, 7, 20, 50)
3.高通滤波
Sobel算子
抗噪声效果好,内含高斯滤波,常用于边缘检测。
Sobel(src, ddepth, dx, dy, ksize)
squirrel = cv.imread("D://dev/opencv/sources/samples/data/squirrel_cls.jpg", cv.IMREAD_GRAYSCALE)
# 对x,y方向均求导
squi_filterx = cv.Sobel(squirrel, cv.CV_64F, 1, 0, ksize=3)
squi_filtery = cv.Sobel(squirrel, cv.CV_64F, 0, 1, ksize=3)
squi_filter = cv.add(squi_filterx, squi_filtery)
Scharr算子
与Sobel类似,但其kernel不同且只支持3 * 3的kernel
只能求单方向的导数
cv.Scharr(src, ddepth, dx, dy)
# 也可以使用Sobel的API将ksize设为-1
squi_filterx = cv.Scharr(squirrel, cv.CV_64F, 1, 0)
squi_filtery = cv.Scharr(squirrel, cv.CV_64F, 0, 1)
squi_filter = cv.add(squi_filterx, squi_filtery)
Laplacian算子
可以同时求两个方向上的导数
对噪声敏感,使用前需要去噪
dst = cv2.Laplacian(src, ddepth, ksize)
squi_filter = cv.Laplacian(squirrel, cv.CV_64F, ksize=5)
Canny边缘检测
5 * 5高斯滤波降噪
Sobel算子从四个方向计算图像的梯度(0°/45°/90°/135°)
非极大值抑制(取局部最大值)
阈值计算
Canny(image, threshold1, threshold2)
squi_filter = cv.Canny(squirrel, 150, 200)
四、opencv-python形态学处理
1.全局二值化
threshold(src, thresh, maxval, thresholdType)
src:最好是灰度图像
thresh:阈值,用于分类像素值。
maxval:当像素值超过了阈值(或者小于阈值,根据阈值类型来决定)所赋予的值。
thresholType | 阈值类型 |
---|---|
cv.THRESH_BINARY | 正向二值化 |
cv.THRESH_BINARY_INV | 反向二值化 |
cv.THRESH_TRUNC | 截断trancate:高于阈值的部分置为阈值 |
cv.THRESH_TOZERO | 低于阈值的部分置零,其余不变 |
cv.THRESH_TOZERO_INV | 高于阈值的部分置零,其余不变 |
import cv2 as cv
img = cv.imread("D://dev/opencv/sources/samples/data/squirrel_cls.jpg",cv.IMREAD_GRAYSCALE)
ret, dst = cv.threshold(img, 90, 255, cv.THRESH_BINARY)
cv.imshow("img", img)
cv.imshow("dst", dst)
cv.waitKey(0)
2.自适应阈值
由于光照不均匀以及阴影的存在,只有一个阈值会导致阴影处的白色被置为黑色
adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
adaptiveMethod:自适应方法,决定如何计算阈值。有两种常见的方法:
cv.ADAPTIVE_THRESH_MEAN_C:阈值是邻域内像素的平均值减去常数C。
cv.ADAPTIVE_THRESH_GAUSSIAN_C:阈值是邻域内像素值的加权和减去常数C,权重通过高斯窗口确定。
blockSize:邻域大小(用于计算阈值的区域大小),必须是奇数。
C:从均值或加权均值中减去的常数,可以是正数或负数。
import cv2 as cv
img = cv.imread("D://dev/opencv/sources/samples/data/squirrel_cls.jpg", cv.IMREAD_GRAYSCALE)
dst = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 3, 0)
cv.imshow("img", img)
cv.imshow("dst", dst)
cv.waitKey(0)
3.获取形态学处理的结构元素
getStructuringElement(shape, ksize)
shape | 结构元素的形状 |
---|---|
cv.MORPH_RECT | 矩形结构元素 |
cv.MORPH_ELLIPSE | 椭圆结构元素 |
cv.MORPH_CROSS | 十字形结构元素 |
import cv2 as cv
kernel = cv.getStructuringElement(cv.MORPH_CROSS, (7, 7))
print(kernel)
4.腐蚀和膨胀
erode(src, kernel, iterations)
dilate(src, kernel, iterations)
iterations:操作的次数,默认是1
# 图片可以选择黑底白字的文字图片,实践一下效果
kernel = np.ones((1, 1), dtype=np.uint8)
dst = cv.erode(src, kernel)
5.形态学高级操作
运算 | 作用结果 |
---|---|
开运算 | 去除大图形外的小图形 |
闭运算 | 去除大图形内的小图形 |
形态学梯度 | 捕获图形边缘 |
顶帽运算 | 去除小图形外的大图形 |
黑帽运算 | 去除蕴含小图形的大图形 |
开运算可以先利用腐蚀去除噪点,再膨胀恢复
闭运算可以先利用膨胀去掉噪点,再腐蚀恢复
形态学梯度是原图减去腐蚀图片,作用结果是捕获边缘
顶帽运算是原图减去开运算
黑帽运算是原图减去闭运算
morphologyEx(src, op, kernel)
op | 形态学操作的类型 |
---|---|
cv.MORPH_ERODE | 腐蚀 |
cv.MORPH_DILATE | 膨胀 |
cv.MORPH_OPEN | 开运算 |
cv.MORPH_CLOSE | 闭运算 |
cv.MORPH_GRADIENT | 形态学梯度 |
cv.MORPH_TOPHAT | 顶帽 |
cv.MORPH_BLACKHAT | 黑帽 |
五、opencv-python轮廓处理
1.查找轮廓
findContours(src, mode, method)
mode | 轮廓检索模式 |
---|---|
cv.RETR_EXTERNAL=0 | 只检索最外层的轮廓 |
cv.RETR_LIST=1 | 检索所有的轮廓并从里到外,从右到左的顺序保存到列表中 |
cv.RETR_CCOMP=2 | 每层最多两级 |
cv.RETR_TREE=3 | 按树形存储轮廓,从右到左,每个里面从外到里 |
method | 轮廓近似方法 |
---|---|
cv.CHAIN_APPROX_NONE | 存储所有的轮廓点 |
cv.CHAIN_APPROX_SIMPLE | 仅存储角点:水平、垂直和对角线方向上的端点 |
返回值 | |
---|---|
contours | 一个Python列表,其中每个元素都是代表图像中一个轮廓的点集 |
hierarchy | 有关图像顶级轮廓、轮廓之间的关系等信息的数组,但在很多应用中通常不使用 |
2.绘制轮廓
drawContours(src, contours, contoursIdx, color, thickness)
src | 通常是原始图像的一个深拷贝,以避免修改原始图像 |
---|---|
contours | 由 cv2.findContours 获得 |
contourIdx | 指定绘制哪个轮廓的索引,如果要绘制所有轮廓,则将其设置为 -1 |
color | 轮廓的颜色 |
thickness | 轮廓线条的厚度。如果指定为负数(例如 -1),则轮廓内部将被填充 |
3.计算轮廓周长和面积
arcLength(curve,closed)
contourArea(contour[index])
import cv2 as cv
import numpy as np
# 绘制一个二值图像作为实例
src = 255 * np.ones((500, 500, 3), dtype=np.uint8)
cv.rectangle(src, (100, 100), (400, 300), (110, 0, 110), -1)
cv.circle(src, (250, 350), 100, (255, 255, 0), -1)
img = src.copy()
# 转为灰度
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 二值化
ret, bina = cv.threshold(gray, 180, 255, cv.THRESH_BINARY)
# 用二值化图像查找轮廓
contour, hierarchy = cv.findContours(bina, 1, cv.CHAIN_APPROX_NONE)
# 在原图像的深拷贝里绘制轮廓
cv.drawContours(img, contour, -1, (0, 0, 255), 2)
# 计算轮廓周长和面积
area = cv.contourArea(contour[0])
len = cv.arcLength(contour[0], True)
print(f"轮廓的面积为{area}")
print(f"轮廓的周长为{len}")
cv.imshow("img", img)
cv.waitKey(0)
4.多边形逼近和凸包
多边形逼近:就是通过多边形逼近一个给定的形状,用于简化轮廓的表示或进行形状的匹配
多边形凸包:就是包含给定点集中所有点的最小凸多边形,可以找到一个形状的外壳
approxPloyDP(curve, epsilon, closed)
epsilon精度
convexHull(points, clockwise)
5.最小外接矩阵和最小无旋转外接矩阵
minAreaRect(points)
points:ndarray数组,可以是轮廓
返回值:RotatedRect由起始点、宽和高、旋转角度组成
boundingRect(array)
array:ndarray数组,可以是轮廓
返回值:Rect由起始点、宽和高组成
import cv2 as cv
import numpy as np
def drawShape(img_src, points):
i = 0
while i < len(points):
if i == len(points) - 1:
x, y = points[i][0]
x1, y1 = points[0][0]
cv.line(img_src, (x, y), (x1, y1), (0, 0, 255), 2)
else:
x, y = points[i][0]
x1, y1 = points[i+1][0]
cv.line(img_src, (x, y), (x1, y1), (0, 0, 255), 2)
i += 1
src = cv.imread(r"D:\dev\opencv\sources\samples\data\hand.png")
img = src.copy()
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
ret, bina = cv.threshold(gray, 180, 255, cv.THRESH_BINARY)
contour, hierarchy = cv.findContours(bina, 1, cv.CHAIN_APPROX_NONE)
cv.drawContours(img, contour[0], -1, (0, 255, 0), 2)
e = 20
# 多边形逼近
approx = cv.approxPolyDP(contour[0], e, True)
# 多边形凸包
hull = cv.convexHull(contour[0])
# 最小外接矩阵和最小无旋转外接矩阵
min_rect = cv.minAreaRect(contour[0])
x, y, w, h = cv.boundingRect(contour[0])
box = cv.boxPoints(min_rect)
box = np.int_(box)
cv.drawContours(img, [box], 0, (0, 255, 255), 2)
cv.rectangle(img, (x, y), (x + w, y + h), (255, 0, 255), 2)
drawShape(img, approx)
drawShape(img, hull)
cv.imshow("img", img)
cv.waitKey(0)
6. 行人识别
import cv2 as cv
# import numpy as np
cap = cv.VideoCapture("D://dev/opencv/sources/samples/data/vtest.avi")
# 去除背景
bgsubmog = cv.createBackgroundSubtractorMOG2()
# 形态学结构元素
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
while True:
ret, frame = cap.read()
if ret:
# 灰度
cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
# 高斯模糊去噪
blur = cv.GaussianBlur(frame, (3, 3), 5)
# 去背景
mask = bgsubmog.apply(blur)
# 腐蚀
erode = cv.erode(mask, kernel, 2)
# 膨胀
dilate = cv.dilate(erode, kernel, 3)
# 闭操作去黑点
close = cv.morphologyEx(dilate, cv.MORPH_CLOSE, kernel)
# 查找轮廓
contours, hierachy = cv.findContours(close, cv.RETR_TREE,
cv.CHAIN_APPROX_SIMPLE)
for (i, c) in enumerate(contours):
x, y, w, h = cv.boundingRect(c)
if w < 40 and h < 40:
continue
cv.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv.imshow("video", frame)
else:
break
print("video failed to load")
key = cv.waitKey(20)
# Esc的ASCII是27
if key & 0xff == 27:
break
cap.release()
cv.destroyAllWindows()
六、opencv-python特征检测
1.角点检测
Harris角点检测
- 光滑地区:无论从哪个方向移动,衡量的系数均不变
- 边缘地区:存在一个方向移动衡量系数不变,而垂直于这个方向剧烈变化
- 交点处:无论往哪个方向移动,衡量系统都剧烈变化
cornerHarris(src, blockSize, ksize, k)
src:输入图像,应该是灰度图,且数据类型为float32。
blockSize:角点检测中要考虑的领域大小。
ksize:Sobel求导中使用的窗口大小。
k:Harris角点检测方程中的自由参数,取值参数为[0.04,0.06]。
import cv2 as cv
blocksize = 2
ksize = 3
k = 0.04
src = cv.imread(r"D:\dev\opencv\sources\samples\data\chessboard.png")
img = cv.resize(src, None, fx=0.3, fy=0.3)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
dst = cv.cornerHarris(gray, blocksize, ksize, k)
img[dst > 0.01 * dst.max()] = [0, 0, 255]
cv.imshow("img", img)
cv.waitKey(0)
Shi-Tomasi角点检测
Shi-Tomasi角点检测是Harris角点检测的改进,其角点测算稳定性不会受到经验值k 的影响
goodFeaturesToTrack(image,…)
parameters | 参数说明 |
---|---|
image | 输入灰度图像,且数据类型通常为 np.uint8 或 np.float32 |
maxCorners | 返回的最大角点数目,0表示无限大 |
qualityLevel | 角点质量等级,取小于1的正浮点数,一般为0.01~0.1 |
minDistance | 角点之间的最小欧氏距离,用于避免检测到相邻的角点 |
corners | 可选参数,用于存储检测到的角点位置 |
mask | 可选参数,指定一个掩码图像,用于限制角点检测的区域 |
blockSize | 角点检测中要考虑的邻域大小 |
useHarrisDetector | 默认是False使用Shi-Tomasi角点检测 |
k | 使用Harris算法的时候设置 |
import cv2 as cv
import numpy as np
maxCorners = 1000
ql = 0.01
minDistance = 35
src = cv.imread(r"D:\dev\opencv\sources\samples\data\chessboard.png")
img = cv.resize(src, None, fx=0.3, fy=0.3)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
corners = cv.goodFeaturesToTrack(gray, maxCorners, ql, minDistance)
corners = np.int_(corners)
for i in corners:
# 转成一维数组
x, y = i.ravel()
cv.circle(img, (x, y), 3, (0, 0, 255), -1)
cv.imshow("img", img)
cv.waitKey(0)
2.关键点检测
SIFT(Scale-Invariant Feature Transform,尺度不变特征变换)
当图像尺度发生变化时,Harris算法可能会在放大或缩小的图片中失效从而检测不出角点,这个时候SIFT就很重要
import cv2 as cv
src = cv.imread(r"D:\dev\opencv\sources\samples\data\chessboard.png")
img = cv.resize(src, None, fx=0.5, fy=0.5)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 创建SIFT对象
sift = cv.SIFT_create()
# 对整张图进行检测关键点和描述子
keypoints, discriptors = sift.detectAndCompute(gray, None)
cv.drawKeypoints(gray, keypoints, img)
cv.imshow("img", img)
cv.waitKey(0)
SURF(Speeded Up Robust Features,加速鲁棒特征)
改善了SIFT的速度
surf = cv.xfeatures2d.SURF_create()
ORB(Oriented FAST and Rotated BRIEF)
实时检测,但是精度低,适用于大批量实时检测
import cv2 as cv
src = cv.imread(r"D:\dev\opencv\sources\samples\data\chessboard.png")
img = cv.resize(src, None, fx=0.5, fy=0.5)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 创建ORB对象
orb = cv.ORB_create()
# 对整张图进行检测关键点和描述子
keypoints, discriptors = orb.detectAndCompute(gray, None)
cv.drawKeypoints(gray, keypoints, img)
cv.imshow("img", img)
cv.waitKey(0)
3.特征匹配
BF(Brute-Force,暴力特征匹配)
一张图片的每个关键点描述子与另一张图片每一个描述子进行匹配
import cv2 as cv
img1 = cv.imread(r"D:\dev\opencv\sources\samples\data\opencv-logo-white.png")
img2 = cv.imread(r"D:\dev\opencv\sources\samples\data\opencv-logo.png")
gray1 = cv.cvtColor(img1, cv.COLOR_BGR2GRAY)
gray2 = cv.cvtColor(img2, cv.COLOR_BGR2GRAY)
sift = cv.SIFT_create()
kp1, des1 = sift.detectAndCompute(gray1, None)
kp2, des2 = sift.detectAndCompute(gray2, None)
bf = cv.BFMatcher(cv.NORM_L1)
match = bf.match(des1, des2)
img3 = cv.drawMatches(img1, kp1, img2, kp2, match, None)
cv.imshow("img3", img3)
cv.waitKey(0)
FLANN(Fast Approximate Nearest Neighbor Search Library,最快近邻区特征匹配)
速度更快,精度较差