OpenCV-Python学习笔记

2 OpenCV中的Gui特性

2-1 图像入门

目标

  • 学习如何读取图像、显示图像和保存图像

  • 学习api函数:cv.imread()、cv.imshow()、cv.imwrite()

  • 学习使用Matplotlib显示图像

使用OpenCV

读取图像

在OpenCV中,使用函数cv.imread()读取图像。


img = cv.imread('lena.png', cv.IMREAD_COLOR)

注意:即使图像路径错误,也不会引发任何错误,但是print(img)会给出None。

显示图像

使用函数cv.imshow()显示图像。


cv.imshow('image', img)
cv.waitKey(0)
cv.destroyAllWindows()

保存图像

使用函数cv.imwrite()保存图像。


cv.imwrite('image.png', img)

示例:


import cv2 as cv
import numpy as np 
from matplotlib import pyplot as pltimg = cv.imread('../../images/lena.png', 0)
cv.imshow('image', img)k = cv.waitKey(0) & 0xFF
if k == 27:cv.destroyAllWindows()
elif k == ord('s'):cv.imwrite('lena_gray.png', img)cv.destroyAllWindows()

其中,函数cv.waitKey()是一个与键盘绑定的函数,参数以毫秒为单位;函数cv.destroyAllWindows()会销毁创建的所有窗口。

使用Matplotlib

Matplotlib是Python的绘图库,可以显示、缩放和保存图像。



import matplotlib.pyplot as pltplt.imshow(img, cmap = 'gray', interpolation = 'bicubic')
plt.xticks([]), plt.yticks([])
plt.show()

注意:OpenCV是以BGR模式加载图像的,而Matplotlib是以RGB模式显示图像的。

2-2 视频入门

目标

  • 学习如何读取视频、显示视频和保存视频

  • 学习如何从摄像头获取图像并显示

  • 学习api函数:cv.VideoCapture(),cv.VideoWriter()

从摄像头中捕获视频

要从摄像头中捕获视频,需要先创建一个VideoCapture对象。


import cv2 as cvcap = cv.VideoCapture(0)if not cap.isOpened():print("Cannot open camera")exit()while True:ret, frame = cap.read()if not ret:print("Can't receive frame.")breakgray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)cv.imshow('frame', gray)if cv.waitKey(1) == ord('q'):breakcap.release()
cv.destroyAllWindows()
  • 使用cap.isOpened()可以检查cap是否已经初始化,否则使用cap.open()进行初始化。

  • cap.read()返回一个布尔值,如果正确读取了帧,将返回True。

  • 还可以使用cap.get(propId)方法访问视频的属性,propId为0到18。使用cap.set(propId, value)对指定属性进行设置。例如可以通过cap.get(cv.CAP_PROP_FRAME_WIDTH)和cap.get(cv.CAP_PROP_FRAME_HEIGHT)获取帧的宽和高。如果想修改宽和高,只需要ret = cap.set(cv.CAP_PROP_FRAME_WIDTH, 320)和ret = cap.set(cv.CAP_PROP_FRAME_HEIGHT, 240)。

从文件中读取视频

与从摄像头捕获一样,只需用视频文件名更改摄像头的索引即可。另外,在显示帧的时候,需要使用适当的时间cv.waitKey()。如果太小,视频就会播放非常快,如果太大,视频就会播放很慢。


import cv2 as cvcap = cv.VideoCapture('../../images/video.mp4')while cap.isOpened():ret, frame = cap.read()if not ret:print("Can't receive fram.")breakgray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)cv.imshow('frame', gray)k = cv.waitKey(25)if k == ord('q'):breakcap.release()
cv.destroyAllWindows()

注意:需要确保正确安装了ffmpeg。

保存视频

保存视频,首先创建一个VideoWriter对象,需要指定输出文件名,FourCC代码,帧率和帧大小。


import cv2 as cv
import ospath = '../Resources/out'listImg = os.listdir(path)
img_path = os.path.join(path, listImg[0])
img = cv.imread(img_path, 1)
h, w = img.shape[:2]
fourcc = cv.VideoWriter_fourcc('M','P','4','V')
out = cv.VideoWriter('../../images/output.mp4', fourcc, 15, (w, h), isColor = True)for i in range(len(listImg)):img_name = listImg[i][:6] + str(i+1) + '.jpg'img_path = os.path.join(path, img_name)frame = cv.imread(img_path, 1)#ret, frame = cv.threshold(frame, 127, 255, cv.THRESH_BINARY)#frame = cv.Canny(frame, 50,100)if frame is None:print("Can't read image.")breakout.write(frame)    cv.imshow('frame',frame)k = cv.waitKey(1)if k == ord('q'):breakout.release()
cv.destroyAllWindows()

2-3 OpenCV绘图

目标

  • 学习OpenCV绘制不同的几何形状

  • 学习api函数:cv.line()、cv.circle()、cv.rectangle()、cv.ellipse()、cv.putText()等。

绘图的常见参数

  • img —— 绘制形状的图像

  • color —— 形状的颜色

  • thickness —— 线条的厚度,如果是-1,将绘制填充的形状

  • lineType —— 线的类型

画线

使用函数cv.line()绘制直线。


cv.line(img, (50,50), (462,50), (0,255,0), 2)

画矩形

使用函数cv.rectangle()绘制矩形。


cv.rectangle(img, (50, 100), (462, 300), (0,255,0), 2)

画圆

使用函数cv.circle()绘制圆。


cv.circle(img, (256,256), 50, (0,0,255), 2)

画椭圆

使用函数cv.ellipse()绘制椭圆。


cv.ellipse(img, (256, 356), (80,40), 0, -180, 180, (255,0,0), 2)

画多边形

使用函数cv.polylines()绘制多边形。首先需要顶点的坐标,将这些点组成形状为ROWSx1x2的数组,其中ROWS是顶点数,并且其类型应为int32。


pts = np.array([[10,5],[20,30],[70,20],[50,10]], np.int32)
pts = pts.reshape((-1,1,2))
cv.polylines(img,[pts],False,(0,255,255))

在图像中添加文本

使用函数cv.putText()绘制文本。


font = cv.FONT_HERSHEY_SIMPLEX
cv.putText(img, 'China', (10,500), font, 4, (255,255,255), 2, cv.LINE_AA)

3 OpenCV核心操作

3-1 图像基本操作

目标

  • 学习访问和修改像素值

  • 访问图像的属性

  • 设置ROI区域

  • 分离和合并图像

  1. 使用OpenCV访问和修改像素值

访问像素值

通过行和列坐标来访问像素值。BGR图像将返回一个数组[b,g,r],灰度图像返回对应的灰度值。


#蓝色通道
b = img[100,100,0]
#绿色通道
g = img[100,100,1]
#红色通道
r = img[100,100,2]

修改像素值


img[100,100] = [255,255,255]

  1. 使用Numpy访问和修改像素值

访问像素值

使用array.item()访问像素值。


#访问蓝色通道
img.item(10,10,0)

修改像素值

使用array.itemset()修改像素值。


#修改红色通道
img.itemset((10,10,2),100)

  1. 访问图像属性

图像的属性包括行数列数通道数图像的数据类型像素数等。

使用img.shape访问图像的行数、列数和通道数:


img.shape

注意:如果是灰度图像,只返回元组仅包含行数和列数。

使用img.size访问像素总数:


img.size

使用img.dtype访问图像数据类型:


img.dtype

  1. 设置图像感兴趣区域ROI


ROI = img[100:200,100:200]

  1. 拆分和合并图像通道

分别使用cv.split()和cv.merge()拆分和合并图像。


b,g,r = cv.split(img)
img = cv.merge(b,g,r)

  1. 为图像设置边框

使用函数cv.copyMakeBorder()填充边界。


dst = cv.copyMakeBorder(img, 10,10,10,10,cv.BORDER_CONSTANT, value = (0,0,255))

3-2 图像的算术运算

目标

  • 学习图像的加法、减法和按位等运算

  • 学习api函数:cv.add()、cv.addWeighted()

图像加法

两幅图像相加,需要保证两幅图像具有相同的深度和类型,或者第二幅图像是一个标量。可以使用OpenCV函数cv.add()或使用Numpy对两幅图像直接进行相加。但是,OpenCV的加法属于饱满运算,当和大于最大值时,就取最大值;而Numpy的加法属于模运算,当和大于最大值时,就取和除以最大值的余数。


x = np.uint8([250])
y = np.uint8([10])cv.add(x,y)    # 250+10=260 -> 255
x + y          # 250+10=260%256 = 4

图像融合(image blending)

图像融合实质也是图像的加法运算,但是赋给图像的权重不同:

在OpenCV中,使用函数cv.adddWeighted()对两幅图像进行(融合)加权求和。


import cv2 as cv
import numpy as np 
from matplotlib import pyplot as plt# 加法运算
x = np.uint8([250])
y = np.uint8([10])
print(x)
print(y)print(cv.add(x,y))
print(x+y)img1 = cv.imread('../../images/lena.jpg')
img2 = cv.imread('../../images/baboon.jpg')# 图像融合
dst = cv.addWeighted(img1, 0.5, img2, 0.5, 0)img1 = cv.cvtColor(img1, cv.COLOR_BGR2RGB)
img2 = cv.cvtColor(img2, cv.COLOR_BGR2RGB)
dst = cv.cvtColor(dst, cv.COLOR_BGR2RGB)plt.subplot(221),plt.imshow(img1),plt.title('img1'),plt.xticks([]),plt.yticks([])
plt.subplot(222),plt.imshow(img2),plt.title('img2'),plt.xticks([]),plt.yticks([])
plt.subplot(223),plt.imshow(dst),plt.title('dst'),plt.xticks([]),plt.yticks([])
plt.show()

图像的按位运算

在OpenCV中,按位运算主要有:

  1. 按位与运算

按位与操作,当参与运算的两国逻辑值都为真时,结果才为真。OpenCV使用函数cv.bitwise_and() 对图像进行按位与运算。

注意:

  • 将任意数值N与数值0进行按位与操作,得到的都是数值0。

  • 将任意数值N与数值255进行按位与操作,都会得到数值N本身。

  1. 按位非运算

按位非操作,对元素进行取反。OpenCV使用函数cv.bitwise_not() 对图像进行按位非运算

  1. 按位或运算

按位或操作,当参与运算的两个逻辑值中有一个值为真时,结果就为真。OpenCV使用函数cv.bitwise_or() 对图像进行按位与运算。

  1. 按位异或运算

按位异或操作,当参与 运算的两个逻辑值一个为真,一个为假,结果才为真。OpenCV使用函数cv.bitwise_xor() 对图像进行按位异或运算

4 OpenCV图像处理

4-1 改变颜色空间

目标

  • 学习如何将图像从一个色彩空间转换到另一个色彩空间,如BGR -> GRAY, BGR -> HSV等。

  • 实操——提取视频中的彩色对象

  • 学习API: cv.cvtColor(), cv.inRange()等。

改变颜色空间

  • OpenCV中有超过150种颜色空间转换方法,常用的是BRG -> GRAY和BGR -> HSV等。

  • 对于颜色转换,使用下面cv函数:


cv.cvtColor(input_image, flag)

其中,flag决定转换类型。

从BGR转换到灰度,flag = cv.COLOR_BGR2GRAY

从BGR转换到HSV,flag = cv.COLOR_BGR2HSV

  • 获取其它标记,运行以下命令:


import cv2 as cv
flags = [i for i in dir(cv) if i.startswith('COLOR_')]
print(flags)
  • HSV的色相范围为[0,179],饱和度范围为[0,255],值范围为[0,255]。

实操——对象追踪

实现功能:

追踪视频中的蓝色对象。

步骤:

  1. 获取视频的每一帧

  1. 使用cv.cvtColor()将图片从BGR转换到HSV颜色空间

  1. 设置HSV图片蓝色范围的阈值

  1. 使用cv.inRange()提取蓝色对象

代码:



"""
步骤:1.获取视频的每一帧2.从BGR转换到HSV颜色空间3.设置HSV蓝色范围的阈值4.提取蓝色
"""import cv2 as cv
import numpy as npdef object_track():cap = cv.VideoCapture(0)while(1):# 读取帧_, frame = cap.read()# 转换颜色空间 BGR -> HSVhsv = cv.cvtColor(frame, cv.COLOR_BGR2HSV)# 定义HSV中蓝色的范围lower_blue = np.array([110,50,50])upper_blue = np.array([130,255,255])# 设置HSV的阈值,使得只取得蓝色mask = cv.inRange(hsv, lower_blue, upper_blue)# 将掩膜和图像逐像素相加res = cv.bitwise_and(frame, frame, mask=mask)cv.imshow('frame', frame)cv.imshow('mask', mask)cv.imshow('res', res)k = cv.waitKey(5) & 0xFFif k == 27:breakcap.release()cv.destroyAllWindows()if __name__ == "__main__":object_track()

如何找到要追踪的HSV值?

使用cv.cvtColor(),只需传递想要的BGR值,而不是传递图像。例如,查找绿色的HSV值:


green = np.uint8([[[0,255,0]]])
hsv_green = cv.cvtColor(green, cv.COLOR_BGR2HSV)
print(hsv_green)

4-2 图像的几何变换

目标:

  • 学习图像的几何变换——平移、旋转和缩放等。

  • 学习cv.getPerspectiveTransform()

相关函数:

  1. cv.warpAffine(src, M, dsize[, dst[, flags[, borderMode[, borderValue]]]])

功能:

对图像进行仿射变换。

参数:

参数

说明

src

输入图像

M

变换矩阵,2x3

dsize

输出图像大小,元组形式(width, height)

dst

表示变换操作的输出图像,可选项

flags

插值方法,可选项

borderMode

边界像素方法,可选项,默认值cv.BORDER_REFLECT

borderValue

边界填充值,可选项,默认值为0(黑色填充)

  1. cv.getRotationMatrix2D(center, angle, scale)

功能:

计算2D旋转的仿射矩阵。

参数:

参数

说明

center

基于源图像的旋转中心,形式为(x, y)

angle

旋转角度,正值逆时针旋转,负值顺时针旋转。

scale

缩放因子。

  1. cv.getAffineTransform(src, dst)

功能:

计算3对对应点的仿射变换。

参数:

参数

说明

src

原图相中,三个点的坐标

dst

目标图像中对应三个点的坐标

变换

  • OpenCV提供了两个转换函数:cv.warpAffine()cv.warpPerspective()

  • cv.warpAffine()采用2x3转换矩阵,也叫仿射变换。

  • cv.warpPerspective()采用3x3转换矩阵,也叫透视变换。

缩放

  • 缩放只是调整了图像的大小,OpenCV提供了cv.resize()函数。

  • 可以手动设置图像的大小,也可以指定缩放的因子。

  • 缩小图像首选插值方法:cv.INTER_AREA

  • 放大采用的插值方法:cv.INTER_LINEAR和cv.INTER_CUBIC

  • 默认情况下,缩小和放大都采用cv.INTER_LINEAR



import cv2 as cvdef resize_img0(src, f_x, f_y):res = cv.resize(src, None, fx = f_x, fy = f_y, interpolation=cv.INTER_LINEAR)return resdef resize_img1(src, width, height):res = cv.resize(src, (width, height), interpolation=cv.INTER_CUBIC)return resif __name__ == "__main__":img = cv.imread('../../images/lena.png')select = 1if select == 0:dst = resize_img0(img, 2, 2)else:dst = resize_img1(img, 600, 900)print(img.shape)print(dst.shape)cv.imshow('src', img)cv.imshow('dst', dst)cv.waitKey(0)cv.destroyAllWindows()

平移

  • 平移就是物体在平面上x和y方向上的移动。

  • 假如在x方向移动了tx,在y方向移动了ty,那么平移矩阵M就为:

  • 可以通过np.float32创建平移矩阵M,并把它传递给cv.warpAffine()


