什么是轮廓?
轮廓可以简单地解释为连接具有相同颜色或强度的所有连续点(沿边界)的曲线。轮廓是用于形状分析以及对象检测和识别的有用工具。- 为了获得更高的准确性,请使用二进制图像。因此,在找到轮廓之前,请应用阈值或 Canny 边缘检测。
- 从OpenCV 3.2开始,**findContours()** 不再修改源图像。
- 在OpenCV中,找到轮廓就像从黑色背景中找到白色物体。因此请记住,要找到的对象应该是白色,背景应该是黑色。
SetWorkingDir A_ScriptDir
hOpencv := DllCall("LoadLibrary", "str", "opencv_world455.dll", "ptr")
hOpencvCom := DllCall("LoadLibrary", "str", "autoit_opencv_com455.dll", "ptr")
DllCall("autoit_opencv_com455.dll\DllInstall", "int", 1, "wstr", A_IsAdmin = 0 ? "user" : "", "cdecl")
cv := ComObject("OpenCV.cv")
img := cv.imread("12.png")
cv.imshow("Image1", img)
img_ray := cv.cvtColor(img, CV_COLOR_BGR2GRAY:=6)
;这里获取threshold()的双返回值有三种方法
; 一
; thresh_img := ComObject("OpenCV.cv.MAT")
; ret := cv.threshold(img_ray, 127, 255, CV_THRESH_BINARY:=0, thresh_img)
; contours := cv.findContours(thresh_img, CV_RETR_TREE:=3, CV_CHAIN_APPROX_SIMPLE:=2)
; 二
; ret := cv.threshold(img_ray, 127, 255, CV_THRESH_BINARY:=0)
; thresh_img := cv.extended()[1]
; contours := cv.findContours(thresh_img, CV_RETR_TREE:=3, CV_CHAIN_APPROX_SIMPLE:=2)
; 三
cv.threshold(img_ray,100, 255, CV_THRESH_BINARY:=0)
ret := cv.extended()[0]
thresh_img := cv.extended()[1]
contours := cv.findContours(thresh_img, CV_RETR_TREE:=3, CV_CHAIN_APPROX_SIMPLE:=2)
hierarchy := cv.extended()[1]
; 参数
; 在 cv.findContours() 函数中有三个参数,
; 第一个是源图像,
; 第二个是轮廓检索模式,
; 第三个是轮廓逼近方法,并输出轮廓和层次。
; 返回值
; contours:图像轮廓坐标,是一个链表
; hierarchy:[Next, Previous, First Child, Parent]
; 如果我们打印出cv.findContours()函数的返回值hierarchy,会发现它是一个包含4个值的数组:[Next, Previous, First Child, Parent]
; Next:与当前轮廓处于同一层级的下一条轮廓
; 举例来说,前面图中跟0处于同一层级的下一条轮廓是1,所以Next=1;同理,对轮廓1来说,Next=2;那么对于轮廓2呢?没有与它同一层级的下一条轮廓了,此时Next=-1。
; Previous:与当前轮廓处于同一层级的上一条轮廓
; 跟前面一样,对于轮廓1来说,Previous=0;对于轮廓2,Previous=1;对于轮廓2a,没有上一条轮廓了,所以Previous=-1。
; First Child:当前轮廓的第一条子轮廓
; 比如对于轮廓2,第一条子轮廓就是轮廓2a,所以First Child=2a;对轮廓3,First Child=3a。
; Parent:当前轮廓的父轮廓
; 比如2a的父轮廓是2,Parent=2;轮廓2没有父轮廓,所以Parent=-1。
如何绘制轮廓?
要绘制轮廓,请使用 cv.drawContours() 函数。只要有边界点,它也可以用来绘制任何形状。它的第一个参数是源图像,第二个参数是作为链表传递的轮廓,第三个参数是轮廓的索引(在绘制单个轮廓时很有用。要绘制所有轮廓,请传递-1),其余参数是颜色,厚度等等。 要在图像中绘制所有轮廓:cv.drawContours(img, contours, -1, ComArrayMake([0,0,255]), 3)
ComArrayMake(inputArray)
{
arr := ComObjArray(VT_VARIANT:=12, inputArray.Length)
Loop inputArray.Length
{
arr[A_Index-1] := inputArray[A_Index]
}
return arr
}
要绘制单个轮廓,请说第四个轮廓:
cv.drawContours(img, contours, 3, ComArrayMake([0,0,255]), 3)
但是在大多数情况下,以下方法会很有用:
cnt := contours[0]
cv.drawContours(img, ComArrayMake([cnt]), 0, ComArrayMake([0,0,255]), 3)
轮廓近似法
相关链接:https://www.cnblogs.com/GaloisY/p/11062065.html https://opencv.apachecn.org/#/docs/4.0.0/4.9-tutorial_py_contours cv.findContours() 函数中的第三个参数。它实际上表示什么? 定义轮廓的近似方法,取值如下: CV_CHAIN_APPROX_NONE:保存物体边界上所有连续的轮廓点到contours向量内; CV_CHAIN_APPROX_SIMPLE:仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留; CV_CHAIN_APPROX_TC89_L1:使用teh-Chinl chain 近似算法; CV_CHAIN_APPROX_TC89_KCOS:使用teh-Chinl chain 近似算法。 下面的矩形图像演示了此技术。只需在轮廓数组中的所有坐标上绘制一个圆(以蓝色绘制)。第一个图像显示点I与得到 cv.CHAIN_APPROX_NONE (734点)和第二图像显示了一个与 cv.CHAIN_APPROX_SIMPLE (仅4个点)。
轮廓特征
1.图像矩
在图像处理、计算机视觉和相关领域中,图像矩是图像像素强度的某个特定加权平均值(矩),或者是此类矩的函数,通常选择具有某些吸引人的属性或解释。 图像矩可用于描述分割后的对象。通过图像矩找到的图像的简单属性包括面积(或总强度)、其质心和有关其方向的信息。 相关链接:https://cloud.tencent.com/developer/article/1470636SetWorkingDir A_ScriptDir
hOpencv := DllCall("LoadLibrary", "str", "opencv_world455.dll", "ptr")
hOpencvCom := DllCall("LoadLibrary", "str", "autoit_opencv_com455.dll", "ptr")
DllCall("autoit_opencv_com455.dll\DllInstall", "int", 1, "wstr", A_IsAdmin = 0 ? "user" : "", "cdecl")
cv := ComObject("OpenCV.cv")
img := cv.imread("jiantou.png")
cv.imshow("Image1", img)
img_ray := cv.cvtColor(img, CV_COLOR_BGR2GRAY:=6)
;这里获取threshold()的双返回值有三种方法
; 一
; thresh_img := ComObject("OpenCV.cv.MAT")
; ret := cv.threshold(img_ray, 127, 255, CV_THRESH_BINARY:=0, thresh_img)
; contours := cv.findContours(thresh_img, CV_RETR_TREE:=3, CV_CHAIN_APPROX_SIMPLE:=2)
; 二
; ret := cv.threshold(img_ray, 127, 255, CV_THRESH_BINARY:=0)
; thresh_img := cv.extended()[1]
; contours := cv.findContours(thresh_img, CV_RETR_TREE:=3, CV_CHAIN_APPROX_SIMPLE:=2)
; 三
cv.threshold(img_ray, 100, 255, CV_THRESH_BINARY:=0)
ret := cv.extended()[0]
thresh_img := cv.extended()[1]
contours := cv.findContours(thresh_img, CV_RETR_TREE:=3, CV_CHAIN_APPROX_SIMPLE:=2)
hierarchy := cv.extended()[1]
cnt := contours[0]
M := cv.moments(cnt)
;M_ji 表示空间矩
;矩 m_00 给出了轮廓的区域,这等价于函数cv2.contourArea()。
m00 := M.m00
m10 := M.m10
m01 := M.m01
m20 := M.m20
m11 := M.m11
m02 := M.m02
m30 := M.m30
m21 := M.m21
m12 := M.m12
m03 := M.m03
;Mu_ji 表示中心矩
mu20 := M.mu20
mu11 := M.mu11
mu02 := M.mu02
mu30 := M.mu30
mu21 := M.mu21
mu12 := M.mu12
mu03 := M.mu03
;Nu_jl 表示归一化中心矩
nu20 := M.nu20
nu11 := M.nu11
nu02 := M.nu02
nu30 := M.nu30
nu21 := M.nu21
nu12 := M.nu12
nu03 := M.nu03
MsgBox("m00: " m00 "`n" "m10: " m10 "`n"
"m01: " m01 "`n" "m20: " m20 "`n"
"m11: " m11 "`n" "m02: " m02 "`n"
"m30: " m30 "`n" "m21: " m21 "`n"
"m12: " m12 "`n" "m03: " m03 "`n"
, "空间矩"
)
MsgBox("mu20: " mu20 "`n" "mu11: " mu11 "`n"
"mu02: " mu02 "`n" "mu30: " mu30 "`n"
"mu21: " mu21 "`n" "mu12: " mu12 "`n"
"mu03: " mu03 "`n"
, "中心矩"
)
MsgBox("nu20: " nu20 "`n" "nu11: " nu11 "`n"
"nu02: " nu02 "`n" "nu30: " nu30 "`n"
"nu21: " nu21 "`n" "nu12: " nu12 "`n"
"nu03: " nu03 "`n"
, "归一化中心矩"
)
在图像矩中,您可以提取有用的数据,例如面积,质心等。质心由关系C给出 $C_x = \frac{M_{10}}{M_{00}}$ 和 $C_y = \frac{M_{01}}{M_{00}}$。可以按照以下步骤进行:
cx := Integer(M.m10/M.m00)
cy := Integer(M.m01/M.m00)
MsgBox "质心X: " cx "`n" "质心Y: " cy
接下来在坐标处画圆
效果图:
2.轮廓面积
轮廓区域由函数 cv.contourArea() 或从力矩**m00**中给出。area := cv.contourArea(cnt)
MsgBox "轮廓面积: " area
3.轮廓周长
也称为弧长。可以使用 cv.arcLength() 函数找到它。第二个参数指定形状是闭合轮廓(如果通过True)还是曲线。perimeter := cv.arcLength(cnt, ComValue(0xB, -1)) ;第二个参数给了true
MsgBox perimeter
4.轮廓近似
根据我们指定的精度,它可以将轮廓形状近似为顶点数量较少的其他形状。它是Douglas-Peucker算法的实现。 相关链接:https://blog.csdn.net/wangqinghao/article/details/8207070#:~:text=Douglas-,%E6%95%B0%E9%87%8F%E7%9A%84%E4%B8%80%E7%A7%8D%E7%AE%97%E6%B3%95%E3%80%82在数字化时,要对曲线进行采样,即在曲线上取有限个点,将其变为折线,并且能够在一定程度上保持原有的形状。
经典的Douglas-Peucker算法步骤如下:
(1)在曲线首尾两点A,B之间连接一条直线AB,该直线为曲线的弦;
(2)得到曲线上离该直线段距离最大的点C,计算其与AB的距离d;
(3)比较该距离与预先给定的阈值threshold的大小,如果小于threshold,则该直线段作为曲线的近似,该段曲线处理完毕。
(4)如果距离大于阈值,则用C将曲线分为两段AC和BC,并分别对两段取信进行1~3的处理。
(5)当所有曲线都处理完毕时,依次连接各个分割点形成的折线,即可以作为曲线的近似。
SetWorkingDir A_ScriptDir
hOpencv := DllCall("LoadLibrary", "str", "opencv_world455.dll", "ptr")
hOpencvCom := DllCall("LoadLibrary", "str", "autoit_opencv_com455.dll", "ptr")
DllCall("autoit_opencv_com455.dll\DllInstall", "int", 1, "wstr", A_IsAdmin = 0 ? "user" : "", "cdecl")
cv := ComObject("OpenCV.cv")
img := cv.imread("jinsi.png")
cv.imshow("Image", img)
img_ray := cv.cvtColor(img, CV_COLOR_BGR2GRAY:=6)
cv.threshold(img_ray, 100, 255, CV_THRESH_BINARY:=0)
ret := cv.extended()[0]
thresh_img := cv.extended()[1]
contours := cv.findContours(thresh_img, CV_RETR_TREE:=3, CV_CHAIN_APPROX_SIMPLE:=2)
hierarchy := cv.extended()[1]
cnt := contours[0]
epsilon := 0.01*cv.arcLength(cnt, ComValue(0xB, -1))
approx := cv.approxPolyDP(cnt, epsilon, ComValue(0xB, -1))
cv.drawContours(img, ComArrayMake([approx]), 0, ComArrayMake([0,0,255]), 3)
cv.imshow("Image1", img)
cv.waitKey()
cv.destroyAllWindows()
ComArrayMake(inputArray)
{
arr := ComObjArray(VT_VARIANT:=12, inputArray.Length)
Loop inputArray.Length
{
arr[A_Index-1] := inputArray[A_Index]
}
return arr
}
下面,在第二张图片中,绿线显示了 精度 epsilon = 10% 时的近似曲线。第三幅图显示了精度 epsilon = 1% 时的情况。第三个参数指定曲线是否闭合。
5.凸包
逼近多边形是某个图像轮廓的高度近似,而凸包的提出是为了简化逼近多边形的。其实,凸包跟逼近多边形很像,只不过它是物体最外层的“凸”多边形。
简单的概括,凸包是指完全包含原有轮廓,并且仅由轮廓上的点所构成的多边形。凸包的特点是每一处都是凸的,即在凸包内连接任意两点的直线都在凸包的内部,并且任意连续3个点的内角小于180度。
凸包外观看起来与轮廓逼近相似,但并非如此(在某些情况下两者可能提供相同的结果)。在这里,**cv.convexHull()** 函数检查曲线是否存在凸凹缺陷并对其进行校正。一般而言,凸曲线是始终凸出或至少平坦的曲线。如果在内部凸出,则称为凸度缺陷。
hull := cv.convexHull(points[, hull[, clockwise[, returnPoints]]])
参数详细信息:
- points: 就是我们传入的轮廓。
- hull: 是输出,通常我们避免它。
- clockwise:方向标记。如果为True,则输出凸包为顺时针方向。否则,其方向为逆时针方向。
- returnPoints:默认情况下为True。然后返回船体点的坐标。如果为False,则返回与船体点相对应的轮廓点的索引。
因此,要获得如上图所示的凸包,以下内容就足够了:
hull := cv.convexHull(cnt)
SetWorkingDir A_ScriptDir
hOpencv := DllCall("LoadLibrary", "str", "opencv_world455.dll", "ptr")
hOpencvCom := DllCall("LoadLibrary", "str", "autoit_opencv_com455.dll", "ptr")
DllCall("autoit_opencv_com455.dll\DllInstall", "int", 1, "wstr", A_IsAdmin = 0 ? "user" : "", "cdecl")
cv := ComObject("OpenCV.cv")
img := cv.imread("tubao.png")
cv.imshow("Image", img)
img_ray := cv.cvtColor(img, CV_COLOR_BGR2GRAY:=6)
cv.threshold(img_ray, 100, 255, CV_THRESH_BINARY:=0)
ret := cv.extended()[0]
thresh_img := cv.extended()[1]
contours := cv.findContours(thresh_img, CV_RETR_TREE:=3, CV_CHAIN_APPROX_SIMPLE:=2)
hierarchy := cv.extended()[1]
cnt := contours[0]
hull := cv.convexHull(cnt)
cv.drawContours(img, ComArrayMake([hull]), 0, ComArrayMake([0,0,255]), 2)
cv.imshow("Image1", img)
cv.waitKey()
cv.destroyAllWindows()
ComArrayMake(inputArray)
{
arr := ComObjArray(VT_VARIANT:=12, inputArray.Length)
Loop inputArray.Length
{
arr[A_Index-1] := inputArray[A_Index]
}
return arr
}
凸包缺陷
对二值图像进行轮廓分析之后,可以构建每个轮廓的凸包,构建完成之后会返回该凸包包含的点集。根据返回的凸包点集可以绘制该轮廓对应的凸包。一般来说,凸性曲线总是凸出来的,至少是平的。如果有地方凹进去了就被叫做凸性缺陷。 OpenCV带有一个现成的函数 cv.convexityDefect() 来查找该函数。 注意:进行凸检测时,凸包检测中的returnPoints要设置为False 凸缺陷返回一个数组,每一行包含值是起点,终点,最远的点,到最远点的近似距离,返回的前三个点都是轮廓索引。convexityDefects := cv.convexityDefects(contour, convexhull)
contour: 检测到的轮廓,可以调用findContours函数得到
convexhull:检测到的凸包,可以调用convexHull函数得到。
convexityDefects:输出参数,检测到的最终结果,返回一个数组,其中每一行包含的值是[起点,终点,最远的点,到最远点的近似距离]。前三个点都是轮廓索引。
前三个值得含义分别为:凸缺陷的起始点,凸缺陷的终点,凸缺陷的最深点(即边缘点到凸包距离最大点)
下面这部分代码由社区群友zzZ...大佬提供。
SetWorkingDir A_ScriptDir
cv := ComObject("OpenCV.cv")
out := ComObject("OpenCV.cv.MAT")
img := cv.imread("tubao.png")
cv.imshow("Image", img)
img_ray := cv.cvtColor(img, CV_COLOR_BGR2GRAY:=6)
cv.threshold(img_ray, 100, 255, CV_THRESH_BINARY:=0)
ret := cv.extended()[0]
thresh_img := cv.extended()[1]
contours := cv.findContours(thresh_img, CV_RETR_TREE:=3, CV_CHAIN_APPROX_SIMPLE:=2)
hierarchy := cv.extended()[1]
cnt := contours[0]
vofp := ComObject("OpenCV.VectorOfVectorOfPoint")
contours := cv.findContours(thresh_img, CV_RETR_TREE:=3, CV_CHAIN_APPROX_SIMPLE:=2, vofp)
vp := vofp[0]
; epsilon := 0.01*cv.arcLength(cnt, ComValue(0xB, -1))
; approx := cv.approxPolyDP(cnt, epsilon, ComValue(0xB, -1))
hull := cv.convexHull(cnt)
hull1 := cv.convexHull(cnt,,, ComValue(0xB, 0))
defects := cv.convexityDefects(cnt,hull1)
data := defects.data
loop(defects.rows)
{
r := A_Index
loop(defects.cols)
{
c := A_Index
loop(defects.channels)
{
f := numget(data, (r - 1) * (defects.cols * defects.channels * 4) + (c - 1) * defects.channels * 4 + (A_Index - 1) * 4 , "int")
a .= f "-"
if(A_Index == 3)
{
px := numget(cnt.data, f * 8, "Int")
py := numget(cnt.data, f * 8 + 4, "Int")
ax := vp[f][0]
ay := vp[f][1]
cv.circle(img,ComArrayMake([px,py]), 3, ComArrayMake([0,0,255]), -1)
}
}
a .= " | "
}
a .= "`n"
}
;cv.drawContours(img, ComArrayMake([hull]), 0, ComArrayMake([0,0,255]), 2)
cv.imshow("Image1", img)
cv.waitKey()
cv.destroyAllWindows()
ComArrayMake(inputArray)
{
arr := ComObjArray(VT_VARIANT:=12, inputArray.Length)
Loop inputArray.Length
{
arr[A_Index-1] := inputArray[A_Index]
}
return arr
}
6.检查凸度
cv.isContourConvex() 用来检查曲线是否为凸多边形。它只是返回True还是False。k := cv.isContourConvex(cnt)
MsgBox(k)
7.边界矩形
7.1直边界矩形(外接矩形)
一个直矩形(就是没有旋转的矩形)。它不会考虑对象是否旋转。 所以边界矩形的面积不是最小的。可以使用函数 cv.boundingRect() 查找得到。返回值 (x, y)为矩形左上角的坐标,( w, h)是矩形的宽和高。
SetWorkingDir A_ScriptDir
hOpencv := DllCall("LoadLibrary", "str", "opencv_world455.dll", "ptr")
hOpencvCom := DllCall("LoadLibrary", "str", "autoit_opencv_com455.dll", "ptr")
DllCall("autoit_opencv_com455.dll\DllInstall", "int", 1, "wstr", A_IsAdmin = 0 ? "user" : "", "cdecl")
cv := ComObject("OpenCV.cv")
img := cv.imread("xiefangkuai.png")
img_ray := cv.cvtColor(img, CV_COLOR_BGR2GRAY:=6)
cv.threshold(img_ray, 127, 255, CV_THRESH_BINARY:=0)
ret := cv.extended()[0]
thresh_img := cv.extended()[1]
contours := cv.findContours(thresh_img, CV_RETR_TREE:=3, CV_CHAIN_APPROX_SIMPLE:=2)
hierarchy := cv.extended()[1]
cnt := contours[0]
For k in contours{
i := cv.boundingRect(k)
img := cv.rectangle(img, i, ComArrayMake([0, 0, 255]), 2)
}
cv.imshow("Image", img)
cv.waitKey()
cv.destroyAllWindows()
ComArrayMake(inputArray)
{
arr := ComObjArray(VT_VARIANT:=12, inputArray.Length)
Loop inputArray.Length
{
arr[A_Index-1] := inputArray[A_Index]
}
return arr
}
7.2旋转的边界矩形(最小外接矩形)
这个边界矩形是面积最小的,因为它考虑了对象的旋转。用到的函数为 cv2.minAreaRect()。返回的是一个Box2D 结构,其中包含矩形左上角角点的坐标( x, y),矩形的宽和高( w, h),以及旋转角度。但是要绘制这个矩形需要矩形的 4 个角点,可以通过函数 cv.boxPoints() 获得。
s := cv.minAreaRect(cnt)
a := cv.boxPoints(s)
arr := []
loop 8{
arr.push(Integer(cv.boxPoints(s)[A_Index-1]))
}
cv.polylines(img, OpencvAHK_ConstPointConst([[arr[1], arr[2]], [arr[3],arr[4]],[arr[5],arr[6]],[arr[7],arr[8]]]), ComValue(0xB, -1), ComArrayMake([255,0,0]), 3)
8.最小外接圆
接下来,我们使用函数 cv.minEnclosingCircle() 找到对象的外接圆。它是一个以最小面积完全覆盖对象的圆圈。center := cv.minEnclosingCircle(cnt)
radius := cv.extended()[1]
cv.circle(img, center, radius, ComArrayMake([0,255,0]), 2)
9.拟合椭圆
下一步是使椭圆适合对象。它返回椭圆所在的旋转矩形。 点集拟合椭圆并非最小包围椭圆。返回椭圆并不满足“轮廓上的点均在椭圆包围空间内”这一条件。 其原理是: ① 寻找包裹轮廓的最小斜矩阵**minAreaRect() **。 ② 返回斜矩阵内最大的椭圆(矩阵长宽分别做椭圆长轴、短轴)。RotatedRect fitEllipse(InputArray points); 唯一一个参数是输入的二维点集,可以是 vector 或 Mat 类型。
ellipse := cv.fitEllipse(cnt)
cv.ellipse(img,ellipse,ComArrayMake([0,255,0]),2)
10.拟合线
图像处理中,通常会遇到根据给定的点集(比如轮廓)拟合出一条直线的情形。opencv2和opencv3中提供了一个专门用于直线拟合的函数**cv.fitLine()**。
第一个参数是用于拟合直线的输入点集,可以是二维点的cv::Mat数组,也可以是二维点的STL vector。 第二个参数是输出的直线,对于二维直线而言类型为cv::Vec4f,对于三维直线类型则是cv::Vec6f,输出参数的前半部分给出的是直线的方向,而后半部分给出的是直线上的一点(即通常所说的点斜式直线)。 第三个参数是距离类型,拟合直线时,要使输入点到拟合直线的距离和最小化(即下面公式中的cost最小化),可供选的距离类型如下表所示,ri表示的是输入的点到直线的距离。 第四个参数是距离参数,跟所选的距离类型有关,值可以设置为0,cv::fitLine()函数本身会自动选择最优化的值。 第五、六两个参数用于表示拟合直线所需要的径向和角度精度,通常情况下两个值均被设定为1e-2。rows := img.rows()
cols := img.cols()
line := cv.fitLine(cnt, CV_DIST_L2:=2, 0, 0.01, 0.01)
vx := line[0]
vy := line[1]
x := line[2]
y := line[3]
lefty := Integer((-x*vy/vx) + y)
righty := Integer(((cols-x)*vy/vx) + y)
cv.line(img, ComArrayMake([cols-1, righty]), ComArrayMake([0,lefty]), ComArrayMake([255,0,0]), 2)
轮廓属性
质心,面积,周长等也属于此类,上面已经写了。1.长宽比
它是对象边界矩形的宽度与高度的比率。aspect := cv.boundingRect(cnt)
x := aspect[0]
y := aspect[1]
w := aspect[2]
h := aspect[3]
aspect_ratio := float(w)/h
MsgBox("长宽比-直边界矩形(外接矩形):" aspect_ratio)
2.范围
范围是轮廓区域与边界矩形区域的比率。area := cv.contourArea(cnt)
aspect := cv.boundingRect(cnt)
w := aspect[2]
h := aspect[3]
rect_area := w*h
extent := float(area)/rect_area
MsgBox("范围-轮廓区域与边界矩形区域的比率" extent)
3.固实性
固实性是轮廓面积与其凸包面积的比率。area := cv.contourArea(cnt)
hull := cv.convexHull(cnt)
hull_area := cv.contourArea(hull)
solidity := float(area)/hull_area
MsgBox("固实性-轮廓面积与其凸包面积的比率:" solidity)
4.等效直径
等效直径是面积与轮廓面积相同的圆的直径。area := cv.contourArea(cnt)
equi_diameter := sqrt(4*area/3.141592653589793)
MsgBox("等效直径-面积与轮廓面积相同的圆的直径:" equi_diameter)
5.方向
方向是物体指向的角度。ellipse := cv.fitEllipse(cnt)
MsgBox(ellipse.angle())
6.遮罩和像素点
在某些情况下,我们可能需要构成该对象的所有点。可以按照以下步骤完成:mat := ComObject("OpenCV.cv.MAT")
mask := mat.zeros(img.size(), CV_8UC1:= cv2.CV_MAKETYPE(cv2.CV_8U, 1))
cv.circle(mask,ComArrayMake([300,263]), 140, ComArrayMake([255, 255, 255]), -1)
pixelpoints := cv.findNonZero(mask)
7.最大值,最小值及其位置
我们可以使用遮罩图像找到这些参数。cv.minMaxLoc(img_ray, mask)
min_val := cv.extended()[0]
max_val := cv.extended()[1]
min_loc := cv.extended()[2]
max_loc := cv.extended()[3]
MsgBox("最小值:" min_val "最大值:" max_val "最小位置:" "x:" min_loc[0] "y:" min_loc[1] "最大位置:" "x:" max_loc[0] "y:" max_loc[1])
8.平均颜色或平均强度
在这里,我们可以找到对象的平均颜色。或者可以是灰度模式下物体的平均强度。我们再次使用相同的蒙版进行此操作。mean_val := cv.mean(img, mask)
MsgBox("B通道均值:" mean_val[0] "`n" "G通道均值:" mean_val[1] "`n" "R通道均值:" mean_val[2] "`n")
9.极端点
极点是指对象的最顶部,最底部,最右侧和最左侧的点。天黑版opencv_ahk.dll使用(改变了调用方式,优化速度…)
相关文件:https://wwz.lanzouw.com/iAkK803eaaud
cv2.ahk和log.ahk来自社区群友zzZ…
可以用文件中的天黑版的v2h版ahk运行。
示例:轮廓
#Dllload lib
#DllLoad opencv_ahk.dll
#include <cv2>
#include <log>
SetWorkingDir A_ScriptDir
;初始化opencv模块
cv := ObjFromPtr(DllCall('opencv_ahk.dll\opencv_init', 'ptr', DllCall(A_AhkPath '\ahkGetApi', 'ptr'), 'cdecl ptr'))
img := cv.imread("image/11.png")
cv.imshow("image", img)
cv.cvtColor(img, img_grey := cv.Mat(), cv2.CV_COLOR_BGR2GRAY)
cv.Canny(img_grey, canny := cv.Mat(), 80.0, 160.0)
k := cv.getStructuringElement(cv2.CV_MORPH_RECT, [3, 3], [-1, -1])
cv.dilate(canny, dilation := cv.MAT(), k)
cv.threshold(dilation, thresh_img := cv.MAT(), 127, 255, cv2.CV_THRESH_BINARY)
cv.findContours(thresh_img, contours := cv.Vector_Vector_Point(), cv2.CV_RETR_TREE, cv2.CV_CHAIN_APPROX_SIMPLE)
cv.drawContours(img, contours, 0, [0,255,0], 3)
;矩
M := cv.moments(contours[0])
m00 := M.m00
log.info('m00:' m00)
;轮廓面积
area := cv.contourArea(contours[0])
log.info('轮廓面积:' area)
;轮廓周长
perimeter := cv.arcLength(contours[0], true)
log.info('轮廓周长:' perimeter)
;轮廓近似
epsilon := 0.01*cv.arcLength(contours[0], true)
cv.approxPolyDP(contours[0], approx := cv.Vector_Point(), epsilon, true)
;cv.drawContours(img, cv.Vector_Vector_Point([approx]), 0, [0, 0, 255], 3)
;凸包
cv.convexHull(contours[0], hull := cv.Vector_Point())
;cv.drawContours(img, cv.Vector_Vector_Point([hull]), 0, [0, 0, 255], 2)
;检查凸度
k := cv.isContourConvex(contours[0])
log.info('凸度:' k)
;直边界矩形(外接矩形)
box := cv.boundingRect(contours[0])
x := box[1]
y := box[2]
w := box[3]
h := box[4]
cv.rectangle(img, [x, y], [x+w, y+h], [0,255,0], 2)
;旋转的边界矩形(最小外接矩形)
rect := cv.minAreaRect(contours[0])
;log.info(rect)
cv.boxPoints(rect, box1 := cv.Vector_Point2f())
cv.polylines(img, cv.Vector_Point(box1.toarray()), true, [0, 255, 255], 1)
;最小外接圆
cv.minEnclosingCircle(contours[0], ¢er, &radius)
;log.info(center)
;.info(radius)
cv.circle(img, center, radius, [0, 0, 255], 2)
;拟合椭圆
ellipse := cv.fitEllipse(contours[0])
cv.ellipse(img, ellipse, [0,255,0], 2)
;拟合线
cv.fitLine(contours[0], line := cv.vector_float(), cv2.CV_DIST_HUBER, 0, 0.01, 0.01)
;log.info(line[0])
vx := line[0]
vy := line[1]
x := line[2]
y := line[3]
lefty := Integer((-x*vy/vx) + y)
righty := Integer(((img.cols-x)*vy/vx) + y)
cv.line(img, [img.cols-1, righty], [0,lefty], [255,0,0], 2)
;轮廓属性
;长宽比
box := cv.boundingRect(contours[0])
x := box[1]
y := box[2]
w := box[3]
h := box[4]
aspect_ratio := float(w)/h
log.info('长宽比-(外接矩形):' aspect_ratio)
;范围
area := cv.contourArea(contours[0])
aspect := cv.boundingRect(contours[0])
w := aspect[2]
h := aspect[3]
rect_area := w*h
extent := float(area)/rect_area
log.info("范围-(轮廓区域与边界矩形区域的比率):" extent)
;固实性
area := cv.contourArea(contours[0])
cv.convexHull(contours[0], hull := cv.Vector_Point())
hull_area := cv.contourArea(hull)
solidity := float(area)/hull_area
log.info("固实性-(轮廓面积与其凸包面积的比率):" solidity)
;等效直径
area := cv.contourArea(contours[0])
equi_diameter := sqrt(4*area/3.141592653589793)
log.info("等效直径-(面积与轮廓面积相同的圆的直径):" equi_diameter)
;方向
ellipse := cv.fitEllipse(contours[0])
log.info('方向是物体指向的角度:' ellipse[5])
;遮罩和像素点
mask := cv.MAT(img.rows, img.cols, cv2.CV_8UC1, [0, 0, 0]) ;注意单通道
cv.circle(mask, [100, 60], 40, [255, 255, 255], -1)
cv.imshow("image11", mask)
cv.findNonZero(mask, pixelpoints := cv.Vector_Point())
log.info(pixelpoints.toarray())
;最大值,最小值及其位置
cv.minMaxLoc(img_grey, &minVal, &maxVal, &minLoc, &maxLoc)
log.info("最小值:" minVal "最大值:" maxVal "最小位置:" "x:" minLoc[1] "y:" minLoc[2] "最大位置:" "x:" maxLoc[1] "y:" maxLoc[2])
;平均颜色或平均强度(BGR)
mean_val := cv.mean(img, mask)
log.info(mean_val)
;极端点
log.info(contours[0].toarray())
x_array := []
y_array := []
max_right := 0
max_left := 9999
max_top := 9999
max_bot := 0
loop contours[0].Size{
x_array.push(contours[0][A_Index-1][1])
y_array.push(contours[0][A_Index-1][2])
if x_array[A_Index] > max_right{
max_right := x_array[A_Index]
max_right_y := contours[0][A_Index-1][2]
}
if x_array[A_Index] < max_left{
max_left := x_array[A_Index]
max_left_y := contours[0][A_Index-1][2]
}
if y_array[A_Index] > max_bot{
max_bot := y_array[A_Index]
max_bot_x := contours[0][A_Index-1][1]
}
if y_array[A_Index] < max_top{
max_top := y_array[A_Index]
max_top_x := contours[0][A_Index-1][1]
}
}
log.info('最右侧点:' max_right ' ' max_right_y)
log.info('最顶部点:' max_bot_x ' ' max_bot)
log.info('最左侧点:' max_left ' ' max_left_y)
log.info('最底部点:' max_top_x ' ' max_top)
cv.circle(img, [max_right, max_right_y], 5, [0, 0, 255], -1)
cv.circle(img, [max_bot_x, max_bot], 5, [0, 0, 255], -1)
cv.circle(img, [max_left, max_left_y], 5, [0, 0, 255], -1)
cv.circle(img, [max_top_x, max_top], 5, [0, 0, 255], -1)
cv.imshow("image1", img)
cv.waitKey()
cv.destroyAllWindows()
匹配形状
OpenCV带有函数 cv.matchShapes(),使我们能够比较两个形状或两个轮廓,并返回显示相似性的度量。结果越低,匹配越好。它是基于 **hu-moment 值计算的。文档中介绍了不同的测量方法。#Dllload lib
#DllLoad opencv_ahk.dll
#include <cv2>
#include <log>
SetWorkingDir A_ScriptDir
;初始化opencv模块
cv := ObjFromPtr(DllCall('opencv_ahk.dll\opencv_init', 'ptr', DllCall(A_AhkPath '\ahkGetApi', 'ptr'), 'cdecl ptr'))
img1 := cv.imread("image/111.png")
img2 := cv.imread("image/123.png")
cv.imshow("image", img2)
cv.cvtColor(img1, img_grey1 := cv.Mat(), cv2.CV_COLOR_BGR2GRAY)
cv.threshold(img_grey1, thresh_img1 := cv.MAT(), 127, 255, cv2.CV_THRESH_BINARY)
cv.findContours(thresh_img1, contours1 := cv.Vector_Vector_Point(), cv2.CV_RETR_TREE, cv2.CV_CHAIN_APPROX_SIMPLE)
cv.drawContours(img1, contours1, 0, [0, 0, 255], 3)
cv.cvtColor(img2, img_grey2 := cv.Mat(), cv2.CV_COLOR_BGR2GRAY)
cv.threshold(img_grey2, thresh_img2 := cv.MAT(), 127, 255, cv2.CV_THRESH_BINARY)
cv.findContours(thresh_img2, contours2 := cv.Vector_Vector_Point(), cv2.CV_RETR_TREE, cv2.CV_CHAIN_APPROX_SIMPLE)
cnt1 := contours1[0]
loop 3{
M := cv.moments(contours2[A_Index-1])
cx := Integer(M.m10/M.m00)
cy := Integer(M.m01/M.m00)
cv.circle(img2, [cx, cy], 3, [0, 0, 255], -1)
cnt2 := contours2[A_Index-1]
cv.drawContours(img2, contours2, A_Index-1, [0, 0, 255], 3)
ret := cv.matchShapes(cnt1, cnt2, 1, 0.0)
cv.putText(img2, ret, [cx, cy], 0, 0.5, [255, 0, 255], 1)
log.info(ret)
}
cv.imshow("image1", img1)
cv.imshow("image2", img2)
cv.waitKey()
cv.destroyAllWindows()
匹配自身结果=0.0
匹配旋转后的自身结果=0.008218551978777078
匹配矩形结果=0.3271618191421985
有错误请联系我改正!
天黑主项目地址:https://github.com/thqby/opencv_ahk
本系列所有贡献者(AutoHotKey中文社区群友)不分先后:天黑请闭眼,zzZ…,演好自己,僵尸,城西,Tebayaki。
标签:img,AHK,cv,opencv,v2,contours,轮廓,CV,255 From: https://www.cnblogs.com/ff888/p/16783806.html