OpenCV系列 - 作者:cc   原链接:https://www.cnblogs.com/ff888/p/16783806.html

什么是轮廓?

轮廓可以简单地解释为连接具有相同颜色或强度的所有连续点(沿边界)的曲线。轮廓是用于形状分析以及对象检测和识别的有用工具。
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)

 

轮廓近似法

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/1470636
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("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**中给出。
  1. area := cv.contourArea(cnt)
  2. MsgBox "轮廓面积: " area

3.轮廓周长

也称为弧长。可以使用 cv.arcLength() 函数找到它。第二个参数指定形状是闭合轮廓(如果通过True)还是曲线。
  1. perimeter := cv.arcLength(cnt, ComValue(0xB, -1)) ;第二个参数给了true
  2. MsgBox perimeter

4.轮廓近似

根据我们指定的精度,它可以将轮廓形状近似为顶点数量较少的其他形状。它是Douglas-Peucker算法的实现。

在数字化时,要对曲线进行采样,即在曲线上取有限个点,将其变为折线,并且能够在一定程度上保持原有的形状。

经典的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()** 函数检查曲线是否存在凸凹缺陷并对其进行校正。一般而言,凸曲线是始终凸出或至少平坦的曲线。如果在内部凸出,则称为凸度缺陷。

  1. 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
凸缺陷返回一个数组,每一行包含值是起点,终点,最远的点,到最远点的近似距离,返回的前三个点都是轮廓索引。
  1. 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。
  1. k := cv.isContourConvex(cnt)
  2. 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 类型。
  1. ellipse := cv.fitEllipse(cnt)
  2. 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.极端点

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

 

dbgba优化整理OpenCV系列示例打包下载20240513:

 

天黑版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], &center, &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。

声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。