import cv2 as cv
import numpy as npdef translate_img(src, x, y):M = np.float32([[1,0,x], [0,1,y]])rows, cols, _ = src.shapedst = cv.warpAffine(src, M, (cols, rows))return dstif __name__=="__main__":img = cv.imread('../../images/lena.png')dst = translate_img(img, 50, 100)cv.imshow('src', img)cv.imshow('dst', dst)cv.waitKey(0)cv.destroyAllWindows()

旋转

1.绕原点旋转,旋转矩阵为:

2.绕任意点旋转,旋转矩阵为:

其中:



import cv2 as cv
import numpy as npdef rotate_img(src, angle):rows, cols, _ = src.shapeM = cv.getRotationMatrix2D((cols/2, rows/2), angle, 0.5)dst = cv.warpAffine(src, M, (cols, rows))return dstif __name__ == "__main__":img = cv.imread('../../images/messi5.jpg')dst = rotate_img(img, -45)cv.imshow('src', img)cv.imshow('dst', dst)cv.waitKey(0)cv.destroyAllWindows()

仿射变换

在仿射变换中,为了获取变换的矩阵,需要分别在原图提取至少三个点的坐标,并在目标图像中提取对应三个点的坐标,然后利用cv.getAffineTransform()创建变换的矩阵,在把变换矩阵传递给cv.warpAffine()进行仿射变换:


import cv2 as cv
import numpy as np
import matplotlib.pyplot as pltdef affine_transformation(src, pt1, pt2):rows, cols, _ = src.shapeM = cv.getAffineTransform(pt1, pt2)print(M)dst = cv.warpAffine(src, M, (cols, rows))return dstif __name__ == "__main__":img = cv.imread('../../images/lena.png')pt1 = np.float32([[50,50], [200,50],[50,200]])pt2 = np.float32([[10,100], [200,50], [100,250]])dst = affine_transformation(img, pt1, pt2)img1 = cv.cvtColor(img, cv.COLOR_BGR2RGB)img2 = cv.cvtColor(dst, cv.COLOR_BGR2RGB)plt.subplot(121), plt.imshow(img1),plt.title('src')plt.subplot(122), plt.imshow(img2),plt.title('dst')plt.show()

透视变换

  • 在源图上提取4个点的坐标,在目标图像上也提取对应4个点的坐标

  • 利用cv.getPerspectiveTransform()获取透视变换的矩阵,然后再传进cv.warpPerspective()进行变换


import cv2 as cv
import numpy as np def perspective_trans(src, pts1, pts2):rows, cols, _ = src.shapeM = cv.getPerspectiveTransform(pts1, pts2)print(M)dst = cv.warpPerspective(src, M, (300, 300))return dstif __name__ == "__main__":img = cv.imread('../../images/sudoku.png')pts1 = np.float32([[56,65],[368,52],[28,387],[389,390]])pts2 = np.float32([[0,0],[300,0],[0,300],[300,300]])dst = perspective_trans(img, pts1, pts2)cv.imshow('src', img)cv.imshow('dst', dst)cv.waitKey(0)cv.destroyAllWindows()

4-3 图像阈值

目标

  • 掌握简单阈值、自适应阈值和Otsu阈值。

  • 掌握API —— cv.threshold()和cv.adaptiveThreshold()

简单阈值

retval, dst = cv.threshold(src, thresh, maxval, type[,dst])

功能:

阈值处理。

参数:

参数

说明

src

输入图像

thresh

阈值

maxval

最大值

type

阈值类型

阈值类型:

类型

说明

cv.THRESH_BINARY

二值阈值化。超过阈值的,设置maxval值,否则设为0。

cv.THRESH_BINARY_INV

二值阈值化反转。超过阈值的,设置0,否则设为maxval值。

cv.THRESH_TRUNC

截断阈值化。超过阈值的,置为阈值,小于阈值的不变。

cv.THRESH_TOZERO

超过阈值的被置为0,其它的不变。

cv.THRESH_TOZERO_INV

低于阈值的被置为0,其它的不变。

返回值:

  • retval——使用的阈值

  • dst——阈值后的图像

自适应阈值

自适应阈值是一种改进的阈值技术,这种方法由函数cv.adaptiveThreshold()来实现:

dst = cv.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C[,dst])

该函数有两种不同的自适应阈值方法,cv.ADAPTIVE_THRESH_MEAN_Ccv.ADAPTIVE_THRESH_GAUSSIAN_C,由参数adaptiveMethod设置。在这两种方法中,自适应阈值在每个像素点都不同。通过计算像素点周围区域的加权平均,然后减去一个常量得到。的大小由参数blockSize设置,常量由参数C设置。

  • cv.ADAPTIVE_THRESH_MEAN_C——对区域的所有像素平均加权。

  • cv.ADAPTIVE_THRESH_GAUSSIAN_C——对像素周围的像素根据高斯函数按照它们离中心点的距离进行加权计算。

自适应阈值主要适用于具有很强照明或反射梯度,需要根据梯度进行阈值化的图像。


import cv2 as cv
import numpy as np
from matplotlib import pyplot as pltimg = cv.imread('../../images/sudoku.png', 0)
img = cv.medianBlur(img, 5)ret, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)th2 = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 11, 2)th3 = cv.adaptiveThreshold(img, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 11, 2)titles = ['Original Image', 'Global Threshold(127)', 'Adaptive Mean Threshold', 'Adaptive Gaussian Threshold']images = [img, th1, th2, th3]for i in range(4):plt.subplot(2,2,i+1), plt.imshow(images[i], 'gray')plt.title(titles[i])plt.xticks([]), plt.yticks([])plt.show()

Otsu的二值化

在全局阈值化中,阈值的大小是任意指定的,具有很大的不确定性。为了解决该问题,OpenCV退出了Otsu阈值方法。该方法从图像的直方图中确定最佳的全局阈值。只需要把参数cv.THRESH_OTSU传进函数cv.threshold()即可。

全局阈值与Otsu阈值:


import cv2 as cv
import numpy as np
from matplotlib import pyplot as pltimg = cv.imread('../../images/nosiy.png', 0)if img is None:print('image is None.')# 全局阈值
ret1, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)# Otsu阈值处理
ret2, th2 = cv.threshold(img, 0, 255, cv.THRESH_BINARY+cv.THRESH_OTSU)# 先平滑再Otsu阈值处理
blur = cv.GaussianBlur(img, (5,5), 0)
ret3, th3 = cv.threshold(blur, 0, 255, cv.THRESH_BINARY+cv.THRESH_OTSU)
print(ret3)images = [img, 0, th1,img, 0, th2,blur, 0, dst]titles = ['Original Image', 'Histogram', 'Global Threshold(127)','Original Image', 'Histogram', "Otsu's Threshold",'Gaussian filtered Image', 'Histogram', "Otsu's Threshold"]for i in range(3):plt.subplot(3,3,i*3+1), plt.imshow(images[i*3], 'gray')plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])plt.subplot(3,3,i*3+2), plt.hist(images[i*3].ravel(), 256)plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])plt.subplot(3,3,i*3+3), plt.imshow(images[i*3+2], 'gray')plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])plt.show()

Otsu的二值化是如何实现的?

暂时无

4-4 图像平滑

目标

  • 应用多种低通滤波器模糊、平滑图像

  • 应用自定义滤波器到图像中(2D卷积)

2D卷积

低通滤波器去噪,高通滤波器提取边缘。

OpenCV提供了函数cv.filter2D()实现图像与核卷积。

cv.filter2D(src, ddepth, kernel[,dst[,anchor[,delta[,borderType]]]])

参数

说明

src

输入图像

ddepth

输出图像的位深,取-1,表示输出类型和输入类型相同。

kernel

卷积核

anchor

核的参考点,默认值(-1,-1),可选项

delta

偏移量,卷积结果加上这个值,可选项

borderType

边界填充类型,可选项

kernel指的是卷积核,这个kernel可以用numpy生成,也可以用cv.getStructuringElement()函数生成。

图像的5x5平均滤波器处理:


import cv2 as cv
import numpy as np
from matplotlib import pyplot as pltimg = cv.imread('../../images/lena.png')# 创建核 kernel 与 kernel2等价
kernel = np.ones((5,5), np.float32)/25kernel2 = cv.getStructuringElement(cv.MORPH_RECT, (5,5))/25dst = cv.filter2D(img, -1, kernel2, delta=5)img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
dst = cv.cvtColor(dst, cv.COLOR_BGR2RGB)plt.subplot(1,2,1), plt.imshow(img), plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(1,2,2), plt.imshow(dst), plt.title('Filtered Image'), plt.xticks([]), plt.yticks([])
plt.show()

图像模糊

图像模糊是通过将低通滤波器内核与图像进行卷积来实现的。它对去噪非常有用。实际上,它去除了图像上的高频部分(噪声和边缘),因此,边缘也会出现模糊。、

OpenCV提供了4种类型的模糊技术:

  1. 均值模糊

  • 通过将图像与归一化盒子滤波器进行卷积实现。

  • OpenCV提供cv.blur() 或 cv.boxFilter()来实现。

示例:


import cv2 as cv
import numpy as np 
from matplotlib import pyplot as pltimg = cv.imread('../../images/lena.png')
blur = cv.blur(img, (5,5))img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
blur = cv.cvtColor(blur, cv.COLOR_BGR2RGB)plt.subplot(121),plt.imshow(img),plt.title('Original Image'),plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(blur),plt.title('Blured Image'),plt.xticks([]),plt.yticks([])
plt.show()
  1. 高斯模糊

高斯滤波用卷积核与输入图像的每一个点进行卷积,将最终计算结果之和作为输出图像的像素值。OpenCV提供的实现函数:cv.GaussianBlur()。另外,可以通过函数cv.getGaussianKernel()生成高斯滤波器内核。


blur = cv.GaussianBlur(img, (5,5), 0)
  1. 中值模糊

中值滤波器将中心像素的正方形邻域内的每一个像素值用中间像素值代替。它对处理图像的椒盐噪点非常有用。OpenCV提供了中值模糊的实现函数:cv.medianBlur()


median = cv.medianBlur(img, 5)
  1. 双边滤波

双边滤波在去噪的同时保留了边缘。双边滤波根据每个像素及其邻域构造一个加权平均值,加权计算包括两个部分,其中第一部分加权方式与高斯平滑中的相同,第二部分也属于高斯加权,但不是基于中心像素点与其他像素点的空间距离之上的加权,而是基于其他像素与中心像素的亮度差值的加权。

OpenCV提供了双边滤波的实现函数:cv.bilateralFilter()


blur = cv.bilateralFilter(img, 9, 75, 75)

均值滤波、高斯滤波、中值滤波和双边滤波:


import cv2 as cv
import numpy as np 
from matplotlib import pyplot as pltimg = cv.imread('../../images/lena.png')
# 均值模糊
blur = cv.blur(img, (21,21))# 高斯模糊
gauss = cv.GaussianBlur(img, (21,21), 0)
# 中值模糊
median = cv.medianBlur(img, 21)
#blur = cv.boxFilter(img, -1, (5,5))# 双边滤波
bilateral = cv.bilateralFilter(img, 21, 70, 70)img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
blur = cv.cvtColor(blur, cv.COLOR_BGR2RGB)
gauss = cv.cvtColor(gauss, cv.COLOR_BGR2RGB)
median = cv.cvtColor(median, cv.COLOR_BGR2RGB)
bilateral = cv.cvtColor(bilateral, cv.COLOR_BGR2RGB)plt.subplot(231),plt.imshow(img),plt.title('Original Image'),plt.xticks([]),plt.yticks([])
plt.subplot(232),plt.imshow(blur),plt.title('Blured Image'),plt.xticks([]),plt.yticks([])
plt.subplot(233),plt.imshow(gauss),plt.title('Gaussed Image'),plt.xticks([]),plt.yticks([])
plt.subplot(234),plt.imshow(median),plt.title('Medianed Image'),plt.xticks([]),plt.yticks([])
plt.subplot(235),plt.imshow(bilateral),plt.title('Bilateraled Image'),plt.xticks([]),plt.yticks([])
plt.show()

总结:

  • 高斯滤波——处理高斯噪声

  • 中值滤波——处理椒盐噪声

  • 双边滤波——去噪同时保留边缘

4-5 形态转换

目标

  • 学习不同的形态学操作,例如腐蚀、膨胀、开运算和闭运算。

  • 学习api函数:cv.erode()、cv.dilate()和cv.morphologyEx()等。

原理

形态学转换是一些基于图像形态的操作,一般应用于二进制图像上。

膨胀

膨胀是指将图像与核进行卷积,求局部最大值的操作。核与图像卷积,计算核覆盖的区域的像素点最大值,并把这个最大值赋值给参考点指定的像素。膨胀会使图像中的高亮区域增大和连通。

OpenCV实现膨胀的函数:cv.dilate()

腐蚀

腐蚀是膨胀的反操作,核与图像进行卷积,计算核覆盖的区域的像素最小值,并把这个最小值赋给参考点指定的像素。腐蚀会使图像中的高亮区缩小和隔离。

OpenCV实现腐蚀的函数:cv.erode()

注:如果不是二值图像,膨胀和腐蚀操作起到的作用不是很明显。

开运算

开运算是先腐蚀,然后再膨胀。开运算能够消除噪点、平滑边缘。

闭运算

闭运算是先膨胀,然后再腐蚀。闭运算能够填充一些小洞。

OpenCV实现开运算和闭运算的函数:cv.morphologyEx()

形态学梯度

形态学梯度操作能描述图像亮度变化的剧烈程度,当想突出高亮区域的外围时,可以使用形态学梯度,因为从原区域的膨胀中减去了原区域的腐蚀,所以留下了完整的外围边缘。

顶帽

顶帽操作是从源图像中减去图像的开运算。

黑帽

黑帽操作是从源图像闭运算减去源图像。

形态学转换示例:


import cv2 as cv
import numpy as np
from matplotlib import pyplot as pltimg = cv.imread('../../images/j.png', 0)
kernel = np.ones((3,3), np.uint8)
# 膨胀
dilation = cv.dilate(img, kernel, iterations=1)
# 腐蚀
erosion = cv.erode(img, kernel, iterations=1)# 开运算
opening = cv.morphologyEx(img, cv.MORPH_OPEN, kernel)# 闭运算
closing = cv.morphologyEx(img, cv.MORPH_CLOSE, kernel)# 形态学梯度
gradient = cv.morphologyEx(img, cv.MORPH_GRADIENT, kernel)# 顶帽
topHat = cv.morphologyEx(img, cv.MORPH_TOPHAT, kernel)# 黑帽
blackHat = cv.morphologyEx(img, cv.MORPH_BLACKHAT, kernel)plt.subplot(241), plt.imshow(img, 'gray'), plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(242), plt.imshow(dilation, 'gray'), plt.title('Dilation'), plt.xticks([]), plt.yticks([])
plt.subplot(243), plt.imshow(erosion, 'gray'), plt.title('Erosion'), plt.xticks([]), plt.yticks([])
plt.subplot(244), plt.imshow(opening, 'gray'), plt.title('Openning'), plt.xticks([]), plt.yticks([])
plt.subplot(245), plt.imshow(closing, 'gray'), plt.title('Closing'), plt.xticks([]), plt.yticks([])
plt.subplot(246), plt.imshow(gradient, 'gray'), plt.title('Gradient'), plt.xticks([]), plt.yticks([])
plt.subplot(247), plt.imshow(topHat, 'gray'), plt.title('TopHat'), plt.xticks([]), plt.yticks([])
plt.subplot(248), plt.imshow(blackHat, 'gray'), plt.title('BlackHat'), plt.xticks([]), plt.yticks([])
plt.show()

