opencv之图像轮廓
文章目录
- 前言
- 查找并绘制轮廓
- 查找图像轮廓:findContours函数
- 绘制图像轮廓:drawContours函数
- 轮廓实例
- 矩特征
- Hu矩
- Hu矩函数
- 形状匹配
前言
图像轮廓指的是图像中物体边缘或形状的外部线条,通常通过图像处理技术来检测和提取。轮廓是用于描述物体形状、进行目标识别、图像分割等操作的重要特征之一。
边缘检测虽然能够检测出边缘,但边缘是不连续的,检测到的边缘并不是一个整体。图像轮廓是指将边缘连接起来形成的一个整体,用于后续的计算。
OpenCV提供了查找图像轮廓的函数cv2.findContours(),该函数能够查找图像内的轮廓信息,而函数cv2.drawContours()能够将轮廓绘制出来。
图像轮廓是图像中非常重要的一个特征信息,通过对图像轮廓的操作,我们能够获取目标图像的大小、位置、方向等信息。
查找并绘制轮廓
一个轮廓对应着一系列的点,这些点以某种方式表示图像中的一条曲线。在OpenCV中,函数cv2.findContours()用于查找图像的轮廓,并能够根据参数返回特定表示方式的轮廓(曲线)。函数cv2.drawContours()能够将查找到的轮廓绘制到图像上,该函数可以根据参数在图像上绘制不同样式(实心/空心点,以及线条的不同粗细、颜色等)的轮廓,可以绘制全部轮廓也可以仅绘制指定的轮廓。
查找图像轮廓:findContours函数
函数cv2.findContours()的语法格式为:
image, contours, hierarchy = cv2.findContours( image, mode, method)
式中的返回值为:
- image:与函数参数中的原始图像image一致。
- contours:返回的轮廓。
- hierarchy:图像的拓扑信息(轮廓层次)。
- image:原始图像。8位单通道图像,所有非零值被处理为1,所有零值保持不变。也就是说灰度图像会被自动处理为二值图像。在实际操作时,可以根据需要,预先使用阈值处理等函数将待查找轮廓的图像处理为二值图像。
- mode:轮廓检索模式。
- method:轮廓的近似方法。
函数cv2.findContours()的返回值及参数的含义比较丰富,下面对上述返回值和参数逐一做出说明。
1.返回值image
该返回值与参数image是一致的,就是原始输入图像。在OpenCV 4.X中,该返回值已经被取消。在OpenCV 4.X中,函数cv2.findContours()仅有两个返回值,其语法格式为:
contours, hierarchy = cv2.findContours( image, mode, method)
2.返回值contours
该返回值返回的是一组轮廓信息,每个轮廓都是由若干个点所构成的。例如,contours[i]是第i个轮廓(下标从0开始), contours[i][j]是第i个轮廓内的第j个点。
图1所示为提取的轮廓示例,函数cv2.findContours()提取出左图的3个轮廓,每一个轮廓都是由若干个像素点构成的。
下面针对图1来简单介绍一下contours的基本属性。
(1)type属性
返回值contours的type属性是list类型,list的每个元素都是图像的一个轮廓,用Numpy中的ndarray结构表示。
例如,使用如下语句获取轮廓contours的类型:
print (type(contours))
结果为<class ‘list’>。
使用如下语句获取轮廓contours中每个元素的类型:
print (type(contours[0]))
结果为<class ‘numpy.ndarray’>。
(2)轮廓的个数使用如下语句可以获取轮廓的个数:
使用如下语句可以获取轮廓的个数:
print (len(contours))
结果为“3”,表示在图1中,存在3个轮廓
(3)每个轮廓的点数每一个轮廓都是由若干个像素点构成的,点的个数不固定,具体个数取决于轮廓的形状。
例如,使用如下语句,可以获取每个轮廓内点的个数
print (len(contours[0])) #打印第0个轮廓的长度(点的个数):4
print (len(contours[1])) #打印第1个轮廓的长度(点的个数):60
print (len(contours[2])) #打印第2个轮廓的长度(点的个数):184
使用如下语句,可以获取每个轮廓内点的shape属性:
print(contours[0].shape)
print(contours[1].shape)
print(contours[2].shape)
(4)轮廓内的点
使用如下语句,可以获取轮廓内第0个轮廓中具体点的位置属性:
print (contours[0]) #打印第0个轮廓中的像素点
3.返回值hierarchy
图像内的轮廓可能位于不同的位置。比如,一个轮廓在另一个轮廓的内部。在这种情况下,我们将外部的轮廓称为父轮廓,内部的轮廓称为子轮廓。按照上述关系分类,一幅图像中所有轮廓之间就建立了父子关系。
根据轮廓之间的关系,就能够确定一个轮廓与其他轮廓是如何连接的。比如,确定一个轮廓是某个轮廓的子轮廓,或者是某个轮廓的父轮廓。上述关系被称为层次(组织结构),返回值hierarchy就包含上述层次关系。
每个轮廓contours[i]对应4个元素来说明当前轮廓的层次关系。其形式为:
[Next, Previous, First_Child, Parent]
式中各元素的含义为:
- Next:后一个轮廓的索引编号。
- Previous:前一个轮廓的索引编号。
- First_Child:第1个子轮廓的索引编号。
- Parent:父轮廓的索引编号。
如果上述各个参数所对应的关系为空时,也就是没有对应的关系时,则将该参数所对应的值设为“-1”。
使用print语句可以查看hierarchy的值:
print(hierarchy)
需要注意,轮廓的层次结构是由参数mode决定的。也就是说,使用不同的mode,得到轮廓的编号是不一样的,得到的hierarchy也不一样。
4.参数image
该参数表示输入的图像,必须是8位单通道二值图像。一般情况下,都是将图像处理为二值图像后,再将其作为image参数使用的。
5.参数mode
参数mode决定了轮廓的提取方式,具体有如下4种:
- cv2.RETR_EXTERNAL:只检测外轮廓。
- cv2.RETR_LIST:对检测到的轮廓不建立等级关系。
- cv2.RETR_CCOMP:检索所有轮廓并将它们组织成两级层次结构。上面的一层为外边界,下面的一层为内孔的边界。如果内孔内还有一个连通物体,那么这个物体的边界仍然位于顶层。
- cv2.RETR_TREE:建立一个等级树结构的轮廓。
下面分别对这四种情况进行简单的说明。
- (1)cv2.RETR_EXTERNAL(只检测外轮廓)例如,在图2中仅检测到两个外轮廓,轮廓的序号如图中的数字标注所示。
import cv2# 读取图像
image = cv2.imread('C:\\Users\\Administrator\\Desktop\\res2.png', cv2.IMREAD_GRAYSCALE)# 检测轮廓
# contours, hierarchy = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 打印轮廓的hierarchy信息
print("Hierarchy:\n", hierarchy)# 显示图像和轮廓
cv2.imshow("Contours", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
其中:
- 输出值“[ 1-1-1-1]”,表示的是第0个轮廓的层次。
- 它(即第0个轮廓)的后一个轮廓就是第1个轮廓,因此第1个元素的值是“1”。
- 它的前一个轮廓不存在,因此第2个元素的值是“-1”。
- 它不存在子轮廓,因此第3个元素的值是“-1”。
- 它不存在父轮廓,因此第4个元素的值是“-1”。
- 输出值“[-1 0-1-1]”,表示的是第1个轮廓的层次。
- 它(即第1个轮廓)的后一个轮廓是不存在的,因此第1个元素的值是“-1”。
- 它的前一个轮廓是第0个轮廓,因此第2个元素的值是“0”。
- 它不存在子轮廓,因此第3个元素的值是“-1”。
- 它不存在父轮廓,因此第4个元素的值是“-1”。
此时,轮廓之间的关系为:
(2)cv2.RETR_LIST(对检测到的轮廓不建立等级关系)
例如,在图3中检测到三个轮廓,各个轮廓的序号如图中数字的标注所示。
import cv2# 读取图像
image = cv2.imread('C:\\Users\\Administrator\\Desktop\\res2.png', cv2.IMREAD_GRAYSCALE)# 检测轮廓
contours, hierarchy = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# cv2.RETR_LIST
contours, hierarchy = cv2.findContours(image, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# 打印轮廓的hierarchy信息
print("Hierarchy:\n", hierarchy)# 显示图像和轮廓
cv2.imshow("Contours", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
其中:
- 输出值“[ 1-1-1-1]”,表示的是第0个轮廓的层次。
- 它(即第0个轮廓)的后一个轮廓是第1个轮廓,因此第1个元素的值是“1”。
- 它的前一个轮廓不存在,因此第2个元素的值是“-1”。
- 它不存在子轮廓,因此第3个元素的值是“-1”。
- 它不存在父轮廓,因此第4个元素的值是“-1”。
- 输出值“[2 0-1-1]”,表示的是第1个轮廓的层次。
- 它(即第1个轮廓)的后一个轮廓是第2个轮廓,因此第1个元素的值是“2”。
- 它的前一个轮廓是第0个轮廓,因此第2个元素的值是“0”。
- 它不存在子轮廓,因此第3个元素的值是“-1”。
- 它不存在父轮廓,因此第4个元素的值是“-1”。
- 输出值“[-1 1-1-1]”,表示的是第2个轮廓的层次。
- 它(即第2个轮廓)的后一个轮廓是不存在的,因此第1个元素的值是“-1”。
- 它的前一个轮廓是第1个轮廓,因此第2个元素的值是“1”。
- 它不存在子轮廓,因此第3个元素的值是“-1”。
- 它不存在父轮廓,因此第4个元素的值是“-1”。
从上述分析可以看出,当参数mode为cv2.RETR_LIST时,各个轮廓之间没有建立父子关系。此时,轮廓之间的关系为:
(3)cv2.RETR_CCOMP(建立两个等级的轮廓)
当参数mode为cv2.RETR_CCOMP时,建立两个等级的轮廓。上面的一层为外边界,下面的一层为内孔边界。
例如,在图4中检测到三个轮廓,各轮廓的序号如图中数字的标注所示。
import cv2# 读取图像
image = cv2.imread('C:\\Users\\Administrator\\Desktop\\res2.png', cv2.IMREAD_GRAYSCALE)# 检测轮廓
contours, hierarchy = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# cv2.RETR_LIST
contours, hierarchy = cv2.findContours(image, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
# 打印轮廓的hierarchy信息
print("Hierarchy:\n", hierarchy)# 显示图像和轮廓
cv2.imshow("Contours", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
其中:
- 输出值“[ 1-1-1-1]”,表示的是第0个轮廓的层次。
- 它(即第0个轮廓)的后一个轮廓是第1个轮廓,因此第1个元素的值是“1”。
- 它的前一个轮廓不存在,因此第2个元素的值是“-1”。
- 它不存在子轮廓,因此第3个元素的值是“-1”。
- 它不存在父轮廓,因此第4个元素的值是“-1”。
- 输出值“[-1 0 2-1]”,表示的是第1个轮廓的层次。
- 它(即第1个轮廓)的后一个轮廓不存在,因此第1个元素的值是“-1”。
- 它的前一个轮廓是第0个轮廓,因此第2个元素的值是“0”。
- 它的第1个子轮廓是第2个轮廓,因此第3个元素的值是“2”。
- 它不存在父轮廓,因此第4个元素的值是“-1”。
- 输出值“[-1-1-1 1]”,表示的是第2个轮廓的层次。
- 它(即第2个轮廓)的后一个轮廓不存在,因此第1个元素的值是“-1”。
- 它的前一个轮廓也不存在,因此第2个元素的值是“-1”。
- 它不存在子轮廓,因此第3个元素的值是“-1”。
- 它的父轮廓是第1个轮廓,因此第4个元素的值是“1”。
此时,轮廓关系为:
(4)cv2.RETR_TREE(建立一个等级树结构的轮廓)
例如,在图5中检测到三个轮廓,各个轮廓的序号如图中的数字标注所示。
import cv2# 读取图像
image = cv2.imread('C:\\Users\\Administrator\\Desktop\\res2.png', cv2.IMREAD_GRAYSCALE)# 检测轮廓
# contours, hierarchy = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# contours, hierarchy = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# cv2.RETR_LIST
contours, hierarchy = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 打印轮廓的hierarchy信息
print("Hierarchy:\n", hierarchy)# 显示图像和轮廓
cv2.imshow("Contours", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
其中:
- 输出值“[ 1-1-1-1]”,表示的是第0个轮廓的层次。
- 它(即第0个轮廓)的后一个轮廓是第1个轮廓,因此第1个元素的值为“1”。
- 它的前一个轮廓不存在,因此第2个元素的值是“-1”。
- 它不存在子轮廓,因此第3个元素的值是“-1”。
- 它不存在父轮廓,因此第4个元素的值是“-1”。
- 输出值“[-1 0 2-1]”,表示的是第1个轮廓的层次。
- 它(即第1个轮廓)的后一个轮廓不存在,因此第1个元素的值是“-1”。
- 它的前一个轮廓是第0个轮廓,因此第2个元素的值是“0”。
- 它的第1个子轮廓是第2个轮廓,因此第3个元素的值是“2”。
- 它的父轮廓不存在,因此第4个元素的值是“-1”。
- 输出值“[-1-1-1 1]”,表示的是第2个轮廓的层次。
- 它(即第2个轮廓)的后一个轮廓不存在,因此第1个元素的值是“-1”。
- 它的前一个轮廓是不存在的,因此第2个元素的值是“-1”。
- 它的子轮廓是不存在的,因此第3个元素的值是“-1”。
- 它的父轮廓是第1个轮廓,因此第1个元素的值是“1”。
此时,轮廓之间的关系为:
需要注意,本例中仅有两层轮廓,所以使用参数cv2.RETR_CCOMP和cv2.RETR_TREE得到的层次结构是一致的。当有多层轮廓时,使用参数cv2.RETR_CCOMP也会得到仅有两层的层次结构;而使用参数cv2.RETR_TREE会得到含有多个层次的结构。限于篇幅,这里不再列举更多层轮廓的层次关系。
6.参数method
参数method决定了如何表达轮廓,可以为如下值:
- ● cv2.CHAIN_APPROX_NONE:存储所有的轮廓点,相邻两个点的像素位置差不超过1,即max(abs(x1-x2), abs(y2-y1))=1。
- ● cv2.CHAIN_APPROX_SIMPLE:压缩水平方向、垂直方向、对角线方向的元素,只保留该方向的终点坐标。例如,在极端的情况下,一个矩形只需要用4个点来保存轮廓信息。
- ● cv2.CHAIN_APPROX_TC89_L1:使用teh-Chinl chain近似算法的一种风格。
- ● cv2.CHAIN_APPROX_TC89_KCOS:使用teh-Chinl chain近似算法的一种风格。
例如,在图6中,左图是使用参数值cv2.CHAIN_APPROX_NONE存储的轮廓,保存了轮廓中的每一个点;右图是使用参数值cv2.CHAIN_APPROX_SIMPLE存储的轮廓,仅仅保存了边界上的四个点。
在使用函数cv2.findContours()查找图像轮廓时,需要注意以下问题:
- 待处理的源图像必须是灰度二值图。因此,在通常情况下,都要预先对图像进行阈值分割或者边缘检测处理,得到满意的二值图像后再将其作为参数使用。
- 在OpenCV中,都是从黑色背景中查找白色对象。因此,对象必须是白色的,背景必须是黑色的。
- 在OpenCV 4.x中,函数cv2.findContours()仅有两个返回值。
绘制图像轮廓:drawContours函数
在OpenCV中,可以使用函数cv2.drawContours()绘制图像轮廓。该函数的语法格式是:
image = cv2.drawContours(
image,
contours,
contourIdx,
color[,
thickness[,
lineType[,
hierarchy[,
maxLevel[,
offset]]]]] )
其中,函数的返回值为image,表示目标图像,即绘制了边缘的原始图像。该函数有如下参数:
- image:待绘制轮廓的图像。需要注意,函数cv2.drawContours()会在图像image上直接绘制轮廓。也就是说,在函数执行完以后,image不再是原始图像,而是包含了轮廓的图像。因此,如果图像image还有其他用途的话,则需要预先复制一份,将该副本图像传递给函数cv2.drawContours()使用。
- contours:需要绘制的轮廓。该参数的类型与函数cv2.findContours()的输出contours相同,都是list类型。
- contourIdx:需要绘制的边缘索引,告诉函数cv2.drawContours()要绘制某一条轮廓还是全部轮廓。如果该参数是一个整数或者为零,则表示绘制对应索引号的轮廓;如果该值为负数(通常为“-1”),则表示绘制全部轮廓。
- color:绘制的颜色,用BGR格式表示。
- thickness:可选参数,表示绘制轮廓时所用画笔的粗细。如将该值设置为“-1”,则表示要绘制实心轮廓。
- lineType:可选参数,表示绘制轮廓时所用的线型。
- hierarchy:对应函数cv2.findContours()所输出的层次信息。
- maxLevel:控制所绘制的轮廓层次的深度。如果值为0,表示仅仅绘制第0层的轮廓;如果值为其他的非零正数,表示绘制最高层及以下的相同数量层级的轮廓。
- offset:偏移参数。该参数使轮廓偏移到不同的位置展示出来。函数cv2.drawContours()的参数image和返回值image,在函数运算后的值是相同的。因此,也可以将函数cv2.drawContours()写为没有返回值的形式:
cv2.drawContours(
image,
contours,
contourIdx,
color[,
thickness[,
lineType[,
hierarchy[,
maxLevel[,
offset]]]]] )
轮廓实例
【例1】绘制一幅图像内的所有轮廓。如果要绘制图像内的所有轮廓,需要将函数cv2.drawContours()的参数contourIdx的值设置为“-1”。根据题目的要求及分析,编写代码如下:
import cv2# 读取图像
o = cv2.imread(r'C:\\Users\\Administrator\\Desktop\\res3.png')
cv2.imshow("original", o)# 转换为灰度图像
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)# 应用二值化阈值处理
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)# 检测轮廓
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# 绘制轮廓
o = cv2.drawContours(o, contours, -1, (0, 0, 255), 5)# 显示结果图像
cv2.imshow("result", o)
cv2.waitKey()
cv2.destroyAllWindows()
在本程序中,轮廓的颜色被设置为红色(由于黑白印刷的原因,在纸质书中显示为灰色),即(0, 0, 255),参数thickness(轮廓线条的粗细)被设置为“5”。
运行上述程序,结果如图7所示,图像内的所有轮廓都被绘制出来了。
【例2】逐个显示一幅图像内的边缘信息。如果要绘制图像内的某个具体轮廓,需要将函数cv2.drawContours()的参数contourIdx设置为具体的索引值。本例通过循环语句逐一绘制轮廓。根据题目要求及分析,编写代码如下:
import cv2
import numpy as npo = cv2.imread(r'C:\\Users\\Administrator\\Desktop\\res3.png')
cv2.imshow("original", o)
resized_img = cv2.resize(o, (300, 300)) # 调整大小到 300x300
cv2.imshow("original", resized_img)
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
n = len(contours)
contoursImg = []
for i in range(n):temp = np.zeros(o.shape, np.uint8)contoursImg.append(temp)contoursImg[i] = cv2.drawContours(contoursImg[i], contours, i, (255, 255, 255), 5)# 缩放图像resized_img = cv2.resize(contoursImg[i], (300, 300)) # 调整大小到 300x300cv2.imshow(f"contours[{i}]", resized_img)# cv2.imshow("contours[" + str(i) + "]", contoursImg[i])
cv2.waitKey()
cv2.destroyAllWindows()
运行上述程序,结果如图8所示,图像内的轮廓被逐一绘制出来。
【例3】使用轮廓绘制功能,提取前景对象。将函数cv2.drawContours()的参数thickness的值设置为“-1”,可以绘制前景对象的实心轮廓。将该实心轮廓与原始图像进行“按位与”操作,即可将前景对象从原始图像中提取出来。根据题目的要求及分析,编写代码如下:
# test.jpgimport cv2
import numpy as npo = cv2.imread(r'C:\\Users\\Administrator\\Desktop\\test.jpg')
# cv2.imshow("original", o)
resized_img = cv2.resize(o, (350, 500)) # 调整大小到 300x300
cv2.imshow("original", resized_img)
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# image, contours, hierarchy = cv2.findContours(binary,
# cv2.RETR_LIST,
# cv2.CHAIN_APPROX_SIMPLE)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
mask = np.zeros(o.shape, np.uint8)
mask = cv2.drawContours(mask, contours, -1, (255, 255, 255), -1)
mask = cv2.resize(mask, (350, 500)) # 调整大小到 300x300
cv2.imshow("mask", mask)
loc = cv2.bitwise_and(resized_img, mask)
loc = cv2.resize(loc, (350, 500)) # 调整大小到 300x300
cv2.imshow("location", loc)
cv2.waitKey()
cv2.destroyAllWindows()
本例中将函数cv2.drawContours()的参数thickness设置为“-1”,得到了前景对象的实心轮廓mask。接下来,通过语句“cv2.bitwise_and(o, mask)”,将原始图像o与实心轮廓mask进行“按位与”运算,就得到了原始图像的前景对象。运行上述程序,结果如图9所示。其中:
矩特征
比较两个轮廓最简单的方法是比较二者的轮廓矩。轮廓矩代表了一个轮廓、一幅图像、一组点集的全局特征。矩信息包含了对应对象不同类型的几何特征,例如大小、位置、角度、形状等。矩特征被广泛地应用在模式识别、图像识别等方面。
矩的计算:moments函数
OpenCV提供了函数cv2.moments()来获取图像的moments特征。通常情况下,我们将使用函数cv2.moments()获取的轮廓特征称为“轮廓矩”。轮廓矩描述了一个轮廓的重要特征,使用轮廓矩可以方便地比较两个轮廓。函数cv2.moments()的语法格式为:
retval = cv2.moments( array[, binaryImage] )
式中有两个参数:
- array:可以是点集,也可以是灰度图像或者二值图像。当array是点集时,函数会把这些点集当成轮廓中的顶点,把整个点集作为一条轮廓,而不是把它们当成独立的点来看待。
- binaryImage:该参数为True时,array内所有的非零值都被处理为1。该参数仅在参数array为图像时有效。
该函数的返回值retval是矩特征,主要包括:
(1)空间矩
- 零阶矩:m00
- 一阶矩:m10, m01
- 二阶矩:m20, m11, m02
- 三阶矩:m30, m21, m12, m03
(2)中心矩
- ● 二阶中心矩:mu20, mu11, mu02
- ● 三阶中心矩:mu30, mu21, mu12, mu03
(3)归一化中心矩
- ● 二阶Hu矩:nu20, nu11, nu02
- ● 三阶Hu矩:nu30, nu21, nu12, nu03
上述矩都是根据公式计算得到的,大多数矩比较抽象。但是很明显,如果两个轮廓的矩一致,那么这两个轮廓就是一致的。虽然大多数矩都是通过数学公式计算得到的抽象特征,但是零阶矩“m00”的含义比较直观,它表示一个轮廓的面积。
矩特征函数cv2.moments()所返回的特征值,能够用来比较两个轮廓是否相似。例如,有两个轮廓,不管它们出现在图像的哪个位置,我们都可以通过函数cv2.moments()的m00矩判断其面积是否一致。
在位置发生变化时,虽然轮廓的面积、周长等特征不变,但是更高阶的特征会随着位置的变化而发生变化。在很多情况下,我们希望比较不同位置的两个对象的一致性。解决这一问题的方法是引入中心矩。中心矩通过减去均值而获取平移不变性,因而能够比较不同位置的两个对象是否一致。很明显,中心矩具有的平移不变性,使它能够忽略两个对象的位置关系,帮助我们比较不同位置上两个对象的一致性。
除了考虑平移不变性外,我们还会考虑经过缩放后大小不一致的对象的一致性。也就是说,我们希望图像在缩放前后能够拥有一个稳定的特征值。也就是说,让图像在缩放前后具有同样的特征值。显然,中心矩不具有这个属性。例如,两个形状一致、大小不一的对象,其中心矩是有差异的。
归一化中心矩通过除以物体总尺寸而获得缩放不变性。它通过上述计算提取对象的归一化中心矩属性值,该属性值不仅具有平移不变性,还具有缩放不变性。
在OpenCV中,函数cv2.moments()会同时计算上述空间矩、中心矩和归一化中心距。
【例4】使用函数cv2.moments()提取一幅图像的特征。根据题目的要求及分析,编写代码如下:
import cv2
import numpy as np
o = cv2.imread('moments.bmp')
cv2.imshow("original", o)
gray = cv2.cvtColor(o, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255, cv2.THRESH_BINARY)
image, contours, hierarchy = cv2.findContours(binary,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
n=len(contours)
contoursImg=[]
for i in range(n):temp=np.zeros(image.shape, np.uint8)contoursImg.append(temp)contoursImg[i]=cv2.drawContours(contoursImg[i], contours, i,255,3)cv2.imshow("contours[" + str(i)+"]", contoursImg[i])print("观察各个轮廓的矩(moments):")for i in range(n):print("轮廓"+str(i)+"的矩:\n", cv2.moments(contours[i]))print("观察各个轮廓的面积:")for i in range(n):print("轮廓"+str(i)+"的面积:%d" %cv2.moments(contours[i])['m00'])cv2.waitKey()cv2.destroyAllWindows()
本例中,首先使用函数cv2.moments()提取各个轮廓的特征;接下来,通过语句cv2.moments(contours[i])[‘m00’])提取各个轮廓矩的面积信息。
运行上述程序,会显示如图10所示的图像。其中:
- ● (a)图是原始图像。
- ● (b)图是原始图像的第0个轮廓。
- ● ©图是原始图像的第1个轮廓。
- ● (d)图是原始图像的第2个轮廓。
同时,程序会显示如下输出结果:
观察各个轮廓的矩(moments):
轮廓 0 的矩:{'m00': 2953.5, 'm10': 141914.0, 'm01': 674953.8333333333, 'm20': 7297301.083333333, 'm11': 32432175.458333332, 'm02': 155353008.91666666, 'm30': 396608955.90000004, 'm21': 1667753129.2333333, 'm12': 7465179622.3, 'm03': 36008721801.950005, 'mu20': 478413.86613340117, 'mu11': 1026.5490167029202, 'mu02': 1107985.3407868445, 'mu30': 2747.422912657261, 'mu21': 25787.93059720099, 'mu12': 86460.36071126908, 'mu03': -9125.510330200195, 'nu20': 0.05484408663078589, 'nu11': 0.00011768083491774101, 'nu02': 0.12701647740036529, 'nu30': 5.795395481220832e-06, 'nu21': 5.439688799439629e-05, 'nu12': 0.0001823789055053718, 'nu03': -1.924929033962443e-05}
轮廓 0 的面积:2953.5
轮廓 1 的矩:{'m00': 6956.5, 'm10': 1133880.6666666665, 'm01': 834706.1666666666, 'm20': 192610842.25, 'm11': 136054500.54166666, 'm02': 102240819.08333333, 'm30': 33935221179.300003, 'm21': 23111377235.933334, 'm12': 16665018223.266666, 'm03': 12768156053.050001, 'mu20': 7792993.2971582115, 'mu11': 855.0689752697945, 'mu02': 2084938.299697727, 'mu30': 1012.8543243408203, 'mu21': -158289.87633812428, 'mu12': -16717.500845849514, 'mu03': 1969.0043239593506, 'nu20': 0.16103590702740464, 'nu11': 1.7669309179795806e-05, 'nu02': 0.0430835646054556, 'nu30': 2.5094006894295327e-07, 'nu21': -3.921716235660208e-05, 'nu12': -4.141850129870927e-06, 'nu03': 4.878313385539436e-07}
轮廓 1 的面积:6956.5
轮廓 2 的矩:{'m00': 779.0, 'm10': 233716.5, 'm01': 18296.0, 'm20': 70194404.66666666, 'm11': 5489360.916666666, 'm02': 463822.5, 'm30': 21104513696.25, 'm21': 1648724552.9166667, 'm12': 139165821.08333334, 'm03': 12496190.0, 'mu20': 74504.3171801418, 'mu11': 173.3890671795234, 'mu02': 34113.10847240052, 'mu30': -237.9855079650879, 'mu21': -1859.8601903982926, 'mu12': 1102.246367705986, 'mu03': 216.54015137441456, 'nu20': 0.12277403336317387, 'nu11': 0.00028572404827545173, 'nu02': 0.05621424470726354, 'nu30': -1.4050988149079054e-05, 'nu21': -0.00010980867582098307, 'nu12': 6.507812506076738e-05, 'nu03': 1.2784825121402051e-05}
轮廓 2 的面积:779.0
补充:
计算轮廓的面积:contourArea函数
计算轮廓的长度:arcLength函数
Hu矩
Hu矩是归一化中心矩的线性组合。Hu矩在图像旋转、缩放、平移等操作后,仍能保持矩的不变性,所以经常会使用Hu距来识别图像的特征。
在OpenCV中,使用函数cv2.HuMoments()可以得到Hu距。该函数使用cv2.moments()函数的返回值作为参数,返回7个Hu矩值。
Hu矩函数
函数cv2.HuMoments()的语法格式为:
hu = cv2.HuMoments( m )
式中返回值hu,表示返回的Hu矩值;参数m,是由函数cv2.moments()计算得到矩特征值。
【例8】计算图像的Hu矩,对其中第0个矩的关系进行演示。
Hu矩是归一化中心矩的线性组合,每一个矩都是通过归一化中心矩的组合运算得到的。函数cv2.moments()返回的归一化中心矩中包含:
- 二阶Hu矩: n u 20 , n u 11 , n u 02 nu_{20}, nu_{11}, nu_{02} nu20,nu11,nu02
- 三阶Hu矩: n u 30 , n u 21 , n u 12 , n u 03 nu_{30}, nu_{21}, nu_{12}, nu_{03} nu30,nu21,nu12,nu03
为了表述上的方便,将上述字母nu
表示为字母v
,则归一化中心矩为:
- 二阶Hu矩: v 20 , v 11 , v 02 v20, v11, v02 v20,v11,v02
- 三阶Hu矩: v 30 , v 21 , v 12 , v 03 v30, v21, v12, v03 v30,v21,v12,v03
上述7个Hu矩的计算公式为:
本例对Hu矩中的第0个矩 h 0 = v 20 + v 02 ℎ_0=v_{20}+v_{02} h0=v20+v02的关系进行验证,即Hu矩中第0个矩对应的函数cv2.moments()的返回值为:
h 0 = n u 20 + n u 02 ℎ0=nu20+nu02 h0=nu20+nu02
根据题目的要求及分析,编写代码如下:
import cv2
# o1 = cv2.imread('cs1.bmp')
o1 = cv2.imread(r'C:\\Users\\Administrator\\Desktop\\moment.png')
gray = cv2.cvtColor(o1, cv2.COLOR_BGR2GRAY)
HuM1=cv2.HuMoments(cv2.moments(gray)).flatten()
print("cv2.moments(gray)=\n", cv2.moments(gray))
print("\nHuM1=\n", HuM1)
print("\ncv2.moments(gray)['nu20']+cv2.moments(gray)['nu02']=%f+%f=%f\n"%(cv2.moments(gray)['nu20'], cv2.moments(gray)['nu02'],cv2.moments(gray)['nu20']+cv2.moments(gray)['nu02']))
print("HuM1[0]=", HuM1[0])
print("\nHu[0]-(nu02+nu20)=",HuM1[0]-(cv2.moments(gray)['nu20']+cv2.moments(gray)['nu02']))
程序运行结果显示Hu[0]-(nu02+nu20)=0.0
。从该结果可知,关系 h 0 = n u 20 + n u 02 ℎ_0=nu_{20}+nu_{02} h0=nu20+nu02成立。
cv2.moments(gray)={'m00': 2819590.0, 'm10': 397570476.0, 'm01': 403860142.0, 'm20': 71305223188.0, 'm11': 45735796942.0, 'm02': 68512957510.0, 'm30': 14697923289666.0, 'm21': 6934198814642.0, 'm12': 6404215738518.0, 'm03': 13009490405824.0, 'mu20': 15246617721.649727, 'mu11': -11209669913.949831, 'mu02': 10666598891.810768, 'mu30': 344041464963.81635, 'mu21': -117915129251.81013, 'mu12': -45108937401.78011, 'mu03': 140498447760.5766, 'nu20': 0.0019177923774410773, 'nu11': -0.0014100058063420402, 'nu02': 0.0013416957400911801, 'nu30': 2.57718671649104e-05, 'nu21': -8.832926717512085e-06, 'nu12': -3.3790739229389187e-06, 'nu03': 1.0524624794695901e-05}HuM1=[ 3.25948812e-03 8.28435283e-06 2.66019518e-09 5.04299032e-103.02264955e-19 7.35730936e-14 -4.99811415e-19]cv2.moments(gray)['nu20']+cv2.moments(gray)['nu02']=0.001918+0.001342=0.003259HuM1[0]= 0.0032594881175322574Hu[0]-(nu02+nu20)= 0.0Process finished with exit code 0
【例9】计算三幅不同图像的Hu矩,并进行比较。根据题目的要求,编写代码如下:
import cv2
#----------------计算图像o1的Hu矩-------------------
o1 = cv2.imread('cs1.bmp')
gray1 = cv2.cvtColor(o1, cv2.COLOR_BGR2GRAY)
HuM1=cv2.HuMoments(cv2.moments(gray1)).flatten()
#----------------计算图像o2的Hu矩-------------------
o2 = cv2.imread('cs3.bmp')
gray2 = cv2.cvtColor(o2, cv2.COLOR_BGR2GRAY)
HuM2=cv2.HuMoments(cv2.moments(gray2)).flatten()
#----------------计算图像o3的Hu矩-------------------
o3 = cv2.imread('lena.bmp')
gray3 = cv2.cvtColor(o3, cv2.COLOR_BGR2GRAY)
HuM3=cv2.HuMoments(cv2.moments(gray3)).flatten()
#---------打印图像o1、图像o2、图像o3的特征值------------
print("o1.shape=", o1.shape)
print("o2.shape=", o2.shape)
print("o3.shape=", o3.shape)
print("cv2.moments(gray1)=\n", cv2.moments(gray1))
print("cv2.moments(gray2)=\n", cv2.moments(gray2))
print("cv2.moments(gray3)=\n", cv2.moments(gray3))
print("\nHuM1=\n", HuM1)
print("\nHuM2=\n", HuM2)
print("\nHuM3=\n", HuM3)
#---------计算图像o1与图像o2、图像o3的Hu矩之差----------------
print("\nHuM1-HuM2=", HuM1-HuM2)
print("\nHuM1-HuM3=", HuM1-HuM3)
#---------显示图像----------------
cv2.imshow("original1", o1)
cv2.imshow("original2", o2)
cv2.imshow("original3", o3)
cv2.waitKey()
cv2.destroyAllWindows()
运行上述程序,会显示各个图像的shape属性、moments属性、HuMoments属性,以及不同图像的Hu矩之差。
D:\Code\py_demo\.venv\Scripts\python.exe D:\Code\py_demo\operator_cv\sb_hu.py
o1.shape= (400, 500, 3)
o2.shape= (400, 500, 3)
o3.shape= (400, 500, 3)
cv2.moments(gray1)={'m00': 23410570.0, 'm10': 6894702439.0, 'm01': 4322923538.0, 'm20': 2490728253949.0, 'm11': 1296698767586.0, 'm02': 1124670909868.0, 'm30': 975876474148585.0, 'm21': 473799154186464.0, 'm12': 342275113887012.0, 'm03': 337006213255976.0, 'mu20': 460153102538.6518, 'mu11': 23544313804.690384, 'mu02': 326413203396.332, 'mu30': -28715366381641.523, 'mu21': 785697984.9051791, 'mu12': 2350378948735.8237, 'mu03': 8779049674558.577, 'nu20': 0.0008396115002459006, 'nu11': 4.2959781270095615e-05, 'nu02': 0.0005955849865874677, 'nu30': -1.0828901211539646e-05, 'nu21': 2.962959186229814e-10, 'nu12': 8.863554484129715e-07, 'nu03': 3.3106825242450395e-06}
cv2.moments(gray2)={'m00': 28034550.0, 'm10': 6816434088.0, 'm01': 5445534050.0, 'm20': 2251390234926.0, 'm11': 1331515245914.0, 'm02': 1388596230890.0, 'm30': 841561841935968.0, 'm21': 438638459033146.0, 'm12': 340557983142876.0, 'm03': 400222877167790.0, 'mu20': 594014829362.2085, 'mu11': 7466030721.150593, 'mu02': 330835678653.22784, 'mu30': 5287351850554.212, 'mu21': -2310498476300.1636, 'mu12': 28610218122.137913, 'mu03': 1971363833879.6504, 'nu20': 0.0007558056051916368, 'nu11': 9.4995403963859e-06, 'nu02': 0.0004209448114148325, 'nu30': 1.270586505153425e-06, 'nu21': -5.552284521895067e-07, 'nu12': 6.8752294310948995e-09, 'nu03': 4.7373209781990474e-07}
cv2.moments(gray3)={'m00': 26617062.0, 'm10': 6626512283.0, 'm01': 4619614220.0, 'm20': 2204631080519.0, 'm11': 1158027266838.0, 'm02': 1224946110016.0, 'm30': 825815455682453.0, 'm21': 385764754114036.0, 'm12': 307812229426686.0, 'm03': 378612525048194.0, 'mu20': 554912376149.9802, 'mu11': 7940515281.743969, 'mu02': 423173339541.70013, 'mu30': 657431568603.1985, 'mu21': -821148323850.9675, 'mu12': 96649147636.55907, 'mu03': 19122299481335.758, 'nu20': 0.0007832568081619774, 'nu11': 1.1208008547027264e-05, 'nu02': 0.0005973076353573567, 'nu30': 1.7986649164798885e-07, 'nu21': -2.2465770612065727e-07, 'nu12': 2.6442209252425936e-08, 'nu03': 5.231663771877604e-06}HuM1=[1.43519649e-03 6.69311106e-08 1.92880003e-10 1.09816797e-105.71662342e-21 1.57910213e-14 1.49252448e-20]HuM2=[ 1.17675042e-03 1.12492716e-07 6.13950909e-12 1.63855014e-123.42641324e-24 5.40282209e-16 -3.90755201e-24]HuM3=[ 1.38056444e-03 3.50795727e-08 3.48866553e-11 2.51126730e-117.35977227e-22 -4.60754057e-15 1.04139331e-22]HuM1-HuM2= [ 2.58446070e-04 -4.55616057e-08 1.86740494e-10 1.08178247e-105.71319700e-21 1.52507391e-14 1.49291523e-20]HuM1-HuM3= [5.46320433e-05 3.18515379e-08 1.57993347e-10 8.47041240e-114.98064619e-21 2.03985618e-14 1.48211054e-20]
同时,还会显示三幅原始图像,如图11所示:
从上述输出结果可以看到,由于Hu矩的值本身就非常小,因此在这里并没有发现两个对象的Hu矩差值的特殊意义。
形状匹配
我们可以通过Hu矩来判断两个对象的一致性。例如,前面计算了两个对象Hu矩的差,但是结果比较抽象。为了更直观方便地比较Hu矩值,OpenCV提供了函数cv2.matchShapes(),对两个对象的Hu矩进行比较。
函数cv2.matchShapes()允许我们提供两个对象,对二者的Hu矩进行比较。这两个对象可以是轮廓,也可以是灰度图。不管是什么,cv2.matchShapes()都会提前计算好对象的Hu矩值。
函数cv2.matchShapes()的语法格式为:
retval = cv2.matchShapes( contour1, contour2, method, parameter )
式中retval是返回值。
该函数有如下4个参数:
- contour1:第1个轮廓或者灰度图像。
- contour2:第2个轮廓或者灰度图像。
- method:比较两个对象的Hu矩的方法,具体如表1所示。
在表1中,A表示对象1, B表示对象2:
式中,和分别是对象A和对象B的Hu矩。
- parameter:应用于method的特定参数,该参数为扩展参数,目前(截至OpenCV 4.1.0版本)暂不支持该参数,因此将该值设置为0。
【例10】使用函数cv2.matchShapes()计算三幅不同图像的匹配度。根据题目要求,编写代码如下:
import cv2
o1 = cv2.imread('aa1.jpeg')
o2 = cv2.imread('aa2.png')
o3 = cv2.imread('aa3.jpeg')
gray1 = cv2.cvtColor(o1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(o2, cv2.COLOR_BGR2GRAY)
gray3 = cv2.cvtColor(o3, cv2.COLOR_BGR2GRAY)
cv2.imshow('o1',gray1)
cv2.imshow('o2',gray2)
cv2.imshow('o3',gray3)
ret, binary1 = cv2.threshold(gray1,127,255, cv2.THRESH_BINARY)
ret, binary2 = cv2.threshold(gray2,127,255, cv2.THRESH_BINARY)
ret, binary3 = cv2.threshold(gray3,127,255, cv2.THRESH_BINARY)
contours1, hierarchy = cv2.findContours(binary1,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
contours2, hierarchy = cv2.findContours(binary2,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
contours3, hierarchy = cv2.findContours(binary3,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
cnt1 = contours1[0]
cnt2 = contours2[0]
cnt3 = contours3[0]
ret0 = cv2.matchShapes(cnt1, cnt1,1,0.0)
ret1 = cv2.matchShapes(cnt1, cnt2,1,0.0)
ret2 = cv2.matchShapes(cnt1, cnt3,1,0.0)
print("相同图像的matchShape=", ret0)
print("相似图像的matchShape=", ret1)
print("不相似图像的matchShape=", ret2)###################################### result #################################
D:\Code\py_demo\.venv\Scripts\python.exe D:\Code\py_demo\operator_cv\sb_hu2.py
相同图像的matchShape= 0.0
相似图像的matchShape= 0.9588739815881875
不相似图像的matchShape= 1.7976931348623157e+308
从以上结果可以看出:
- 同一幅图像的Hu矩是不变的,二者差值为0。
- 相似的图像即使发生了平移、旋转和缩放后,函数cv2.matchShapes()的返回值仍然比较接近。例如,图像o1和图像o2, o2是对o1经过缩放、旋转和平移后得到的,但是对二者应用cv2.matchShapes()函数后,返回值的差较小。
- 不相似图像cv2.matchShapes()函数返回值的差较大。例如,图像o1和图像o3的差别较大,因此对二者应用cv2.matchShapes()函数后,返回值的差也较大。
相关文章:

opencv之图像轮廓
文章目录 前言查找并绘制轮廓查找图像轮廓:findContours函数绘制图像轮廓:drawContours函数轮廓实例 矩特征Hu矩Hu矩函数形状匹配 前言 图像轮廓指的是图像中物体边缘或形状的外部线条,通常通过图像处理技术来检测和提取。轮廓是用于描述物体…...

shader 案例学习笔记之step函数
step函数 参数是float step(edge,x):当x>edge时返回1,否则返回0 #ifdef GL_ES precision mediump float; #endifuniform vec2 u_resolution;void main(){vec2 st gl_FragCoord.xy/u_resolution.xy;float f step(0.5, st.x);gl_FragColor vec4(f…...

node快速复制文件或文件夹,排除部分文件(node_modules)
const fs require(fs) const path require(path)/*** description: 获取完整的文件路径* param {*} url 路径* return {*} 返回完整的文件路径*/ const getPath (url) > {return path.join(__dirname, url) }/*** description: 获取参数* return {*} target【目标文件夹】…...

网络层 VIII(网络层设备——路由器)【★★★★★★】
一、冲突域与广播域 这里的“域”表示冲突或广播在其中发生并传播的区域。 1. 冲突域 冲突域是指连接到同一物理介质上的所有结点的集合,这些结点之间存在介质争用的现象(能产生冲突的所有设备的集合)。也就是说,若这些设备同时发…...

302.AI学术论文搜索工具的智能体验
Hey朋友们, 你是否曾在学术的海洋里迷失方向,为了找到一篇论文而苦苦挣扎? 就像在茫茫大海中寻找灯塔,我们渴望一盏明灯,指引我们前行。 别担心,今天我来给你介绍一个超级给力的工具——302.AI学术论文…...
arm平台常用工具和动态库编译
目录 perf 交叉编译交叉编译zlib交叉编译 elfutils交叉编译 perfperf 使用 libconfig 交叉编译openssl 交叉编译libpcap 交叉编译统信、龙芯等平台编译QT交叉编译编译QT源码编译QtCreator 编译ssl编译libsrtp(2.3.0版本)编译libyuvlibopus编译libopenh264编译libusrsctp编译lib…...
代码随想录训练营day45|115.不同的子序列,583. 两个字符串的删除操作,72. 编辑距离
115.不同的子序列 题目 dp[i][j]表示的是在以是s[j]为结尾的字符串中最多可以找到几种组成以t[i]为结尾的字符串的方式。 如果s[i]t[j], 1.利用第i个和第j个匹配,在j-1中寻找i-1. 2.不适用这两个进行匹配,在j-1中寻找i 如果s[i]!…...
椋鸟C++笔记#7:标准模板库STL初识
文章目录 标准模板库(Standard Template Library)STL的版本P.J.版RW版SGI版 STL的组成部分 萌新的学习笔记,写错了恳请斧正。 标准模板库(Standard Template Library) 标准模板库STL,是C标准库的一个非常重…...
滴滴嘀嗒,出行行业响起Robotaxi“倒计时”
文:互联网江湖 作者:刘致呈 前几天,各大出行平台的半年报陆续披露完毕,有的还在亏损,但也有人开始盈利。 如祺出行上市后的首份半年报营收10.37亿,同比增长13.6%。上半年运营亏损为2.56亿元,同…...

【MATLAB源码-第264期】基于matlab的跳频通信系统仿真,采用MSK调制方式,差分解调;输出误码率曲线和各节点波形图。
操作环境: MATLAB 2022a 1、算法描述 跳频通信系统是一种能够提高通信抗干扰能力的技术,它通过在传输过程中不断地改变载波频率来避开干扰或者窃听。在这套跳频通信系统中,我们采用了最小频移键控(MSK)作为调制方式…...
如何在多台电脑上同步 VSCode配置和插件
上一篇文章最新前端开发VSCode高效实用插件推荐清单总结了前端开发实用的插件,换电脑的时候怎么同步这些配置与插件呢,难道又要重新安装一遍吗😱 现在就来聊聊要在多台电脑上同步 VSCode配置和插件的几种方法: 方法一࿱…...
深度优先算法,广度优先算法,hill climbing,贪心搜索,A*算法,启发式搜索算法是什么,比起一般搜索法算法有什么区别
深度优先算法(Depth-First Search, DFS) 深度优先搜索是一种用于遍历或搜索树或图的算法。它沿着树的深度遍历树的节点,尽可能深地搜索树的分支。当节点v的所在边都已被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程…...

《python语言程序设计》2018版第8章第14题金融:信用卡号合法性 利用6.29题
一、之前6.29题我做的代码 这是用数字来进行分辨的 is_txt 4383576018402626 #合法def split_the_data_even(vis_n):current_a1 vis_n // 10000a_t1 vis_n % 10000# print("1th", a_t1)a_t2 current_a1 % 10000# print("2th", a_t2)current_a3 curre…...

QT 基础学习
1> 使用绘制事件完成钟表的绘制 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPainter> #include <QDebug> #include <QTime> #include <QTimer> #include <QDateTime> //#include <string> #includ…...

【Gephi】可视化教程
此教程专供欣欣向荣及其舍友使用 文章目录 导入数据上色改变布局设置节点大小统计拓扑结构输出图形保存文件 导入数据 点击【文件】-【导入电子表格】 先选择csv格式的network 直接下一步 点击完成 【图的类型】改为“有向的” 点击确认 会弹出报错,直接clos…...

演化式原型开发-系统架构师(六十五)
1快速迭代式的原型开发能够有效控制成本,()是指开发过程中逐步改进和细化原型直到产生目标系统。 A可视化原型开发 B抛弃式原型开发 C演化式原型开发 D增量式原型开发 解析: 原型开发分为两大类:快速原型开发(抛弃…...

初识爬虫4
1.理解代理ip,正向代理和反向代理 2.代理ip分类,根据匿名度分类:透明,匿名,高匿 3.防止频繁向同一个域名发送请求被封ip,需使用代理ip # -*- coding: utf-8 -*- import requestsurl https://www.baidu.comproxies {…...

Golang | Leetcode Golang题解之第387题字符串中的第一个唯一字符
题目: 题解: type pair struct {ch bytepos int }func firstUniqChar(s string) int {n : len(s)pos : [26]int{}for i : range pos[:] {pos[i] n}q : []pair{}for i : range s {ch : s[i] - aif pos[ch] n {pos[ch] iq append(q, pair{ch, i})} e…...

【CanMV K230 AI视觉】 人体检测
【CanMV K230 AI视觉】 人体检测 人体检测 动态测试效果可以去下面网站自己看。 B站视频链接:已做成合集 抖音链接:已做成合集 人体检测 人体检测是判断摄像头画面中有无出现人体,常用于人体数量检测,人流量监控以及安防监控等。…...

解决浏览器自动将http网址转https
删除浏览器自动使用https的方式 在浏览器地址栏输入:chrome://net-internals/#hsts PS:如果是edge浏览器可输入:edge://net-internals/#hsts 在Delete domain security policies搜索框下,输入要删除的域名,然后点击delete 解决方法&#…...

深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...