结构元素

前面的示例中,都是通过numpy创建的结构元素,但都只能是矩形。在某些情况下,可能需要一些椭圆或圆形的结构元素。OpenCV提供了生成结构元素的函数:


cv.getStructuringElement()

只需要传递结构元素的形状和大小即可。

4-6 图像梯度

目标

  • 学习查找图像梯度、边缘等。

  • 学习api函数:cv.Sobel()、cv.Scharr()、cv.Laplacian()等。

理论

OpenCV提供了3种梯度滤波器或高通滤波器:Sobel, ScharrLaplacian

Sobel和Scharr算子

Sobel算子是高斯平滑与微分的联合,因此它更能抗噪声。可以通过参数yorder和xorder来指定导数的方向。还可以通过参数ksize指定内核的大小。如果ksize = -1,将会采用3x3的Scharr滤波器,比3x3Sobel滤波器具有更好的效果,Sobel的x方向、y方向、Scharr的x方向的内核:

Sobel与Scharr滤波区别:Sobel的卷积核大小是可以改变的,可以改为5x5、7x7等,而Scharr的卷积核是固定3x3的,是不可以改变的。

Sobel与Scharr都是只能求一个方向上的边缘,要么求x方向的,要么求y方向的,不能同时求x和y方向的,这是它们的一个缺点。

OpenCV实现Sobel和Scharr的函数:cv.Sobel()、cv.Scharr()

Laplacian算子

Laplacian可以同时计算两个方向的边缘。对图像进行拉普拉斯变换主要根据一下公式进行:

如果ksize=1,将采用下面的内核进行滤波:

由于Laplacian对噪声比较的敏感,一般会先去噪然后再做Laplacian。

OpenCV实现Laplacian的函数:cv.Laplacian()

本节代码:


import cv2 as cv
import numpy as np
from matplotlib import pyplot as pltimg = cv.imread('../../images/table.jpg')# Sobel算子
sobelx = cv.Sobel(img, -1, 1, 0, ksize=3)
sobely = cv.Sobel(img, -1, 0, 1, ksize=3)# dst = sobelx + sobely
dst = cv.add(sobelx, sobely)# Scharr算子
blur = cv.GaussianBlur(img, (7,7), 0)
scharrx = cv.Scharr(blur, cv.CV_64F, 1, 0)
scharry = cv.Scharr(blur, cv.CV_64F, 0, 1)# Laplacian算子
laplace = cv.Laplacian(img, cv.CV_8U, ksize=5)plt.subplot(241),plt.imshow(img),plt.title('Src'),plt.xticks([]),plt.yticks([])
plt.subplot(242),plt.imshow(sobelx),plt.title('X'),plt.xticks([]),plt.yticks([])
plt.subplot(243),plt.imshow(sobely),plt.title('Y'),plt.xticks([]),plt.yticks([])
plt.subplot(244),plt.imshow(dst),plt.title('X + Y'),plt.xticks([]),plt.yticks([])
plt.subplot(245),plt.imshow(scharrx),plt.title('ScharrX'),plt.xticks([]),plt.yticks([])
plt.subplot(246),plt.imshow(scharry),plt.title('ScharrY'),plt.xticks([]),plt.yticks([])
plt.subplot(247),plt.imshow(laplace),plt.title('Laplacian'),plt.xticks([]),plt.yticks([])plt.show()

4-7 Canny边缘检测

目标

  • 学习Canny边缘检测

  • 学习api函数:cv.Canny()

理论

Canny边缘检测是一种比较流行的边缘检测算法,这是一个强大的多阶段算法:

  1. 去噪

由于图像中边缘检测很容易受到噪声的干扰,它首先对图像进行一个5x5的高斯平滑降噪。而Laplacian是没有去噪这一步的,需要额外地去噪。

  1. 查找图像的强度梯度

在平滑后的图像上,使用Sobel核分别在0°、45°、90°和135°进行一阶求导,得到了图像和图像,找出每个像素的梯度和方向。这个要比Sobel单独进行两个方向求导效果要好很多。

梯度方向通常都是垂直于边缘的。

  1. 非极大值抑制

得到梯度幅度和方向后,对图像进行全面的遍历,抛弃那些不构成边缘的多余的像素。需要检查每个像素是否梯度方向上附近的局部最大值。如果是就保留下来,如果不是就将其抑制(置为0)

  1. 滞后阈值

这一阶段将决定哪些是真边缘哪些是假边缘。需要设置一个高阈值和一个低阈值,如果边缘的强度梯度高于阈值,就认为是真实边缘,如果低于低阈值,将被认为不是边缘而被抛弃,如果介于高阈值和低阈值之间,就看是否能与真实边缘连接上,如果能连接上就被认为是真实边缘,否则将被抛弃。

例如边缘A高于高阈值,被确定为真实边缘,而边缘B和边缘C介于高阈值和低阈值之间,因为C能与A连接上,所以C也被确定为真实边缘,而B被确定为假边缘而抛弃。所以,检测边缘效果的好坏,主要看选择的高阈值与低阈值怎么样。

OpenCV实现Canny检测边缘的函数:cv.Cannny()

Canny示例:


import cv2 as cv
import numpy as np
from matplotlib import pyplot as pltimg = cv.imread('../../images/lena.png', 0)dst = cv.Canny(img, 150, 300)plt.subplot(121), plt.imshow(img, 'gray'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(dst, 'gray'), plt.xticks([]), plt.yticks([])
plt.show()

4-8 图像金字塔

目标

  • 学习图像金字塔

  • 使用图像金字塔创建一个新的水果

  • 学习api函数:cv.pyrUp()和cv.pyrDown()

理论

图像金字塔是图像的集合,由原始图像通过连续的降采样产生,即由高分辨率的图像(大尺寸)产生低分辨率的近似图像(小尺寸)。

假如在图像中搜索人脸时,由于不能确定对象将以多大的尺寸显示在图像中,往往这个时候,就需要创建一组具有不同分辨率的相同图像,并在所有图像中搜索对象,而这些具有不同分辨率的图像集合称为图像金字塔

高斯金字塔

  • 向下采样

从金字塔层生成,首先要用高斯核对进行卷积,然后删除所有偶数行和偶数列。得到的图像面积会变为源图像的四分之一,不断重复该过程,就可以得到该图像的高斯金字塔图像。

OpenCV提供实现高斯金字塔向下采样图像操作的函数:cv.pyrDown()

  • 向上采样

向上采样图像,首先将图像的大小增加到每个维度的原始值的两倍,新的行和列填充0,然后再使用高斯滤波器对图像进行卷积。

OpenCV提供实现高斯金字塔向上采样图像操作的函数:cv.pyrUp()

拉普拉斯金字塔

向上采样和向下采样是不可逆的,因为在做向下采样过程中,丢失了很多信息。如果希望通过对金字塔中的小图像进行向上采样以获取完整的大尺寸高分辨率图像,这就需要用到拉普拉斯金字塔。

为了在向上采样时能够恢复具有较高分辨率的原始图像,就要获取在采样过程中所丢失的信息,这些丢失的信息就构成了拉普拉斯金字塔。

拉普拉斯金字塔的第i层由以下关系定义:

其中,通过将原始图像中的位置中的每个像素映射到目标图像中的像素来进行大小化;符号表示卷积;表示5x5高斯核。而是由OpenCV提供的cv.pyrUp()完成的。因此,可以使用OpenCV直接计算拉普拉斯算子:

4-9 轮廓

4-9-1 轮廓

目标

  • 学习什么是轮廓

  • 学习查找和绘制轮廓

  • 学习api函数:cv.findContours(),cv.drawContours()

什么是轮廓?

虽然Canny边缘检测算法可以根据像素间的差异检测出轮廓边界的像素,但是它并没有将轮廓作为一个整体进行处理。

轮廓是指具有相同颜色强度连续点的曲线。

OpenCV提供查找轮廓的api函数:cv.findContours(),使用的时候需要主要以下问题:

  • 待处理的源图像必须是灰度二值图。因此,需要预先对图像进行阈值分割或者边缘检测处理

  • 背景是黑色,对象是白色。

绘制轮廓

OpenCV提供绘制轮廓的api函数:cv.drawContours()。

代码:


import cv2 as cv
import numpy as np
import matplotlib.pyplot as pltsrc = cv.imread('../../images/contours.png')
img = src.copy()img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)ret, threh = cv.threshold(img, 127, 255, cv.THRESH_BINARY)contours, hierarchy = cv.findContours(threh, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)
print(len(contours))
print(hierarchy)src = cv.drawContours(src, contours, -1, (0,255,0), 2)
src = cv.cvtColor(src, cv.COLOR_BGR2RGB)plt.imshow(src),plt.xticks([]),plt.yticks([])
plt.show()

4-9-2 轮廓特征

目标

  • 学习如何找到轮廓的不同特征:面积、周长、质心、边界框等。

比较两条轮廓最简单的方法就是比较它们的轮廓矩。矩是通过对轮廓上所有点进行积分运算或者认为是求和运算而得到的一个粗略特征。

空间矩(直接在原图的像素坐标计算)

轮廓的(p, q)空间矩定义如下:

其中,对应维度上的矩,对应维度上的矩,阶数表示对应的部分的指数。该计算是对轮廓边界上所有像素(数目为n)进行求和。如果p和q全部为0,那么实际上对应轮廓边界上点的数目;如果p和q均为1时,表示图像中所有不为0的点的横纵坐标相乘再相加得到的结果,作为轮廓的(1,1)矩。

  • 0阶矩:

  • 1阶矩:

  • 2阶矩:

  • 3阶矩:

中心矩(克服了平移带来的矩不同的影响)

中心矩将原图原点设置在所有点的横纵坐标均值位置,能够忽略两个对象的位置关系,具有平移不变性。

例如,想要比较两个位置不同的对象的一致性,这个时候就需要引入中心矩,中心矩通过减去均值而获取平移不变性,因此可以比较两个位置不同的对象是否一致。

其中,

  • 二阶:

  • 三阶:

归一化中心矩(克服平移缩放带来的影响)

归一化中心矩将横纵坐标归一化,能够克服对象大小和位置不同带来的影响,具有平移、缩放不变性。归一化中心矩与中心矩也基本相同,除了每个矩都要除以的某一个幂。

  • 二阶矩:

  • 三阶矩:

OpenCV提供了计算空间矩、中心矩和归一化中心矩的api函数:cv.moments(array[, binaryImage])。

这个函数返回一个字典,给出三阶内的所有矩的值。


import cv2 as cv
import numpy as np
from matplotlib import pyplot as pltsrc = cv.imread('../../images/polygon.png')
img = src.copy()gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)ret, thresh = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)# 查找轮廓
contours, hierarchy = cv.findContours(thresh, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE)for n, item in enumerate(contours):# 计算矩M = CV.moments(item)print(f'轮廓{n}的矩为:\n {cv.moments(item)}')

从这些矩中,可以提取出面积、质心等有用的信息。例如,计算质心公式:


cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])

以上的三种矩,克服了平移缩放带来的影响,但是不能克服旋转镜像映射带来的影响,这需要引入Hu矩解决旋转镜像映射带来的影响

  1. 轮廓面积

计算轮廓的面积使用api函数cv.contourArea()或是从矩中提取M['m00']。


area = cv.contourArea(cnt)

  1. 轮廓周长

也称为弧长。计算轮廓的周长使用函数cv.arcLength()


perimeter = cv.arcLenght(cnt, True)

  1. 轮廓逼近

轮廓逼近是指根据给定的精度,由一条轮廓逼近另外一条顶点更少的轮廓。OpenCV采用道格拉斯-普克算法来实现。该算法首先从轮廓中找到距离最远的两个点,并把两点相连。接着,在轮廓上找到一个离当前直线最远的点,并将该点与原有直线连成一个封闭的多边形。将上述过程不断地迭代,将新找到的距离当前多边形最远的点加入到结果中。当轮廓上所有的点到当前多边形的距离都小于给定的精度时,就停止迭代。一般情况下,精度设置为轮廓总长度的百分比形式。

在OpenCV中,实现轮廓逼近的函数为:cv.approxPolyDP()


epsilon = 0.1 * cv.arcLength(cnt, True)
approx = cv.approxPolyDP(cnt, epsilon, True)

  1. 凸包

凸包与轮廓逼近相似,虽然有时候它们提供的结果一样,但是它们并不一样。通常,凸曲线一般都是向外凸出或至少是平坦的曲线。如果是在内部凸起的,则称为凸缺陷(convexity defects)。

在OpenCV中,获取轮廓凸包的函数为:cv.convexHull()。


hull = cv.convexHull(cnt)
  1. 检查凸度(convexity)

函数cv.isContourConvex()检查曲线是否为凸出,如果是返回true,否则返回false。


k = cv.isContourConvex(cnt)
  1. 边界框(Bounding Rectangle)

有两种矩形边界框:直角矩形边界框旋转矩形边界框

直角矩形边界框

它是一个水平的矩形,不考虑对象的旋转情况,因此边界框的面积不是最小的。使用函数cv.boundingRect()进行查找。


x,y,w,h = cv.boundingRect(cnt)
cv.rectangle(img, (x,y), (x+w, y+h), (0,255,0),2)

(x,y)是左上角坐标,(w,h)是矩形的宽和高。

旋转矩形边界框

绘制最小外接矩形,需要考虑对象的旋转角度。使用函数cv.minAreaRect()来获取对象的最小外接矩形,返回一个Box2D结构,包含了((x,y), (w,h), angle)。但是,绘制矩形时,需要矩形的4个顶点,这4个顶点可以通过函数cv.boxPoints()获得。


rect = cv.minAreaRect(contours[0])
box = cv.boxPoints(rect)
box = np.int0(box)
cv.drawContours(img, [box], -1, (0,0,255), 2)
  1. 最小外接圆

使用函数cv.minEnclosingCircle()获取对象的最小外接矩形。


(x,y),radius = cv.minEnclosingCircle(contours[0])
  1. 拟合椭圆

api函数:cv.fitEllipse()


ellipse = cv.fitEllipse(contours[0])
  1. 拟合直线

api函数:cv.fitLine()


[x1, y1, x2, y2] = cv.fitLine(contours[0], cv.DIST_L2, 0, 0.01, 0.01)

4-9-3 轮廓属性

除了质心、面积和周长外,轮廓还有Solidity、等效直径、掩膜图像、平均强度等属性。

  1. 长宽比 Aspect Ratio

它是对象矩形边界框的宽度与高度的比值。


x,y,w,h = cv.boundingRect(cnt)
aspect_ration = float(w)/h
  1. Extent

它是轮廓面积与矩形边界框面积的比值。


area = cv.contourArea(cnt)
x,y,w,h = cv.boundingRect(cnt)
rect_area = w*h
extent = float(area)/rect_area
  1. Solidity

它是轮廓面积与凸包面积的比值。


area = cv.contourArea(cnt)
hull = cv.convexHull(cnt)
hull_area = cv.contourArea(hull)
solidity = float(area)/hull_area
  1. 等效直径Equivalent Diameter

它是与轮廓面积相等的圆的直径。


area = cv.contourArea(cnt)
equi_diameter = np.sqrt(4*area/np.pi)
  1. 方向Orientation

它是指对象指向的方向。


(x,y),(Ma,Mi),angle = cv.fitEllipse(cnt)
  1. 掩膜和像素点Mask and Pixel Points


mask = np.zeros(imggray.shape, np.uint8)
cv.drawContours(mask, [cnt], 0, 10, -1)
pixelpoints = np.transpose(np.nonzero(mask))
pixelpoints1 = cv.findNonZero(mask)

np.transpose()与cv.findNonZero()都是返回非零像素的坐标,区别是np.transpose()返回的是(row, col),而cv.findNonZero()返回的是(x, y),两者刚好相反。

  1. 最大值、最小值和它们的位置

使用api函数:cv.minMaxLoc(),可以通过掩膜图像获取最大值、最小值及它们的位置。


min_val, max_val, min_loc, max_loc = cv.minMaxLoc(imggray, mask = mask)
  1. 平均颜色或平均强度

使用函数:cv.mean()


mean_val = cv.mean(img, mask=mask)
  1. 极值点

极值点是指对象的最顶部、最底部、最右侧和最左侧的点。


leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])

4-9-4 再深入轮廓

目标

  • 学习凸缺陷以及如何找到

  • 查找点到多边形的最短距离

  • 轮廓匹配

原理和代码

  1. 凸缺陷

凸缺陷是指对象与凸包差的部分。在OpenCV中,获取凸缺陷的函数为:cv.convexityDefects(),返回值是一个数组,其中[起点,终点,轮廓上距离凸包最远的点,最远点到凸包的近似距离]的前三个值是轮廓点的索引


hull = cv.convexHull(cnt, returnPoints = False)
defects = cv.convexityDefects(cnt, hull)
  1. 点多边形测试

函数cv.pointPolygonTest()用来获取点到轮廓的最短距离,如果点在轮廓外,返回一个负值,如果点在轮廓内,返回一个正值,如果点在轮廓上,返回0。


dist = cv.pointPolyTest(cnt, (50,50), True)

第3个参数是measureDist,如果为True,结果返回一个有符号的距离。如果为假,则查找该点是在轮廓内还是在轮廓外(分别返回1、-1和0)。

  1. 形状匹配

Hu矩

Hu矩是归一化中心矩的线性组合,每一个矩都是通过归一化中心矩的组合运算得到的,这些句函数对旋转、缩放和镜像映射(出了hu[0])具有不变性,经常被用来识别图像特征。

归一化中心矩:

  • 二阶:

  • 三阶:

Hu矩为:

每一幅图像对应7个Hu矩,当阶数变高时,Hu矩一般会变小,因为根据定义,高阶Hu矩由多个归一化中心矩的高阶幂计算得到,而归一化矩都是小于的,所以指数越大,计算所得的值越小。

在OpenCV中,计算7个Hu矩函数为:cv.HuMoments()

可以通过Hu矩来判断两个对象的一致性。OpenCV提供了函数cv.matchShapes(),这个函数就是基于hu矩计算对两个形状或两条轮廓进行比较,返回一个显示相似程度的度量。结果越小,说明相似度就越高。

4-9-5 轮廓层次(Contours Hierarchy)

目标

  • 学习轮廓的层次,例如轮廓中的父子关系。

原理

轮廓层次结构

轮廓0、1和2属于最外层,它们处于同一层级,可以认为它们处于层级0;2a可以被认为是2的子轮廓,属于层级1;3是2a的子轮廓,属于2a的下一级,层级2;3a是3的子轮廓,属于层级3;轮廓4和5是3a的子轮廓,它们同属一个层级,属于最后一个层级4。

有上面的例子,引出了同一层级、子轮廓、父轮廓及第一子轮廓。

OpenCV中的层次关系

每一条轮廓都有自己的层次信息,子轮廓是什么,父轮廓是什么。在OpenCV中用一个数组来表示:[Next, Previous, First_Child, Parent]

  • Next —— 表示同级的下一条轮廓。

  • Previous —— 表示同级的上一条轮廓

  • First_Child —— 表示下级的第一个子节点

  • Parent —— 表示上级的父节点

注:如果没有子节点或父节点,则返回-1。

轮廓的检索模式

  1. RETR_LIST

只检索所有的轮廓,不建立任何的父子关系,只有前和后的关系,所以First_Child和Parent一直返回-1。

如果不需要hierarchy,这种模式是属于最好的。


>>> hierarchy
array([[[ 1, -1, -1, -1],[ 2,  0, -1, -1],[ 3,  1, -1, -1],[ 4,  2, -1, -1],[ 5,  3, -1, -1],[ 6,  4, -1, -1],[ 7,  5, -1, -1],[-1,  6, -1, -1]]])
  1. RETR_EXTERNAL

只检索最外层的轮廓。


>>> hierarchy
array([[[ 1, -1, -1, -1],[ 2,  0, -1, -1],[-1,  1, -1, -1]]])
  1. RETR_CCOMP

检索所有的轮廓,并分为两级层次结构,外部对象(边界)属于1层次,孔的内部轮廓属于2层次。

例如上面的图像,0的外部圆属于1层次,0的内部圆输入2层次。

  1. RETR_TREE

检索所有的轮廓并创建一个完整的层次结构。

4-10 直方图(Histograms)

4-10-1 直方图-1:查找、绘制和分析

目标

  • 分别使用OpenCV和Numpy函数查找直方图

  • 分别使用OpenCV和Matplotlib函数绘制直方图

  • 学习函数cv.calcHist()np.histogram()

原理

普通直方图

什么是直方图?直方图就是一张图形或图表,用来统计图像内各个灰度级出现的次数。从直方图的图形上看,X轴是各像素点的灰度级,一般是0~255;Y轴是具有该灰度级的像素个数。直方图的左边反映较暗像素的分布,右边反映较亮。

直方图是我们理解图像的另一种方式,通过直方图,可以直观地了解图像的对比度(contrast)、亮度(brightness)、强度(intensity)分布情况。

归一化直方图

归一化直方图中,X轴还是表示灰度级;Y轴不再表示次数,而是灰度级出现的频率。

查找直方图

术语:

  • BINS —— 参数子集的数目,[0,255]如果每个值划分为一个子集,那么BINS=256;如果每16个值划分为一个子集,即[0,15]、[16,31]...,那么BINS=256/16=16。

  • DIMS —— 收集数据参数的数量,假如只收集图像的灰度值,那么DIMS=1。

  • RANGE —— 收集数据的范围,图像灰度值为[0,256]。

  1. OpenCV中的直方图计算

在OpenCV中,使用函数cv.calcHist()计算直方图。


hist = cv.calcHist([img_gray], [0], None, [256], [0,256])
  1. 在Numpy中计算直方图


hist, bins = np.histogram(img.ravel(), 256, [0,256])

绘制直方图


plt.hist(img_gray.ravel(), 256, [0,256]),plt.plot(hist, 'r'),plt.xlim([0,256])
# 或
plt.subplot(133),plt.plot(hist, 'r'),plt.xlim([0,256])

使用掩膜图像


# 创建掩膜图像
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:200,200:300] = 255
img_mask = cv.bitwise_and(img, img, mask = mask)
hist_mask = cv.calcHist([img],[0],mask,[256],[0,256])

4-10-2 直方图-2:直方图均衡

目标

  • 学习直方图均衡化(histogram equalization)及使用它提高图像的对比度

  • 学习api函数:cv.equalizeHist()

原理

为什么要直方图均衡化?

一幅图像如果拥有全部可能的灰度级,并且像素值均匀分布,那么这幅图像就具有较高的对比度,不会过暗或过亮。

直方图均衡化算法的步骤:

  1. 计算累计直方图

计算所有灰度级的累计概率。

  1. 对累计直方图进行区间转换

用当前灰度级的累计概率乘以当前灰度级的最大值,得到新的灰度级,并作为均衡化的结果。

全局直方图均衡

使用函数cv.equalizeHist()进行全局直方图均衡。


dst = cv.equalizeHist(src)

自适应直方图均衡

使用函数cv.createCLAHE()进行自适应直方图均衡。


clahe = cv.createCLAHE(clipLimit=1.0, tileGridSize=(8,8))
dst2 = clahe.apply(src1)

4-10-3 直方图-3:2D直方图

目标

  • 学习查找和绘制2D直方图。

前面学习的直方图,只考虑了灰度级一个特征,属于一维直方图;二维直方图需要考虑两个特征,例如颜色直方图,考虑了像素的色相(Hue)和饱和度(Saturation)值。

OpenCV中的二维直方图

使用函数cv.calcHist()计算。对于颜色直方图,需要将图像从BGR转为HSV。


hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV)
hist = cv.calcHist([hsv], [0,1], None, [180, 256], [0,180,0,256])

注意:色相值范围为0~179, 饱和度值范围为0~255。

Numpy中的二维直方图

Numpy提供计算一维直方图的函数np.histogram(),而2d直方图的函数:np.histogram2d()。


hsv = cv.cvtColor(img,cv.COLOR_BGR2HSV)
hist, xbins, ybins = np.histogram2d(h.ravel(),s.ravel(),[180,256],[[0,180],[0,256]])

绘制二维直方图

方法1:使用 cv.imshow()


cv.imshow('hist', hist)

方法2:使用Matplotlib


plt.inshow(hist, interpolation='nearest')

注意:插值法采用最近邻可以获得更好的结果。

4-10-4 直方图-4:直方图反投影

目标

  • 学习什么是反向投影及用途。

  • 学习如何使用cv.calcBackProject()计算反向投影。

  • 如何使用cv.mixChannels()混合图像的不同通道。

什么是反向投影?

反向投影是一种记录给定图像中像素点如何适应直方图模型中像素分布的方式。

简单讲,就是先计算某一特征的直方图模型,然后利用这个模型去寻找图像中存在的该特征。


src = cv.imread('../../images/skin.jpg')
src_hsv = cv.cvtColor(src, cv.COLOR_BGR2HSV)roi = src[80:150, 90:130]
roi_hsv = cv.cvtColor(roi, cv.COLOR_BGR2HSV)
roi_hist = cv.calcHist([roi_hsv], [0,1], None, [180,256], [0,180,0,256])cv.normalize(roi_hist, roi_hist, 0, 255, cv.NORM_MINMAX)
dst = cv.calcBackProject([src_hsv], [0,1], roi_hist, [0,180,0,256], 1)

4-11 傅里叶变换

图像的傅里叶变换就是把图像从空间域转换到频率域。图像经过傅里叶变换后,可以得到幅度图像和相位图像。在幅度图像中,幅度变换快的部分属于高频信息,幅度变化慢的部分属于低频信息。

傅里叶变换的目的,就是为了将图像从空间域转换到频率域,并在频域内实现对图像的特定处理,然后再进行逆傅里叶变换得到空间域图像。傅里叶变换可以实现图像增强、图像去噪、边缘检测、特征提取、图像压缩和加密等。

使用Numpy实现傅里叶变换

Numpy傅里叶变换

在Numpy中使用np.fft.fft2()实现傅里叶变换,第一个参数是一副灰度图像,第二个参数是一个可选项参数,指定输出图像的大小,如果输出图像比输入图像要大,在变换前需要对输入图像进行边界0填充,如果输出图像比输入图像小,需要对输入图像进行剪切,如果没有传入参数,那么输出图像与输入图像一样大,返回一个复数数组。


img = cv.imread('lena.jpg', 0)
f = np.fft.fft2(img)

经过np.fft.fft2()函数处理,就可以得到了图像的频谱信息,但是,图像频谱中的零频率分量位于频谱图像的左上角,为了便于观察,一般会使用np.fft.fftshift()函数将零频率成分移动到频域图像的中心位置,


fshift = np.fft.fftshift(f)

使用该函数处理后,图像频谱中的零频率分量会被移到频域图像的中心位置。

对图像进行傅里叶变换后, 得到的是一个复数数组。为了能正常显示图像,需要把复数数组里的值调整到[0,255]的灰度空间内:


magnitude_spetrum = 20 * np.log(np.abs(fshift))

Numpy逆傅里叶变换

在傅里叶变换过程中,如果把零频率分量移动到了图像中心,那么在逆傅里叶变换中,需要使用np.fft.ifftshift()先把零频率分量移回到原来的位置,然后再使用np.fft.ifft2()函数进行逆傅里叶变换:


f_ishift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f_ishift)

np.fft.ifft2()函数的返回值也是一个复数数组,还需要使用np.abs()或np.real()把复数值调整到[0,255]灰度空间内:


img_back = np.abs(img_back)

高通、低通滤波

在一副图像中,同时包含高频信号和低频信号:

  • 低频信号对应图像中幅度变化缓慢的分量。

  • 高频信号对应图像中幅度变换快的分量。

滤波器有高通滤波器和低通滤波器之分:

  • 低通滤波器允许低频信号通过,而抑制高频信号,例如图像去噪就是使用低通滤波器。

  • 高通滤波器允许高频信号通过,而抑制低频信号,例如边缘查找就是使用高通滤波器。

使用OpenCV实现傅里叶变换

OpenCV中进行傅里叶变换使用cv.dft(),进行逆傅里叶变换使用cv.idft(),返回值也是一个复数数组,但是返回的是双通道,第1个通道返回的是实数部分,第2个通道返回的是虚数部分。

OpenCV傅里叶变换


f = cv.dft(np.float32(img), flags = cv.DFT_COMPLEX_OUTPUT)

经过傅里叶变换后,得到了原始图像的频谱信息,0频率分量位于左上角,为了方便观察,还是使用np.fft.fftshift()把0频率分量移到中心位置:


 dft_shift = np.fft.fftshift(f)

继续使用cv.magnitude()函数计算频谱图像的幅度,并把幅度值映射到灰度图像的灰度空间[0,255]内:


magnitude_spectrum = 20*np.log(cv.magnitude(dft_shift[:,:,0], dft_shift[:,:,1]))

OpenCV逆傅里叶变换

首先先把0频率分量移回到原来的位置,然后再使用函数cv.idft()进行逆傅里叶变换:


dft_ishift = np.fft.ifftshift(dft_shift)
img_back = cv.idft(dft_ishift)

进行逆傅里叶变换后,得到的结果还是一个复数数组,继续使用cv.magnitude()计算幅度值:


img_back = cv.magnitude(img_back[:,:,0], img_back[:,:,1])

4-12 模板匹配

目标

  • 使用模板匹配在图像中查找对象

  • 学习api函数:cv.matchTemplate()

原理

模板匹配就是在待测图像上搜索和查找模板图像的位置。OpenCV提供函数cv.matchTemplate()进行模板匹配。将模板图像在输入图像上进行滑动,并比较模板图像与被模板图像覆盖的源图像。

如果输入图像的宽高为WxH,模板图像的宽高为w x h,那么结果图像的宽高为W-w+1和H-h+1。

注意:如果使用的匹配方法为cv.TM_SQDIFF,匹配越好数值就越小。

单模板匹配

得到匹配结果时,可以使用cv.minMaxLoc()找出最大值、最小值以及它们的位置。然后把找到的位置作为矩形的左上角,w和h分别为宽和高,标注出对象。


import cv2 as cv
import numpy as np
from matplotlib import pyplot as pltsrc = cv.imread('messi5.jpg')
img = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
H, W = img.shapeimg2 = img.copy()template = cv.imread('messi_face.jpg', 0)
h, w = template.shapemethods = ['cv.TM_CCOEFF', 'cv.TM_CCOEFF_NORMED', 'cv.TM_CCORR', 'cv.TM_CCORR_NORMED', 'cv.TM_SQDIFF', 'cv.TM_SQDIFF_NORMED']for meth in methods:imgSrc = src.copy()img = img.copy()method = eval(meth)res = cv.matchTemplate(img, template, method)min_val, max_val, min_loc, max_loc = cv.minMaxLoc(res)if meth == 'cv.TM_SQDIFF' or meth == 'cv.TM_SQDIFF_NORMED':top_left = min_locscore = 1 - min_valelse:top_left = max_locscore = max_valbottom_right = (top_left[0]+w, top_left[1]+h)cv.rectangle(imgSrc, top_left, bottom_right, (0,255,0), 2)cv.putText(imgSrc, str(score), top_left, cv.FONT_HERSHEY_COMPLEX, 0.3, (0,255,0), 1)imgSrc = cv.cvtColor(imgSrc, cv.COLOR_BGR2RGB)plt.subplot(121), plt.imshow(res, 'gray'), plt.xticks([]), plt.yticks([])plt.subplot(122), plt.imshow(imgSrc), plt.xticks([]), plt.yticks([]), plt.title(meth)plt.show()

多模板匹配

函数cv.minMaxLoc()只能找出最值,一旦图像中出现多个模板匹配时,cv.minMaxLoc()无法同时给出多个匹配区域的位置,这个时候需要设置一个阈值并通过循环遍历给出多个模板匹配区域的位置。


import cv2 as cv
import numpy as np
from matplotlib import pyplot as pltsrc = cv.imread('calib_distorted_01.png')img = src.copy()
img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
template = cv.imread('template.png', 0)w, h = template.shape[::-1]res = cv.matchTemplate(img, template, cv.TM_CCORR_NORMED)
print(res)loc = np.where(res>0.995)
num = 0
for pt in zip(*loc[::-1]):num += 1cv.rectangle(src, pt, (pt[0]+w, pt[1]+h), (0,255,0), 1)src = cv.cvtColor(src, cv.COLOR_BGR2RGB)
print(num)
plt.subplot(121), plt.imshow(src), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(template, 'gray'), plt.xticks([]), plt.yticks([])
plt.show()

4-13 霍夫线变换

目标

  • 学习霍夫变换

  • 学习使用霍夫变换在图像中检测直线

  • 学习api函数:cv.HoughLines()、cv.HoughLinesP()

原理

霍夫变换是一种能够检测直线、圆及其它形状的技术,只要对象能够用用数学方程表示的,都能适用霍夫变换来检测。

霍夫直线变换

如果在笛卡儿空间内,有N个点能够连成一条直线y=k1x+b1,那么在霍夫空间内就会有N条直线穿过对应的点(k1, b1)。反过来说,如果在霍夫空间中,有越多的直线穿过点(k1, b1),就说明在笛卡儿空间内有越多的点位于斜率为k1,截距为b1的直线y=k1*x+b1上。

在霍夫坐标系内,经过一个点的线越多,说明在极坐标内的直线是由越多的点构成的。

所以,霍夫变换选择直线的基本思路是:选择有尽可能多直线交汇的点。

OpenCV提供了函数cv.HoughLines()用来实现霍夫直线变换。函数要求输入图像是一副二值图像,所以在进行霍夫变换之前需要将源图像进行二值化,或进行Canny边缘检测。

概率霍夫变换

概率霍夫变换是霍夫变换的优化。概率霍夫变换不考虑所有的点,它只考虑一个能够满足检测直线的随机点子集。

OpenCV提供函数cv.HoughLinesP()实现概率霍夫变换,该函数新增了两个参数:

  • minLineLength —— 直线的最小长度。直线长度小的将被拒绝。

  • maxLineGap —— 线段间允许的最大间隙。

4-14 霍夫圆变换

目标

  • 学习使用霍夫变换查找图像中的圆。

  • 学习api函数:cv.HoughCircles()

原理

用霍夫变换检测图像中的圆,需要考虑圆心(x和y)、圆半径3个参数。在OpenCV中,首先是找出可能存在圆的位置,然后再找出半径大小。

OpenCV提供函数cv.HoughCircles()实现霍夫圆变换。

4-15 图像分割与分水岭算法

目标

  • 学习使用分水岭算法分割基于标记的图像

  • 学习api函数:cv.watershed()

原理

任何的一副灰度图像都可以被看作成地理学上的地形表面,灰度值高的区域被看作成山峰,灰度值低的区域被看作成山谷。

如果用不同颜色的水去填充每一个孤立的山谷,随着水位的上升,不同山谷的水就会汇集在一起,为了避免这一情况,需要在汇合的地方建起一道屏障(分水岭线),继续填充水和继续建立屏障,直到所有的山峰都被水填充完为止。屏障就是分割的结果。这就是分水岭算法的基本思想。

但是,在实践中,由于梯度图像中的噪声或局部不规则,会产生过度分割的结果。

为了结果过度分割的问题,现提出了基于标记改进的分水岭分割算法。

在OpenCV中,使用cv.watershed()实现分水岭算法。具体处理步骤如下:

  1. 原图阈值化处理


ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV+cv.THRESH_OTSU)
  1. 使用形态学开运算操作去噪:


opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel)
  1. 使用形态学膨胀操作,确定背景区域


sure_bg = cv.dilate(opening, kernel, iterations=3)
  1. 利用距离变换函数及阈值化确定前景区域


dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5)
ret, sure_fg = cv.threshold(dist_transform, 0.7*dist_transform.max(), 255, cv.THRESH_BINARY)
  1. 背景减去前景,确定未知区域


# 找到未知区域
sure_fg = np.uint8(sure_fg)
unknow = cv.subtract(sure_bg, sure_fg)
  1. 标记图像,背景为1, 未知区域为0, 前景从2开始标记


# 标记图像
ret, markers = cv.connectedComponents(sure_fg)
plt.subplot(245), plt.imshow(markers, 'gray'), plt.title('segmentation'), plt.xticks([]), plt.yticks([])# 所有标记加1,保证背景为1,而不是0
markers = markers+1# 未知区域设置位0
markers[unknow==255] = 0
plt.subplot(247), plt.imshow(markers), plt.title('segmentation'), plt.xticks([]), plt.yticks([])
  1. 应用分水岭算法分割图像


markers = cv.watershed(src, markers)

4-16 基于GrabCut算法的交互式前景提取

目标

  • 学习使用GrabCut算法提取图像前景

  • 创建一个交互式应用

原理

首先是画一个矩形,把需要分割的前景圈住,然后不断迭代地分割,直到把对象分割出来。但是,有时候分割的效果不是很好,有一些前景会被认为是背景,而有一些背景被认为是前景。这个时候,需要用户进行干预,用白色标注要提取为前景的区域,用黑色标注要作为背景的区域,然后,将标注后的图像作为掩膜,让算法继续迭代提取前景从而得到最终得结果。

GrabCut算法的具体实现过程:

  1. 画一个矩形。矩形外的区域被视为背景,矩形内的区域被视为未知区域。用户自定义的区域被视为硬标注区域,分割过程不会被改变。

  1. 初始化。标注前景、背景和硬标注的像素。

  1. 应用高斯混合模型对前景和背景进行建模。

  1. GMM学习和创建新的像素分布。未确定区域是被标注为前景还是背景,取决于它与硬标注像素再颜色统计上的关系。

  1. 根据像素分布情况生成一副图。图中的结点就是各个像素点。除此之外,还有源结点和交汇结点。每一个前景像素连接源结点,每一个背景像素连接交汇结点。

  1. 像素连接源结点或交汇结点的权重是由该像素是成为前景概率或背景概率决定的。像素间的权重是由边缘信息或像素相似度决定的。如果两个像素在颜色上区别很大,那么它们之间的权重就会很低。

  1. 使用mincut算法对图像进行分割。它将图切成具有最小成本函数的两个分离的源结点和交汇结点。成本函数是被切割边缘的所有权重的总和。切割后,连接源结点的像素成了前景,连接交汇结点的像素成了背景。

  1. 通过不断迭代,知道分类收敛为止。

OpenCV提供函数cv.grabCut()实现GraphCut算法。


# 基于矩形分割
cv.grabCut(img, mask, rect, bgdModel, fgdModel, 5, cv.GC_INIT_WITH_RECT)# 基于掩膜分割
cv.grabCut(img, mask, None, bgdModel, fgdModel, 5, cv.GC_INIT_WITH_MASK)

5 特征检测与描述

5-1 特征、角点

特征

图像特征就是指有意义的图像区域,具有独特性、易于识别性,比如角点、斑点以及高密度区。

角点

  • 灰度梯度的最大值对应的像素。

  • 两条线的交点。

  • 极值点。

5-2 Harris角点检测

目的

  • 学习Harris角点检测

  • 学习api函数:cv.cornerHarris()和cv.cornerSubPix()

原理

Harris角点检测是Harris和Stephens提出的检测算法。它的基本原理是计算窗口函数在图像的各个方向上移动的灰度变化值(梯度):

其中窗口函数可以选矩形窗口函数,也可以选高斯窗口函数:

可以看作像素值变化量(梯度),使用泰勒展开

其中分别是图像在x和y方向上的导数,带入上面公式:

最后,生成一个分数,判断窗口是否包含一个角点:

其中是H的两个特征值,这些特征值决定了一块区域是角点、边缘还是平面:

  • 很小时,即很小,区域是一块平面。

  • <0,即>> 或是 <<,区域是边缘。

  • 很大时,即很大,并且~,此时,区域内的是角点。

以上描述可以通过下图表示:

OpenCV中的Harris角点检测

OpenCV提供函数cv.cornerHarris()实现Harris角点检测。


dst = cv.cornerHarris(gray, 3, 3, 0.04)

亚像素精度的角点

OpenCV提供了另外一个函数cv.cornerSubPix()进一步优化角点检测的精度至亚像素精度。步骤如下:

  1. 找出角点


dst = cv.cornerHarris(gray, 3, 3, 0.04)
  1. 确定角点的质心


ret, labels, stats, centroids = cv.connectedComponentsWithStats(dst)
  1. 定义停止迭代的基准


criteria = (cv.TERM_CRITERIA_EPS+cv.TERM_CRITERIA_MAX_ITER, 100, 0.001)
  1. 细化角点


corners = cv.cornerSubPix(gray, np.float32(centroids), (5,5), (-1,-1), criteria)

5-3 Shi-Tomasi角点检测

目标

  • 学习另一种角点检测方法:Shi-Tomasi角点检测

  • 学习api函数:cv.goodFeaturesToTrack()

原理

Shi-Tomasi角点检测算法是J. Shi 和 C. Tomasi在Harris的基础上做了一个小的改进得到的。它与Harris角点检测效果相比,效果更好点。Harris角点检测响应函数为:

而Shi-Tomasi角点检测响应函数为:

如果大于阈值,就认为是角点。在空间中如下图所示:

只有当大于最小值时,才被认为是角点。

OpenCV提供了函数cv.goodFeaturesToTrack()进行Shi-Tomasi角点检测或Harris角点检测,它可以找出N个最好的角点。

在这个函数中,输入图像image必须为8位或32位单通道图像;maxCorners表示可以返回的最大角点数目;qualityLevel表示被认为是角点的可接受的最小特征值,取值范围为0~1,常用的值为0.1或者0.01;检测完所有角点后,还要进一步剔除掉一些距离较近的角点,minDistance表示返回的角点之间的距离不小于该值。

mask是可选参数,是一幅像素为布尔类型的图像,用于指定输入图像中参与角点计算的像素点。若mask的值设为NULL,则旋转整个图像;blockSize是计算导数自相关矩阵时指定点的领域,采用小窗口计算的结果比单点计算的结果要好。若useHarrisDetector的值为非0,则函数使用Harris的角点定义;若为0,则使用Shi-Tomasi的定义。当useHarrisDetector为k且非0,则k为用于设置Hessian自相关矩阵即对Hessian行列式的相对权重的权重系数。


corners = cv.goodFeaturesToTrack(gray, 30, 0.1, 10)

2023.4.6~2023.4.7

5-4 SIFT角点检测

目标

  • 学习SIFT算法

  • 学习如何查找SIFT关键点和描述符

原理

2004年,D.Lowe提出了一种新的角点检测算法——缩放不变特征转换(Scale-Invariant Feature Transform),简称SIFT。

SIFT算法主要包括了4个步骤:

  1. 尺度空间极值检测

从上图可看出,不能使用相同的窗口检测不同尺度空间的角点,检测小的角点可以使用小的窗口,检测大的角点可以使用大的窗口。为此,需要使用尺度空间滤波器。使用值不同的高斯拉普拉斯算子(LOG)对图像进行卷积。具有不同值的LOG可以检测不同尺寸的斑点。简而言之,相当于是一个比率因子。例如,上图,值低的高斯核适用检测小的角点,值高的高斯核适用于检测大的角点。因此,我们可以在尺度空间和二维平面找到局部最大值,这意味着在尺度中的处有一个潜在的关键点。

LOG定义公式:

其中,是高斯函数,

但是呢,LOG的计算量非常大,所以SIFT算法使用近似LOG的高斯差分来代替。高斯差分法(DOG)是指在相同分辨率下,分别使用值的高斯核和值的高斯核对图像进行模糊处理,得到的两个结果再进行相减。定义公式如下:

这个过程是在高斯金字塔的不同层级上执行的。如下图所示:

一旦获得DOG后,就会在图像的尺度空间和二维平面上搜索局部极值。例如,将图像上的一个像素点与其图像域(同一尺度空间)和尺度域(相邻的尺度空间)的所有相邻点进行比较,当其大于(或者小于)所有相邻点时,该点就是极值点。如下图所示,中间的检测点要和其所在图像的邻域8个像素点,以及其相邻的上下两层的邻域18个像素点,共26个像素点进行比较。

对于最上面或者最下面的那层是无法做极值检测的。

关于不同的参数,论文给出了一些指导性参数,octaves = 4,scale levels = 5,

  1. 关键点定位

上述找到的是一些离散的极值点,是DOG空间的局部极值点,精确定位极值点的一种方法是,对尺度空间DOG函数进行曲线拟合,计算其极值点,从而实现关键点的精确定位。

DOG对边缘也有强烈的响应,所以需要消除边界响应。为此,使用了类似Harris角点检测的思路。使用一个的Hessian矩阵(H)计算主曲率。从Harris角点检测可知,对于边缘,一个特征值比另一个特征值大。

如果这个比值大于阈值(在OpenCV中被称为边界阈值),该特征值将被丢弃。论文给出的值是10。

因此,它排除了部分低对比度的关键点和部分边缘的关键点,剩下的就是强兴趣点。

  1. 分配方向

为每个关键点指定方向,以实现图像旋转的不变性。在关键点附近获取一个邻域,并该区域梯度大小和方向。创建一个包含36个bins的方向直方图,由梯度幅值和高斯加权圆形窗口进行加权,其中等于1.5倍关键点大小。直方图中高于最高峰80%均考虑计算方向。创建了具有相同位置和尺度空间但方向不同的关键点

  1. 关键点描述子

现在创建了关键点描述子。在关键点周围选取16x16的邻域,并分为16个4x4大小的子块。每一个子块,又创建一个包含8bin的方向直方图。因此,总共有128个bin值可用。以向量的形式构成了关键点描述子。

  1. 关键点匹配

两幅图像的关键点是通过识别它们附近的邻域进行匹配的。但有时候,第二最接近匹配可能非常接近第一个。这是由于噪声或是一些其它因素造成的。往往这个时候,就要用上最近距离与第二最近距离的比率。如果比率大于0.8,则忽略它们。这样做大约消除了5%的正确匹配,然而消除了95%的错误匹配。

OpenCV中的SIFT

使用cv.SIFT_create()创建一个对象。


sift = cv.SIFT_create()

使用函数sift.detect()查找关键点,可以传入一张掩膜图像,查找指定区域的角点。返回的每一个关键点都包含了(x,y)坐标,有效邻域大小,角度等。

OpenCV还提供了函数cv.drawKeyPoints()用来绘制关键点大小的圆,甚至显示了方向。


kp = sift.detect(gray, None)
img = cv.drawKeypoints(gray, kp, img, flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

计算描述子,OpenCV提供了两种方法:

  1. 当已经找出关键点后,可以调用sift.compute()计算描述子。


kp, des = sift.compute(gray, kp)
  1. 如果还没有找出关键点,调用sift.detectAndCompute()直接找出关键点和描述子。


sift = cv.SIFT_create()
kp, des = sift.detectAndCompute(gray,None)

5-5 SURF角点检测

5-6 FAST特征检测

SIFT、SURF算法提取特征都很优秀,但是都非常耗时间。2010年Edward Rosten和Tom Drummond提出了一种FAST的特征检测方法。

FAST算法的步骤为:

  1. 从图像中选取一个像素,设它的亮度值为。下面判断它是否是一个特征点。

  1. 设定一个合适的阈值

  1. 以像素为中心,半径为3的圆上,有16个像素P1~P16。

  1. 如果这16个像素的圆上有n个连续的像素点,它们的像素值要么都比大,要么都比小,那么它就是一个角点。n的值可以设置为12或者9,实验证明选择9可能会有更好的效果。

上面的算法,对于图像中的每一个像素,如果都要去遍历其领域圆上的16个点的像素,也是比较耗时的。下面有一种快速测试的方法来排除一大部分非角点的像素。该方法只需要检查P1、P9、P5和P13四个像素,首先检测P1和P9像素,如果它们比P像素更亮或更暗,再去检测P5和P13像素,如果P是一个角点,那么P1、P9、P5和P13四个像素中至少有3个要比更亮或比更暗。如果上面两种情况都不成立,那么像素P肯定不是一个角点。图像中所有的像素都做上面的测试,符合条件的像素将作为候选角点,后续继续对所有的候选角点做全段测试,即检测圆上全部的16个像素。

上面的算法效率虽然很高了,但是还有以下缺点:

  • 当设置就无法使用上面的快速测试方法。

  • 检测出来的角点不是最优的,这是因为它的效率取决于问题的排序和角点的分布。

  • 高速测试的结果已经被抛弃。

  • 多个特征容易挤在一起。

前面的3点可以用机器学习方法解决,最后一点使用非最大值抑制法解决。

机器学习角点检测器

  1. 选择一系列图像进行训练,最好是目标应用场景下的多张图像。

  1. 对每一张图像运行FAST算法找出特征点。

  1. 针对每一个特征点,把它邻域圆上的16个点保存在一个vector中。对所有的图像都这样来处理,构建特征向量P。

  1. 邻域圆上16个像素中的每一个像素有以下的三种状态:

  1. 根据这些状态,特征向量P被细分为3个子集,

  1. 定义一个新的布尔变量,如果P是角点,就设为True,否则设为False。

  1. 使用ID3算法查询每一个子集。

  1. 递归应用在所有的子集中,直到熵为0。

  1. 被创建的决策树将用于其它图像中的FAST检测中。

非最大值抑制

针对多个特征点挤在一起的问题,使用非最大值抑制处理。

  1. 计算所有检测到的特征点的分数V,V是像素P分别与邻域圆上16个像素绝对值差的和。

  1. 确定两个相邻的特征点,并比较它们的V值。

  1. 抛弃V值最小的特征点。

总结

  • 它比现有的角点检测器快几倍。

  • 但是噪声高时鲁棒性不是很好,主要依赖一个阈值。

OpenCV中的FAST特征检测器

在OpenCV中使用FAST检测特征,可以指定一个阈值,是否应用非最大值抑制,使用邻域等。

邻域有3种类型的标志:cv.FAST_FEATURE_DETECTOR_TYPE_5_8, cv.FAST_FEATURE_DETECTOR_TYPE_7_12cv.FAST_FEATURE_DETECTOR_TYPE_9_16.


# 使用默认值初始化FAST对象
fast = cv.FastFeatureDetector_create()# 查找和绘制关键点
kp = fast.detect(img, None)
img1 = cv.drawKeypoints(src, kp, None, color=(255,0,0))# 设置参数
fast.setNonmaxSuppression(0)
fast.setThreshold(30)
fast.setType(1)# 设置参数

5-7 BRIEF(Binary Robust Independent Elementary Features)

目标

  • 学习BRIEF算法

原理

BRIEF是对已检测到的特征点进行描述,是一种二进制编码的描述子,而传统的描述子方法是利用区域灰度直方图描述特征点,大大加快了特征描述子建立的速度,也极大降低了特征匹配的时间。

由于BRIEF仅仅是特征描述子,所以首先要获取到特征点的位置,可以利用SIFT、SURF等算法检测特征点的位置,然后在特征点邻域利用BRIEF算法建立特征描述子。

  1. 为了减少噪声干扰,先对图像进行高斯滤波,方差=2,卷积核大小为

  1. 以特征点为中心,取大小为的区域作为大窗口。在大窗口中随机选取一对的子窗口,比较子窗口内的像素和,进行如下二进制赋值:

其中是点分别是随机点所在子窗口的像素和。

  1. 在大窗口中随机选取N对子窗口,重复步骤2的二进制赋值,形成一个二进制编码,这个编码就是对特征点的描述,即特征描述子。(一般N=256)

关于一对随机点的选择方法,有以下5种方法,其中方法2比较好:

  1. ,准则采样服从各向同性的同一高斯分布

  1. ,采样分为2步进行:首先在原点处为进行高斯采样,然后在中心为

算法优点

  1. 占用内存小

  1. 运行时间短

算法缺点

  1. 不具备旋转不变性

  1. 不具备尺度不变性

5-8 ORB

原理

ORB——Oriented FAST and Rotated BRIEF,是一种快速特征点提取和描述的算法。该算法主要分为两部分:特征点提取和特征点描述。ORB是将FAST特征点的检测方法与BRIEF特征描述子结合起来,并在它们原来的基础上做了改进和优化。

  1. 特征点的检测

ORB采用FAST算法来检测特征点。即取一个像素与它周围的像素比较,如果它和其中大部分的像素都不一样就可以认为它是一个特征点。

具体计算过程:

  • 在图像中选取一个像素P,设它的灰度值为,下面判断该像素P是否为特征点。

  • 设定一个合适的阈值t:比较两个像素灰度值之差的绝对值大于t时,就认为这两个像素不同。

  • 将像素P与邻域圆上16个像素进行比较。

  • 如果这16个点中有连续的n个点都与像素P不同,那么就可以认为像素P是一个特征点。这里n设定为12。

为了提高效率,现有一个高效的测试,快速地排除一大部分非特征点。该测试只需要将像素P与邻域圆上1、9、5和13位置像素进行比较,首先与1和9位置进行比较,如果与1和9位置都不同,再与5和13位置的像素进行比较,如果是一个特征点,那么上述4个像素中至少有3个是不同的。

  1. 特征点的描述

计算特征描述子

得到特征点后需要描述这些特征点的属性,这些属性的输出即为特征点的描述子。

ORB采用BRIEF算法来计算特征点的描述子。具体步骤如下:

  • 以关键点P为圆心,取一个的大窗口。

  • 在大窗口中随机选取N个点对,N=128、256或512,假设选取的点对分别标记为:

  • 定义值:

  • 分别对已选取的点对进行操作,将得到的结果进行组合。

假如:

则最终的描述子为:1011


# 创建ORB对象
orb = cv.ORB_create()# 查找关键点
kp = orb.detect(img, None)# ORB计算描述子
kp, des = orb.compute(img, kp)# 绘制关键点, 没有尺寸和方向
img2 = cv.drawKeypoints(img, kp, None, color=(0,255,0), flags=0)

5-9 特征匹配

目的

  • 学习图像的特征匹配

  • 使用OpenCV的暴力匹配和FLANN匹配

原理

暴力特征匹配,采用第一组特征的一个特征描述子,使用一些距离计算与第二组中的所有特征点进行匹配,并返回距离最短的一个特征点。

OpenCV中的暴力匹配,先使用cv.BFMatcher()创建一个BFMatcher对象,它有两个可选参数,第一个参数normType,指定了计算距离的方式,默认选择cv.NORM_L2,比较适合SIFT、SURF等算法。对于基于二进制字符的描述子,如ORB、BRIEF、BRISK等,应使用cv.NORM_HAMMING,它使用Hamming距离进行测量,如果ORB中WTA_K==3或4,应该使用cv.NORM_HAMMING2。

第二个参数是crossCheck,是一个布尔类型,默认为false,如果为true,则匹配器仅返回具有值(i,j)的匹配,使得集合A的第i个描述子与集合B中的第j个描述子做为最佳匹配,反之亦然。即两个集合中的这两个特征应该要相互匹配。

创建完BFMatcher对象后,可以使用BFMatcher.match()和BFMatcher.knnMatch()两种方法进行匹配,其中BFMatcher.match()返回一个最佳匹配结果,BFMatcher.knnMatch()返回k个最佳匹配结果,k值由用户自定义。

使用cv.drawMatches()绘制匹配结果。水平堆叠两张图像,并从第一张图像到第二张图像绘制直线,显示最佳匹配。

使用cv.drawMatchesKnn()绘制所有k个最佳匹配。

对ORB描述子使用暴力匹配


# 读取待匹配的图片和模板图片
img1 = cv.imread('../../images/board/board-03.png', 0)
img2 = cv.imread('../../images/board/board-04.png', 0)# 初始化ORB检测器
orb = cv.ORB_create()# 检测关键点和描述符
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)# 使用BFMatcher进行暴力匹配
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)# 根据排序结果进行排序
matches = sorted(matches, key=lambda x: x.distance)# 绘制匹配结果
img3 = cv.drawMatches(img1, kp1, img2, kp2, matches[:50], None, flags=2)

对SIFT描述符进行暴力匹配


# 读取待匹配的图片和模板图片
img1 = cv.imread('../../images/board/board-03.png', 0)
img2 = cv.imread('../../images/board/board-04.png', 0)# 初始化SIFT检测器
sift = cv.xfeatures2d.SIFT_create()# 检测关键点和描述符
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)# 使用BFMatcher进行暴力匹配
bf = cv.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)# 根据Lowe's ratio test筛选匹配结果
good_matches = []
for m, n in matches:if m.distance < 0.75 * n.distance:good_matches.append(m)

FLANN匹配

FLANN——Fast Library for Approximate Nearest Neighbors,指的是最近邻快速库。它包含了一组针对大型数据进行快速最近邻域搜索和高维特征进行优化的算法。对于大型数据集,它比BFMatcher速度更快。

对于FLANN匹配,需要传递两个字典作为参数,用来指定要使用的算法及其相关参数等。第一个字典是IndexParams。对于不同的算法,参数也不同。

当使用SIFT和SURF算法时,可以传递以下信息:


FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)

当使用ORB算法时,可以传递以下信息:


FLANN_INDEX_LSH = 6
index_params= dict(algorithm = FLANN_INDEX_LSH, table_number = 6, key_size = 12, multi_probe_level = 1)

第二个字典是SearchParams。它指定索引中的树应该递归遍历的次数。值越高,精度越好,也更费时间。如果想改变这个值,传递search_params = dict(checks=100)。


# 读取待匹配的图片和模板图片
img1 = cv.imread('../../images/img1.png', 0)
img2 = cv.imread('../../images/img2.png', 0)# 初始化SIFT检测器
sift = cv.xfeatures2d.SIFT_create()# 检测关键点和描述符
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)# FLANN匹配器参数设置
index_params = dict(algorithm=0, trees=5)
search_params = dict(checks=50)# 创建FLANN匹配器对象并进行匹配
flann = cv.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1, des2, k=2)# 根据Lowe's ratio test筛选匹配结果
good_matches = []
for m, n in matches:if m.distance < 0.75 * n.distance:good_matches.append(m)

5-10 特征匹配+单应性查找对象

目标

  • 本节将混合特征匹配与calib3d模块中的findHomography在复杂图像上查找对象。

原理

OpenCV中的calib3d模块提供一个函数cv.findHomographt(),当我们传入两张图像的点集后,至少4个点以上,就能找出这两张图像的透视变换关系,然后使用函数cv.perspectiveTransform()进行转换,再进行特征匹配。


import numpy as np
import cv2 as cv
from matplotlib import pyplot as pltMIN_MATCH_COUNT = 10img1 = cv.imread('../../images/mat2.jpg', 0)
img2 = cv.imread('../../images/mat1.jpg', 0)# 初始化sift检测器
sift = cv.xfeatures2d.SIFT_create()# 查找关键点
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)# 特征匹配:knn匹配
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks = 50)flann = cv.FlannBasedMatcher(index_params, search_params)matches = flann.knnMatch(des1, des2, k=2)good = []
for m, n in matches:if m.distance < 0.7*n.distance:good.append(m)if len(good) > MIN_MATCH_COUNT:src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1,1,2)dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1,1,2)M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC, 5.0)matchesMask = mask.ravel().tolist()h, w = img1.shape#print(img1.shape)pts = np.float32([[0, 0], [0, h-1], [w-1, h-1], [w-1, 0]]).reshape(-1,1,2)dst = cv.perspectiveTransform(pts, M)img2 = cv.polylines(img2, [np.int32(dst)], True, 255, 3, cv.LINE_AA)draw_params = dict(matchColor = (0, 255, 0), singlePointColor = None, matchesMask = matchesMask, flags = 2)img3 = cv.drawMatches(img1, kp1, img2, kp2, good, None)plt.imshow(img3, 'gray'), plt.show()

附录 OpenCV函数

2.1

  1. cv.imread()

原型:


retval = cv.imread(filename[,flags])

功能:

读取图像。

参数:

  • filename —— 加载图像的路径。

  • flags —— 加载图像的模式,常用的是cv.IMREAD_COLOR、cv.IMREAD_GRAYSCALE。

2.3

  1. cv.line()

原型:


img = cv.line(img, pt1, pt2, color[,thickness[,lineType[,shift]]])

功能:

绘制直线

参数:

  • img —— 输入图像。

  • pts1 —— 起始点

  • pts2 —— 终止点

  • color —— 直线颜色

  • thickness —— 直线宽度

  • lineType —— 直线类型

  • shift —— 坐标系中的小数位数

  1. cv.circle()

原型:


img = cv.circle(img, center, radius, color[,thickness[,lineType[,shift]]])

功能:

绘制圆。

参数:

  • img —— 输入图像。

  • pts1 —— 左上角坐标

  • pts2 —— 右下角坐标

  • color —— 直线颜色

  • thickness —— 线宽度,如果为-1,为填充。

  • lineType —— 线类型

  • shift —— 坐标系中的小数位数

  1. cv.rectangle()

原型:


img = cv.rectangle(img, pt1, pt2, colo[,thickness[,lineType[,shift]]])
img = cv.rectangle(img, rect, color[,thickness[,lineType[,shift]]])

功能:

绘制矩形。

参数:

  • img —— 输入图像。

  • center —— 起始点

  • radius —— 终止点

  • color —— 直线颜色

  • thickness —— 线宽度,如果为-1,为填充。

  • lineType —— 线类型

  • shift —— 坐标系中的小数位数

  1. cv.ellipse()

原型:


img    =    cv.ellipse(img, center, axes, angle, startAngle, endAngle, color[, thickness[, lineType[, shift]]])
img    =    cv.ellipse(img, box, color[, thickness[, lineType]])

功能:

绘制椭圆

参数:

img

输入图像

center

椭圆中心

axes

椭圆的长轴和短轴

angle

椭圆的旋转角度

startAngle

椭圆的起始角度

endAngle

终止角度

color

椭圆颜色

thickness

线宽,-1为填充。

lineType

线的类型。

shift

坐标系中的小数位数

  1. cv.putText()

原型:


img = cv.putText(img, text, org, fontFace, fontScale, color[,thickness[,lineType[,bottomLeftOrigin]]])

功能:

写入文本。

参数:

img

输入图像

text

文本内容

org

文本的左下角坐标

fontFace

字体类型

fontScale

字体大小

color

字体颜色

thickness

线宽

lineType

线类型

bottomLeftOrigin

布尔类型,如果为True,图像的原点在左下角,否则原点在左上角

3.1

  1. cv.copyMakeBorder()

原型:


dst = cv.copyMakeBorder(src, top, bottom, left, right, borderType[, dst[, value]])

功能:

填充图像边框。

参数:

src

Source image.

top

指定源图上、下、左和右外推像素的个数。例如, top=1, bottom=1, left=1, right=1 表示上、下、左和右各新建1个像素宽的边框。

bottom

left

right

borderType

填充边框的类型。

value

如果borderType==BORDER_CONSTANT ,表示边框的值。

3.2

  1. cv.addWeighted()

原型:


dst=cv.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]])

功能:

计算两个图像(数组)的加权求和。

参数:

src1

first input array.

alpha

weight of the first array elements.

src2

second input array of the same size and channel number as src1.

beta

weight of the second array elements.

gamma

scalar added to each sum.

dtype

optional depth of the output array; when both input arrays have the same depth, dtype can be set to -1, which will be equivalent to src1.depth().

  1. dst=cv.blur(src, ksize[, dst[, anchor[, borderType]]])

参数

说明

src

输入图像

ksize

核的大小

anchor

核的参考点

borderType

边界填充类型

  1. dst = cv.boxFilter(src, ddepth, ksize[, dst[, anchor[, normalize[, borderType]]]])

参数

说明

src

输入图像

ddept

输出图像的位深

ksize

核的大小

anchor

核的参考点

normalize

是否归一化,默认值true

borderType

边界填充类型

  1. dst=cv.GaussianBlur(src, ksize, sigmaX[, dst[, sigmaY[, borderType]]])

对于高斯模糊,需要指定ksize、sigmaX和sigmaY的值。ksize代表滤波器窗口的宽度和高度,只能是正值和奇数。sigmaX和sigmaY分别代表了X和Y方向上的标准方差,如果只给出了sigmaX,那么sigmaY = sigmaX;如果都设置为0,系统会根据ksize来计算sigmaX和sigmaY。

参数

说明

src

输入图像

ksize

核的大小,正值和奇数

sigmaX

X方向的标准方差

sigmaY

Y方向的标准方差

borderType

边界填充类型

  1. dst = cv.bilateralFilter(src, d, sigmaSpace[,dst[,borderType]])

参数

说明

src

输入图像

d

像素邻域的直径

sigmaColor

滤波时选取的颜色插值范围

sigmaSpace

坐标空间中的simga值

borderType

边界填充类型

4.5

  1. dst = cv.erode(src, kernel, iterations)

参数

说明

src

输入图像

kernel

卷积核

iterations

迭代次数

  1. dst = cv.dilate(src, kernel, iterations)

参数

说明

src

输入图像

kernel

卷积核

iterations

迭代次数

  1. dst = cv.morphologyEx(src, op, kernel)

参数

说明

src

输入图像

op

形态学操作类型

kernel

卷积核

4.6

  1. dst = cv.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])

用Sobel算子对图像进行1阶、2阶等求导

  • src——源图像;

  • ddepth——用来确定目标图像的深度或类型(如CV_32F),假如src是一幅8位图像,那么dst需要至少CV_16S的深度保证不出现溢出;dx和dy是求导的顺序,其取值范围为0、1和2。0表示在这个方向上不求导,dx和dy不能同时设为0;

  • ksize——是一个奇数,表示调用的滤波器的宽和高。Sobel算子一个好处就是可以修改核的大小。如果ksize设为1,系统会自动改为3。如果ksize=-1,将会调用Scharr卷积核,相当于作Scharr算子用。


dst = cv.Sobel(img, cv.CV_64F, 1, 0, ksize=5)
  1. dst = cv.Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]])

使用Scharr算子在图像x或y方向上进行近似一阶求导。

参数参考sobel算子。


dst = cv.Scharr(img, cv.CV_64F, 1, 0)
  1. dst = cv.Laplacian(src, ddepth[, dst[, ksize[, scale[, borderType]]]]])

使用Laplacian算子对图像进行求导。


dst = cv.Laplacian(img, cv.CV_64F, ksize=5)

4.7

  1. cv.Canny()

edges = cv.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]])
edges = cv.Canny(dx, dy, threshold1, threshold2[, edges[, L2gradient]])

使用Canny算法查找边缘。

  • image —— 输入图像,必须是单通道图像

  • threshold1 —— 低阈值

  • threshold2 —— 高阈值

  • apertureSize —— 内部调用的Sobel导数算子的核大小,默认值为3

  • L2gradient —— 选择计算方向梯度的类型,如果为true,将选择L2范数(精度高速度慢),否则选择L1范数


edges = cv.Canny(img, 100, 200)

4.8

  1. cv.pyrDown()

dst = cv.pyrDown(src[, dst[, dstsize[, borderType]]])

对图像进行平滑和向下采样。

  • src —— 输入图像

  • dstsize —— 输出图像的大小,默认是((src.cols+1)/2, (src.rows+1)/2),另外可以设置dstsize,指定输出图像的大小,但dstsize必须满足:


dst = cv.pyrDown(img)
  1. cv.pyrUp()

dst = cv.pyrUp(src[, dst[, dstsize[, borderType]]])

对图像进行向上采样再平滑。

  • src —— 输入图像

  • dstsize —— 输出图像大小,默认是Size(src.cols*2, src.rows*2),另外可以设置dstsize,指定输出图像的大小,但dstsize必须满足:

4.9

  1. cv.findContours()


contours,hierarchy = cv.findContours(image,mode,method[,contours[,hierarchy[,offset]]])

二值化图像中查找轮廓。

  • image —— 源图像,8位单通道图像,并且应该被转化成二值的图像

  • contours —— 返回值,找到的轮廓,是一组数组,例如,std::vector<std::vector<cv::Point>>,在一个轮廓vector中,contours[i]是一条特定轮廓,而contours[i][j]则是contour[i]中的一个点。

  • hierarchy —— 返回值,可选项,输出所有轮廓的树结构,输出是一个数组,每条轮廓对应数组中的一个值。数组中的每个值都是一个四元数组,每个元素代表一个与当前节点有特定链接的节点。

  • mode —— 指定轮廓提取方式:1)cv.RETR_EXTERNAL:只检索最外层轮廓。2)cv.RETR_LIST:检索所有轮廓并保存到表(List)中。3)cv.RETR_CCOMP:检索所有的轮廓,并将它们组织成双层结构,顶层边界是所有成分的外部边界,第二层边界是孔的边界。4)cv.RETR_TREE:检索所有轮廓并重新建立网状轮廓结构。

  • method —— 轮廓逼近方法。1)cv.CHAIN_APPROX_NONE:将轮廓编码中的所有点转换为点。

2)cv.CHAIN_APPROX_SIMPLE:压缩水平、垂直、斜的部分,只保留最后一个点。3)cv.CHAIN_APPROX_TC89_L1或cv.CHAIN_APPROX_TC89_KCOS

  • offset —— 补偿量,可选项。

  1. cv.drawConoturs()

原型:


image = cv.drawContours(image, contours, contourldx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]])

功能:

绘制或填充轮廓。

参数:

  • image —— 待绘制轮廓的图像

  • contours —— 要绘制的轮廓

  • contourldx —— 轮廓的id,如果为-1,将绘制所有的轮廓

  • color —— 绘制轮廓颜色

  • thickness —— 线宽

  • lineType —— 线型

示例:


cnt = contours[0]
img = cv.drawContours(img, [cnt], -1, (0,255,0), 2)

  1. cv.moments()

原型:


retval = cv.moments(array[, binaryImage])

功能:

计算多边形或栅格所有的一阶、二阶和三阶矩。

参数:

  • array —— 栅格图像(单通道,8位或2D浮点型数组)或2D数组

  • binaryImage —— 这个参数只对图像起作用,如果是True,图像的所有非0像素都被置为1。

返回值:

  • moments —— 特征矩。

  1. cv.contourArea()

原型:


retval = cv.contourArea(contour[,oriented])

功能:

计算轮廓的面积。

参数:

  • contour —— 输入轮廓

  • oriented —— 默认值为false,表示返回的retval是一个绝对值,如果为true,返回值包含正、负号,用来表示轮廓是顺时针还是逆时针。

返回值:

  • retval —— 轮廓的面积

  1. cv.arcLength()

原型:


retval = cv.arcLength(curve, closed)

功能:

计算轮廓的周长。

参数:

  • curve —— 输入轮廓

  • closed —— 用来表示轮廓是否为封闭,如果为True时,表示轮廓是封闭。

返回值:

  • retval —— 轮廓的周长

  1. cv.approxPolyDP()

原型:


approxCurve = cv.approxPolyDP(curve, epsilon, closed[,approxCurve])

功能:

根据给定的精度,逼近一条多边形曲线。

参数:

  • curve —— 输入轮廓

  • epsilon —— 逼近精度,原始曲线与逼近多边形的最大距离,通常设置为轮廓周长的几分之一。

返回值:

  • approxCurve —— 逼近的多边形

  1. cv.convexHull()

原型:


hull = cv.convexHull(points[, hull[,clockwise[,returnPoints]]])

功能:

根据点集获取凸包。

参数:

  • points —— 轮廓,标准模板库向量对象或Mat类型

  • hull —— 返回的凸包。

  • clockwise —— 方向标志,布尔类型,如果为True,凸包将按顺时针方向排列,否则,将以逆时针方向排列。

  • returnPoints —— 默认值为true,如果points是标准模板库向量对象,此参数会被忽略,如果是Mat类型,需要返回点时,returnPoints需要置为true,而需要返回索引时,returnPoints必须设置为false。

返回值:

  • hull —— 获取的凸包

  1. cv.isContourConvex()

原型:


retval = cv.isContourConvex(contour)

功能:

检查曲线的凸性。

参数:

  • contour —— 输入轮廓,std::vector型或Mat型,不能自交。

返回值:

  • retval —— 布尔类型

  1. cv.boundingRect()

原型:


retval = cv.boundingRect(array)

功能:

获取轮廓的直角矩形边界框。

参数:

  • array —— 输入轮廓

返回值:

retval —— cv::Rect(x,y,w,h)结构

  1. cv.minAreaRect()

原型:


retval = cv.minAreaRect(points)

功能:

获取轮廓最小外接矩形边界框。

参数:

  • points —— 输入轮廓或点集

返回值:

retval —— cv::Rect(x,y,w,h)结构

  1. cv.boxPoints()

原型:


points = cv.boxPoints(box[,points])

功能:

获取旋转矩形的4个顶点坐标。

参数:

  • box —— 输入的旋转矩形

返回值:

  • points —— 数组,矩形的4个顶点

  1. cv.minEncloseingCircle()

原型:


center, radius = cv.minEnclosingCircle(points)

功能:

查找一个包围2D点序列的最小面积圆。

参数

  • points —— 点集、轮廓

返回值:

  • center —— 圆中心

  • radius —— 圆半径

  1. cv.fitEllipse()

原型:


retval = cv.fitEllipse(points)

功能:

拟合一个包围一些列点的椭圆。

参数:

  • points —— 点集、轮廓

返回值:

  • retval —— 返回一个椭圆内嵌在里面的旋转矩形,((x,y),(w,h),angle)

  1. cv.fitLine()

原型:


line = cv.fitLine(points, distType, param, reps, aeps[, line])

功能:

根据2D或3D点集拟合直线。

参数:

  • points —— 点集、轮廓

  • distType —— M估计器使用的距离类型,拟合直线时,要使输入点到拟合直线的距离之和最小,类型如下:

  • param —— 距离参数,与所选的距离类型有关。如果设置为0,系统会选择一个最优值。

  • reps —— 拟合直线所需要的径向精度,一般设为0.01

  • aeps —— 拟合直线所需要的角度精度,一般设为0.01

  1. cv.findNonZero()

原型:


idx = cv.findNonZero(src[, idx])

功能:

获取非零像素坐标列表。

参数:

  • src —— 单通道图像。

返回值:

  • idx —— 非零像素坐标坐标索引。

  1. cv.minMaxLoc()

原型:


minVal, maxVal, minLoc, maxLoc = cv.minMaxLox(src[, mask])

功能:

获取最小值、最大值及它们的坐标。

参数:

  • src —— 单通道图像

  • mask —— 掩膜图像

返回值:

  • minVal —— 最小值

  • maxVal —— 最大值

  • minLoc —— 最小值位置

  • maxLoc —— 最大值位置

  1. cv.mean()

原型:


retval = cv.mean(src[,mask])

功能:

计算数组元素的平均值。

参数:

  • src —— 1到4通道图像

  • mask —— 掩膜图像

  1. cv.convexityDefects()

原型:


cv.convexityDefects(contour, convexhull[, convexityDefects]

功能:

获取轮廓的凸缺陷。

参数:

  • contour —— 输入轮廓

  • convexhull —— 凸包,在查找该凸包时,函数cv.convexHull()的参数returnPoints的值必须是False。

  1. cv.HuMoments()

原型:


hu = cv.HuMoments(m[,hu])

功能:

获取7个Hu矩。

参数:

  • m —— 由函数cv.moments()计算得到的矩特征值。

返回值:

hu —— 返回的Hu矩值。

  1. cv.matchShapes()

原型:


retval = cv.matchShapes(contour1, contour2, method, parameter)

功能:

比较两个形状或轮廓。

参数:

  • contour1 —— 第1条轮廓或灰度值图像

  • contour2 —— 第2条轮廓或灰度值图像

  • method —— 比较两个Hu矩的方法

其中,表示对象1,表示对象2,表示的Hu矩,表示的Hu矩。

  • parameter —— 该参数已不提供支持,设为0即可。

4.10

  1. cv.calcHist()

原型:


hist = cv.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]])

功能:

计算直方图。

参数:

  • images —— 源图像,uint8或float32,它应该被放到方括号中,如[images]。

  • channels —— 也是需要放到方括号中,是计算直方图通道的索引。例如,灰度图像,它的值为[0];如果是彩色图像,值可以是[0]、[1]、[2]分别计算蓝色、绿色和红色的直方图。

  • mask —— 掩膜图像。如果设为None,就计算整幅图像的直方图;如果只是计算图像部分区域的直方图,就需要创建一张掩膜图像。

  • histSize —— BINS的值,需要放到[]里。

  • range —— 范围

  1. cv.equalizeHist()

原型:


dst = cv.equalizeHist(src[.dst])

功能:

对灰度图像进行直方图均衡。

参数:

src

Source 8-bit single channel image.

  1. cv.createCLAHE()

原型:


retval = cv.createCLAHE([,clipLimit[,tileGridSize]])

功能:

创建一个指向类CLAHE的智能指针并初始化。

参数:

clipLimit

对比度限制的阈值。

tileGridSize

将输入图像划分为M*N块,然后对每一块应用直方图均衡。

  1. cv.calcBackProject()

原型:


dst = cv.calcBackProject(images, channels, hist, ranges, scale[,dst])

功能:

计算直方图的反向投影。

参数:

images

Source arrays. They all should have the same depth, CV_8U, CV_16U or CV_32F , and the same size. Each of them can have an arbitrary number of channels.

channels

通道数。

hist

输入的直方图

ranges

Array of arrays of the histogram bin boundaries in each dimension.

scale

Optional scale factor for the output back projection.

uniform

Flag indicating whether the histogram is uniform or not (see above).

4.12

  1. cv.matchTemplate()

原型:


res = cv.matchTemplate(image, templ, method[, result[, mask]])

功能:

模板匹配。

参数:

image

输入图像。

templ

模板图像。

method

匹配方法。

mask

模板图像掩膜。

匹配方法:

  • TM_SQDIFF —— 平方差匹配法

利用平方差来进行匹配,匹配越好,匹配值就越小,最好为0。

  • TM_SQDIFF_NORMED —— 归一化平方差匹配法

  • TM_CCORR —— 相关匹配法

将模板与图像进行乘法操作,较大的数表示匹配程度较高, 0表示匹配最差。

  • TM_CCORR_NORMED —— 归一化相关匹配法

  • TM_CCOEFF —— 相关匹配法

将模板对其均值的相对值与图像对其均值的相对值进行匹配,1表示完美的匹配,-1表示最糟糕的匹配,0表示没有任何相关性。

其中,

  • TM_CCOEFF_NORMED —— 归一化相关匹配法

4.13

  1. cv.HoughLines()

原型:


lines = cv.HoughLines(image, rho, theta, threshold[, lines[, srn[, stn[, min_theta[, max_theta]]]]])

功能:

在二值图像中使用标准霍夫变换查找直线。

参数:

image

输入图像。8位,单通道,二值图像。

rho

以像素为单位的距离r的精度,一般使用精度是1。

theta

角度的精度,一般为pi/180

threshold

阈值。该值越小,判断出的直线就越多。

返回值:

lines —— 返回一个直线的vector。每个vector包含2~3个元素:

  1. cv.HoughLinesP()

原型:


lines= cv.HoughLinesP(image, rho, theta, threshold[, lines[, minLineLength[, maxLineGap]]])

功能:

概率霍夫直线变换。

参数:

image

输入图像。8位,单通道,二值图像。

rho

以像素为单位的距离r的精度,一般使用精度是1。

theta

角度的精度,一般为pi/180

threshold

阈值。该值越小,判断出的直线就越多。

minLineLength

直线的最小距离。

maxLineGap

线段间的最大间隙。

返回值:

lines —— 返回直线的vector。每个vector包含4个元素:

  1. cv.HoughCircles()

原型:


circles = cv.HoughCircle(image, method, dp, minDist[, circles[, param1[, param2[, minRadius[, maxRadius]]]]])

功能:

霍夫圆变换

参数:

image

输入图像。8位,单通道。

method

检测方法。

dp

累计器分辨率。即图像分辨率与圆心累计器分辨率的比例。

minDist

圆心间的最小距离。该参数如果设置太小,那么在检测出一个真圆的同时,会检测出多个邻近的假圆;如果设置太大,有些圆会丢失。

param1

特定方法的第一个参数。方法如果是HOUGH_GRADIENT和HOUGH_GRADIENT_ALT,那么它就是Canny边缘检测器的高阈值。HOUGH_GRADIENT_ALT使用Scharr算法计算图像的导数,因此阈值设置比较高。

param2

特定方法的第二个参数。方法如果是HOUGH_GRADIENT,它就是检测圆心阶段的累加器阈值。如果设置太小,就会检测出更多的伪圆。累加器值较大的圆会先返回。方法如果是HOUGH_GRADIENT_ALT,这是圆的完美度量。越接近1,算法选择的圆形状就越好。在大多数情况下,0.9就是做好的。如果想要获取更好的小圆,可能需要把该值降低到0.85,0.8甚至是更小。但是,也会通过minRadius和maxRadius限制了搜索的范围,避免了更多的伪圆。

minRadius

最小的圆半径。

maxRadius

最大的圆半径。如果<=0,使用最大的图像尺寸。如果<0,HOUGH_GRADIENT只返回中心而没有半径。HOUGH_GRADIENT_ALT会计算圆的半径。

返回值:

circles —— 返回一个vector,么个vector包含3或4个元素,(x, y, radius)和(x,y,radius,votes)。

4.14

  1. cv.distanceTransform()

原型:


dst = cv.distanceTransform(src, distanceType, maskSize[, dst[, dstType]])

功能:

计算源图像中的每个像素到最近的零像素的距离。

参数:

src

输入图像。8位,单通道二值化图像。

distanceType

距离的类型。

maskSize

掩膜尺寸。如果distanceType=cv.DIST_L1或cv.DIST_C,maskSize被强制设置为3,因为3x3掩膜得到的结果与5x5甚至更大尺寸掩膜得到的结果一样。

返回值:

dst —— 计算距离后的图像。是一副8位或32位浮点,单通道图像,大小与源图像相同。

  1. cv.connectedComponents()

原型:


retval, labels = cv.connectedComponents(image, [, labels[, connectivity[, Itype]]])

功能:

计算布尔图像中被标记图像的连通组件。

参数:

image —— 8位单通道被标记的图像

4.16

  1. cv.grabCut()

原型:


mask, bgdModel, gfbModel = cv.grabCut(img, mask, rect, bgdModel, fgbModel, iterCount[, mode])

功能:

执行GraphCut图像分割算法。

参数:

img

输入图像。8位3通道图像。

mask

掩膜图像。8位单通道图像。用于确定前景区域、背景区域和不确定区域。有4种形式:GC_BGD(表示确定的背景区域)、GC_FGD(表示确定的前景区域)、GC_PR_BGD(表示可能的背景区域)和GC_PR_FGD(表示可能的前景区域)。

rect

包含前景对象的矩形区域。矩形外的区域被视为确定为背景,当mode==GC_INIT_WITH_RECT时,rect才有效。

bgdModel

背景模板的临时数组。

fgdModel

前景模板的临时数组。

iterCount

算法的迭代次数。

mode

迭代模式:

  1. GC_INIT_WITH_RECT —— 使用矩形模板。

  1. GC_INIT_WITH_MASK —— 使用自定义模板。

  1. GC_EVAL —— 修复模式。

  1. GC_EVAL_FREEZE_MODEL —— 使用固定模式。

5.2

  1. cv.cornerHarris()

原型:


dst = cv.cornerHarris(src, blockSize, ksize, k[, dst[, borderType]])

功能:

Harris角点检测。

参数:

src

输入图像。单通道,8位或浮点型图像。

blockSize

邻域大小,即检测窗口的大小。

ksize

Sobel的卷积核。

k

权重系数,经验值,一般取0.02~0.04之间。

返回值:

dst

存储Harris检测器的响应值。

  1. cv.cornerSubPix()

原型:


corner = cv.cornerSubPix(image, corners, winSize, zeroZone, criteria)

功能:

细化角点位置。

参数:

image

输入图像。单通道、8位或浮点类型图像。

corners

输入角点初始坐标及输出细化后角点的坐标。

winSize

搜索窗口尺寸的半长。例如,如果winSize = Size(5,5),那么搜索窗口大小为:(5∗2+1)×(5∗2+1)=11×11

zeroZone

根据一下公式,还没有完成求和的搜索区域中间死区大小的一半。它有时被用来避免自相关矩阵中可能的奇异点。(-1,-1)表示没有这样的尺寸大小。

Half of the size of the dead region in the middle of the search zone over which the summation in the formula below is not done. It is used sometimes to avoid possible singularities of the autocorrelation matrix. The value of (-1,-1) indicates that there is no such a size.

criteria

在细化角点的迭代过程中,终止迭代的准则条件。要么当达到准则条件的最大值时终止,要么当角点的位置移动小于时终止。

Criteria for termination of the iterative process of corner refinement. That is, the process of corner position refinement stops either after criteria.maxCount iterations or when the corner position moves by less than criteria.epsilon on some iteration.

  1. cv.connectedComponentsWithStats()

原型:


retval, labels, stats, centroids = cv.connectedComponentsWithStats(image[, labels[, stats[, centroids[, connectivity[, Itype]]]]])

功能:

计算和标注图像的连通区域,统计每块连通区域的矩形边界框、面积及质心坐标。

参数:

image —— 输入图像,8位、单通道图像。

返回值:

retval

retval = 连通区域数+1个背景,

labels

一个与源图像大小一样,背景被标注为0,其它连通区域从1开始标注。

stats

每块连通区域的统计信息,[x,y,w,h,area],其中area为各个连通区域的面积。

centroids

每一块连通区域的质心坐标

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/bicheng/85937.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年- H87-Lc195--287.寻找重复数(技巧,二分查找OR动态规划)--Java版

1.题目描述 2.思路 3.代码实现 public class H287 {public int findDuplicate(int[] nums) {// 重复数字可能的最小值int min1;// 重复数字可能的最大值&#xff0c;数组长度为 n&#xff0c;数字范围是 [1, n-1]int maxnums.length-1;while(min<max) {// 防止溢出&#xf…

PVE使用ubuntu-cloud-24.img创建虚拟机并制作模板

前言 在使用pve时,虽然它已经可以克隆虚拟机,已经极大提升了创建虚拟机速度,但创建完成后,不可避免还是要配置下网络,因为服务器要使用静态IP,克隆出的机器需要重新设置新的IP地址,有没有连这一步都省了方法呢?有,就是Cloud-Init 创建虚拟机模板 1. 下载ubuntu-clo…

LLM增强检索---GraphRAG + LangGraph项目实战

专栏&#xff1a;大模型垂直应用技术​ ​​​​ 个人主页:云端筑梦狮 大模型应用落地亟需解决的核心问题有一个是&#xff1a;如何与私域数据交互。私域数据主要的问题是&#xff1a;需要有效地将企业数据整合进大语言模型中。由于大模型的上下文处理能力有限&#xff0c;一…

修改SSH端口实战

本次实战主要涉及SSH端口的修改和配置。首先&#xff0c;对master、slave1和slave2三台云主机的SSH配置文件进行修改&#xff0c;指定新的SSH端口&#xff0c;并重启SSH服务。接着&#xff0c;通过FinalShell工具修改连接端口&#xff0c;实现SSH连接到三台云主机。然后&#x…

PyTorch中的permute, transpose, view, reshape和flatten函数详解(已解决)

1.permute permute函数用于重新排列张量的维度。它接受一个元组作为参数&#xff0c;表示新的维度顺序。例如&#xff0c;如果我们有一个形状为(2, 3)的二维张量&#xff0c;我们可以使用permute函数将其维度重新排列为(3, 2)&#xff0c;如下所示&#xff1a; >>> …

开疆智能ModbusTCP转EtherCAT网关连接甘纳数据采集系统配置案例

本案例是通过开疆智能研发的ModbusTCP转EtherCAT网关连接ModbusTCP主站与甘纳数据采集系统的配置案例&#xff0c;具体配置如下。 配置过程 首先设置ModbusTCP主站&#xff0c;这里以信捷PLC为例 IP设定 要走Modbus-TCP协议&#xff0c;要把设备IP设在同一网段且地址不同&am…

Neo4j常用语法-path

在 Neo4j 中&#xff0c;Path&#xff08;路径&#xff09; 是连接两个或多个节点的关系序列&#xff0c;是图查询的核心概念之一。理解 Path 的用法对于复杂图分析至关重要 关键特性&#xff1a; 有向性&#xff1a;路径中的关系具有方向 可变长度&#xff1a;路径可以包含 0 …

从 Cluely 融资看“AI 协同开发”认证:软件考试应该怎么升级?

AI 工具大爆发&#xff0c;软件考试却还停在“纯手写”时代&#xff1f; 2025 年 6 月&#xff0c;一个标语写着 “Cheat on Everything”&#xff08;对&#xff0c;意思就是“什么都能开挂”&#xff09;的 AI 初创公司——Cluely&#xff0c;正式宣布获得由 a16z 领投的 1 …

商品中心—10.商品B端搜索系统的说明文档

大纲 1.商品B端搜索系统的运行流程 缓存和索引设计 2.商品B端搜索系统监听数据变更与写入ES索引 3.商品B端搜索系统的历史搜索词的实现 4.商品B端搜索系统的搜索词补全的实现 5.商品B端搜索系统的搜索接口实现 6.索引重建 1.商品B端搜索系统的运行流程 缓存和索引设计 …

HCIP-Datacom Core Technology V1.0_4 OSPF路由计算

ospf是如何计算生成这些路由呢&#xff0c; 区域内路由计算 LSA概述 同一个区域内路由器去进行一个数据库同步&#xff0c;形成一个LSDB&#xff0c;那么数据库里面所存在的LSA,是如何利用它去进行计算和生成路由的呢&#xff0c;以及这些LSA分别包含了哪些信息&#xff0c;比…

微服务拆分之术与道:从原则到实践的深度解析

引言&#xff1a;微服务的塞壬之歌 - 超越单体巨石 故事要从一家名为“巨石公司”&#xff08;Monolith Inc.&#xff09;的虚构企业说起。它的旗舰产品曾是公司的骄傲&#xff0c;但随着岁月流逝&#xff0c;这个系统逐渐演变成了一个“大泥球”&#xff08;Big Ball of Mud&a…

【新手向】GitHub Desktop 的使用说明(含 GitHub Desktop 和 Git 的功能对比)

GitHub Desktop 是 GitHub 公司推出的一款桌面应用程序&#xff0c;旨在帮助开发人员更轻松地使用 GitHub&#xff0c;以下是其简单的使用说明&#xff1a; 安装与登录 下载 GitHub Desktop |GitHub 桌面 访问GitHub Desktop 官方网站&#xff0c;根据自己的操作系统下载对应的…

Linux驱动编程 - gpio、gpiod函数

​​​​​ 目录 简介&#xff1a; 1、GPIO 子系统有两套API&#xff1a; 一、GPIO新、旧版互相兼容转换 API 1、转化函数 二、基于描述符接口(descriptor-based) &#xff08;以"gpiod_"为前缀&#xff09; 1、获取 GPIO 2.1 struct gpio_desc *gpiod_get(s…

Tensorflow推理时遇见PTX错误,安装CUDA及CuDNN, 解决问题!

问题原因&#xff1a; 使用TensorFlow一个小模型是进行推理的时候&#xff0c;报了PTX错误&#xff1a; Traceback (most recent call last): 20273 2025-06-18 10:20:38.345 INFO 1 --- [checkTask-1] c.l.a.d.a.util.AnalyzeCommonHelper : File "/home/python/commo…

C# 网络编程-关于HTTP/HTTPS的基础(一)

一、HTTP基础概念 1. 请求-响应模型 HTTP是基于客户端-服务器的无状态协议&#xff0c;流程如下&#xff1a; 客户端&#xff08;如浏览器&#xff09;发起请求。服务器接收请求并处理。服务器返回响应&#xff0c;包含状态码、Header和响应体。连接关闭&#xff0c;后续请求…

小程序右上角○关闭事件

小程序用户真实离开事件追踪&#xff1a;一场与技术细节的博弈 在数据分析的场景下&#xff0c;精准捕捉用户行为至关重要。我们遇到了这样一个需求&#xff1a;在小程序的埋点方案中&#xff0c;只记录用户真正意义上的离开&#xff0c;即通过点击小程序右上角关闭按钮触发的…

数据库高性能应用分析报告

数据库高性能应用分析报告 引言摘要 在数字经济加速发展的今天&#xff0c;数据库性能已成为企业核心竞争力的关键要素。根据Gartner 2024年最新研究&#xff0c;全球企业因数据库性能问题导致的直接经济损失高达每年420亿美元&#xff0c;同时性能优化带来的业务提升可达到2…

Java使用itext pdf生成PDF文档

Java使用itext pdf生成PDF文档 Java使用itextpdf生成PDF文档 在日常开发中&#xff0c;我们经常需要生成各种类型的文档&#xff0c;其中PDF是最常用的一种格式。本文将介绍如何使用Java和iText库生成包含中文内容的PDF文档&#xff0c;并通过一个具体的示例来展示整个过程。…

利用VBA将Word文档修改为符合EPUB3标准规范的HTML文件

Word本身具有将docx文件转换为HTML文件的功能&#xff0c;但是转换出来的HTML文档源代码令人不忍卒读&#xff0c;占用空间大&#xff0c;可维护性极差&#xff0c;如果想给HTML文档加上点自定义交互行为&#xff0c;也不是一般的麻烦。如果文档中包含注释&#xff0c;对于Word…

开发语言本身只是提供了一种解决问题的工具

前言 你是否曾经注意到&#xff0c;在中国的软件工程师日常工作中&#xff0c;他们使用的工具界面大多为英文&#xff1f;从代码编辑器到开发框架文档&#xff0c;再到错误信息提示框&#xff0c;英语似乎已经成为了计算机领域事实上的标准语言。那么为什么在全球化日益加深的…