(七)OpenCV-Python學習—幾何形狀擬合


  通過閾值分割提取圖像中的目標物體前景,或者邊緣提取目標物體的輪廓,在這些前景中可以尋找感興趣的幾何形狀,如直線,圓,三角形,矩形等。

1. 點集的最小外包

  opencv中提供了擬合像素點的最小外包旋轉矩形,最小外包直立矩形,最小外包圓,最小外包三角形和最小凸包,其對應函數使用如下:

最小外包旋轉矩形

  opencv 中函數minAreaRect()計算坐標點集的最小外包面積矩形,返回矩形的中心坐標點,寬和高,以及旋轉角度,一般再通過函數boxPoints()能活得矩形框的四個頂點坐標,兩個函數的參數和返回值說明如下:

center, size, angle = cv2.minAreaRect(points)
points: 坐標點array,數據類型為數據類型為int32或者float32
        points = np.array([[154, 154],[253, 171], [154, 176],[248, 204]], np.int32)
center: 旋轉矩形的中心點
size: 矩形的寬,高; 結果可能為(h, w)或者(w, h)
angle: 相對於x軸正方向的旋轉角度,順時針旋轉為正數,逆時針旋轉為負數,在(-90, 0)之間

 vertices = cv2.boxPoints(box)
    box: 為minAreaRect()的返回值
      box = (center, size, angle), 注意為一個元組,最外面括號不能少
      box = ((201.00001525878906, 179.00001525878906),
          (33.37017059326172,101.1060562133789), -80.25636291503906)
 vertices:返回矩形的四個頂點坐標
    vertices=[[248.00002 204.00002],
    [148.35243 186.88881],
    [154.00002 154.00002],
    [253.6476 171.11122]]

  返回值中旋轉角度值得注意一下:

    1. 旋轉角度θ是水平軸(x軸)逆時針旋轉,與碰到的矩形的第一條邊的夾角。並且這個邊的邊長是width,另一條邊邊長是height。也就是說,在這里,width與height不是按照長短來定義的。
    2. 在opencv中,坐標系原點在左上角,相對於x軸,逆時針旋轉角度為負,順時針旋轉角度為正。所以,θ∈(-90度,0]。

  下面為minAreaRect() 的使用代碼和示例:

#coding:utf-8

import cv2
import numpy as np

def draw_rect(img_file, points):
    img = cv2.imread(img_file)
    center, size, angle = cv2.minAreaRect(points)   #中心點坐標,尺寸,旋轉角度
    print(center, size, angle)
    vertices= cv2.boxPoints((center, size, angle))
    print(vertices)

    for i in range(4):
        point1 = vertices[i, :]
        point2 = vertices[(i+1)%4, :]
        cv2.line(img, tuple(point1), tuple(point2), (0, 0, 255), 2)

    cv2.imshow("img", img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == "__main__":
    img_file = r"D:\data\timg.jpg"

    points1 = np.array([[154, 154],[253, 171], [154, 176],[248, 204]], np.int32)  # 數據類型為int32或者float32
    draw_rect(img_file, points1)


    # points2 = np.array([[148, 171], [247, 153], [253, 186], [154, 204]], np.int32)
    # draw_rect(img_file, points2)
minAreaRect()

   注意對比上圖中左邊紅色框和右邊綠色框返回的結果,中心坐標差不多,但是size和angle都不一樣。

最小外包直立矩形

  boundingRect()函數返回坐標點集的最小外包直立矩形,平行於坐標軸的矩形,沒有旋轉角, 其參數如下:

rect = cv2.boundingRect(points)
    points: 坐標點array,數據類型為數據類型為int32或者float32
        points = np.array([[154, 154],[253, 171], [154, 176],[248, 204]], np.int32)
    rect: 矩形的左上角坐標,寬,高 (x, y, w, h)
        rect = (154, 154, 100, 51)    

  boundingRect() 使用代碼和示例如下:

#coding:utf-8

import cv2
import numpy as np

img_file = r"D:\data\timg.jpg"
img = cv2.imread(img_file)
points1 = np.array([[154, 154],[253, 171], [154, 176],[248, 204]], np.int32)  # 數據類型為int32或者float32
rect = cv2.boundingRect(points1)
print(rect)
x, y, w, h = rect

cv2.rectangle(img,(x, y), (x+w, y+h), (0, 0, 255), 0)

cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
boundingRect()

 

最小外包圓

  minEnclosingCircle() 函數返回坐標點集的最小外包圓,其使用參數如下:

center, radius=cv2.minEnclosingCircle(points)
    points: 坐標點array,數據類型為數據類型為int32或者float32
        points = np.array([[154, 154],[253, 171], [154, 176],[248, 204]], np.int32)
    center:圓心坐標, 如(201.0, 179.0)
    radius: 圓直徑, 如53.23542404174805

  minEnclosingCircle() 代碼使用示例如下:

#coding:utf-8

import cv2
import numpy as np

img_file = r"D:\data\timg.jpg"
img = cv2.imread(img_file)
points1 = np.array([[154, 154],[253, 171], [154, 176],[248, 204]], np.int32)  # 數據類型為int32或者float32
center, radius = cv2.minEnclosingCircle(points1)
print(center, radius)

cv2.circle(img, (int(center[0]), int(center[1])), int(radius), (0, 0, 255), 2)  # 傳入center和radius需要為整形

cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
minEnclosingCircle()

最小外包三角形

  minEnclosingTriangle()函數返回坐標點集的最小外三角形,其使用參數如下:

area, triangle=cv.minEnclosingTriangle(points)
    points: 坐標點array,數據類型為數據類型為int32或者float32,注意shape必須為n*1*2
        points = np.array([[[154 154]],[[253 171]],[[154 176]],[[248 204]]], np.int32)
    area: 三角形的頂點
    triangle: 三角形的三個頂點

  minEnclosingTriangle() 代碼使用示例如下:

#coding:utf-8

import cv2
import numpy as np

img_file = r"D:\data\timg.jpg"
img = cv2.imread(img_file)
points = np.array([[154, 154],[253, 171], [154, 176],[248, 204]], np.int32)  # 數據類型為int32或者float32
points = points.reshape(4, 1, 2)  #注意shape必須為n*1*2
print(points)
area, triangle = cv2.minEnclosingTriangle(points)
print(area, triangle, triangle.shape)
for i in range(3):
    point1 = triangle[i, 0, :]
    point2 = triangle[(i+1)%3, 0, :]
    print(point1)
    cv2.line(img, tuple(point1), tuple(point2), (0, 0, 255), 2)

cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
minEnclosingTriangle()

最小凸包

  convexHull()函數返回坐標點集的最小凸包,凸包實際上就是把坐標點集最外層的坐標點連接起來構成的凸多邊形。其使用參數如下:

hull=cv.convexHull(points, clockwise=False, returnPoints=True)
    points: 坐標點array,數據類型為數據類型為int32或者float32
        points =np.array([[154, 154],[187, 150],[253, 171], [190, 197],
                   [259, 187],[154, 176],[248, 204], [141, 164]], np.int32)
    clockwise: 為True時,返回的凸包頂點順時針方向排列,否則逆時針排列,默認為False
    returnPoints:為True時,返回凸包的頂點坐標,否則返回坐標點在點集中的index,默認為True

  convexHull()代碼使用示例如下:

import cv2
import numpy as np

img_file = r"D:\data\timg.jpg"
img = cv2.imread(img_file)
points = np.array([[154, 154],[187, 150],[253, 171], [190, 197],
                   [259, 187],[154, 176],[248, 204], [141, 164]], np.int32)  # 數據類型為int32或者float32
hull = cv2.convexHull(points)
n = hull.shape[0]
for i in range(n):
    point1 = hull[i, 0, :]
    point2 = hull[(i+1)%n, 0, :]
    cv2.line(img, tuple(point1), tuple(point2), (0,0,255), 2)


cv2.imshow("img", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
convexHull()

2 霍夫直線檢測

對於標准霍夫直線檢測的理解,主要在於三點:

  • 平面坐標系中的一條直線,可以轉換為極坐標系中的一個坐標點

  • 平面坐標系中過一點有無數條直線,這些直線在極坐標系中對應無數個坐標點,且這些點共同形成了極坐標系中的一條曲線

  • 判斷平面坐標系中多個點是否在一條直線上,則對應極坐標系中,判斷這線點對應的曲線是否相較於一點

 投票器

  實際在判斷霍夫空間中曲線的交點時,采用的時將曲線進行離散化,制定一個投票器(投票器中每一點對應一條直線),將邊緣二值圖中的像素點分別映射到投票器中,投票器中投票數較多的點則對應直線交點

  自己實現霍夫直線檢測代碼和對應結果如下:

#coding:utf-8
import numpy as np
import math
import cv2

def HTline(image, stepTheta=1, stepRho=1):
    rows, cols = image.shape
    L = round(math.sqrt(pow(rows-1, 2.0)+pow(cols-1, 2.0))) +1
    numtheta = int(180/stepTheta)
    numRh0 = int(2*L/stepRho + 1)   # L到2L+1之間
    accumulator = np.zeros((numRh0, numtheta), np.int32)
    accuDict = {}  # 每條直線上點的集合
    for i in range(numRh0):
        for j in range(numtheta):
            accuDict[(i, j)] = []
    for y in range(rows):
        for x in range(cols):
            if image[y][x] == 255:
                for m in range(numtheta):
                    rho = x*math.cos(stepTheta*m*math.pi/180) + y*math.sin(stepTheta*m*math.pi/180)
                    n = int(round(L+rho)/stepRho)
                    accumulator[n, m] += 1
                    accuDict[(n, m)].append((x, y))
    return accumulator, accuDict

if __name__ == "__main__":
    img_path = r"D:\data\building.jpg"
    img = cv2.imread(img_path)
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_edge = cv2.Canny(img_gray, threshold1=50, threshold2=200, apertureSize=3)  # 返回edge是0和255組成的二值圖

    accumulator, accudict = HTline(img_edge, 1, 1)
    voteThresh = 200
    rows, cols = accumulator.shape
    for r in range(rows):
        for c in range(cols):
            if accumulator[r, c] > voteThresh:
                points = accudict[(r, c)]
                cv2.line(img, points[0], points[-1], (0, 0, 255), 2)

    cv2.namedWindow("img", cv2.WINDOW_NORMAL)
    cv2.imshow("img", img)
    cv2.namedWindow("img_edge", cv2.WINDOW_NORMAL)
    cv2.imshow("img_edge", img_edge)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
python實現HoughLines

HoughLines()和HoughLinesP()

  opencv中提供了HoughLines()和HoughLinesP()兩個函數進行霍夫直線檢測:

  HoughLines:標准霍夫變換,多尺度霍夫變換; 其對應的參數和含義如下:

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

    image: 單通道的灰度圖或二值圖
    rho: 距離步長,單位為像素(上述投票器中縱軸)
    theta: 角度步長,單位為弧度 (上述投票器中橫軸)
    threshold: 投票器中計數閾值,當投票器中某個點的計數超過閾值,則認為該點對應圖像中的一條直線,也可以理解為圖像空間空中一條直線上的像素點個數閾值(如設為5,則表示這條直線至少包括5個像素點)
    srn:默認為0, 用於在多尺度霍夫變換中作為參數rho的除數,rho=rho/srn
    stn:默認值為0,用於在多尺度霍夫變換中作為參數theta的除數,theta=theta/stn
        (如果srn和stn同時為0,就表示HoughLines函數執行標准霍夫變換,否則就是執行多尺度霍夫變換)
    min_theta: 默認為0,表示直線與坐標軸x軸的最小夾角為0
    max_theta:默認為CV_PI,表示直線與坐標軸x軸的最大夾角為180度
    
lines:返回的直線集合,每一條直線表示為 (ρ,θ) or (ρ,θ,votes),ρ表示(0,0)像素點到該直線的距離,θ表示直線與坐標軸x軸的夾角,votes表示投票器中計數值

  HoughLines()使用代碼及其對應結果示例如下:

import cv2
import math
img_path = r"D:\data\building.jpg"
img = cv2.imread(img_path)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_edge = cv2.Canny(img_gray, threshold1=50, threshold2=200, apertureSize=3)
lines = cv2.HoughLines(img_edge, rho=1, theta=math.pi/180, threshold=200)
print(lines)
# [[[ 355.           1.1693705]]
#  [[ 454.           1.37881  ]]
for line in lines:
    rho = line[0][0]
    theta = line[0][1]
    a = math.cos(theta)
    b = math.sin(theta)
    x0 = rho*a
    y0 = rho*b  # 原點到直線的垂線,與直線的交點
    x1 = int(x0+1000*(-b))   # 取1000長度,在(x0, y0)上下從直線中各取一點 (由於圖像坐標系y軸反向,所以為-b)
    y1 = int(y0+1000*a)
    x2 = int(x0 - 1000 * (-b))
    y2 = int(y0 - 1000 * a)
    cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)

cv2.namedWindow("img", cv2.WINDOW_NORMAL)
cv2.imshow("img", img)
cv2.namedWindow("img_edge", cv2.WINDOW_NORMAL)
cv2.imshow("img_edge", img_edge)
cv2.waitKey(0)
cv2.destroyAllWindows()
HoughLines()使用

  HoughLinesP:概率霍夫直線檢測(可以檢測線段)

  標准霍夫直線檢測比較耗時和內存,概率霍夫直線檢測不再是遍歷邊緣二值圖中的每一個像素點,而是從二值圖中隨機選取像素點。其對應的參數和含義如下:

lines=cv.HoughLinesP(image, rho, theta, threshold, minLineLength=0, maxLineGap=0)
    rho: 距離步長,單位為像素
    theta: 角度步長,單位為弧度
    threshold: 投票器中計數閾值,當投票器中某個點的計數超過閾值,則認為該點對應圖像中的一條直線,也可以理解為圖像空間空中一條直線上的像素點個數閾值(如設為5,則表示這條直線至少包括5個像素點)
    minLineLength: 默認值為0,表示最小線段長度閾值(小於該閾值的會過濾掉)
    maxLineGap: 默認值為0,表示直線斷裂的最大間隔距離閾值。即如果有兩條線段是在一條直線上,但它們之間有間隙,那么如果這個間隔距離小於該值,則被認為是一條線段,否則認為是兩條線段。
    
lines:返回的直線集合,每一條直線表示為(x1,y1,x2,y2),(x1,y1)和(x2,y2)表示線段的兩個端點坐標值

  HoughLinesP()使用示例代碼和對應效果如下:

import cv2
import math
img_path = r"D:\data\building.jpg"
img = cv2.imread(img_path)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_edge = cv2.Canny(img_gray, threshold1=50, threshold2=200, apertureSize=3)
lines = cv2.HoughLinesP(img_edge, rho=1, theta=math.pi/180, threshold=80, minLineLength=30, maxLineGap=10)
print(lines)
# [[[147 323 842  28]]
#  [[763 314 867 294]]
for line in lines:
    cv2.line(img, tuple(line[0][:2]), tuple(line[0][2:]), (0, 0, 255), 2)

cv2.namedWindow("img", cv2.WINDOW_NORMAL)
cv2.imshow("img", img)
cv2.namedWindow("img_edge", cv2.WINDOW_NORMAL)
cv2.imshow("img_edge", img_edge)
cv2.waitKey(0)
cv2.destroyAllWindows()
HoughLinesP()使用

 

3 霍夫圓檢測

標准霍夫圓檢測

  和標准霍夫直線檢測一樣,標准霍夫圓檢測也有一個坐標軸轉換過程,其理解主要在於三點:

  • xy坐標軸中過一個點,且半徑為1所有圓的圓心,對應ab坐標軸中一個圓

  • xoy平面內過一點,且半徑為任意r的所有圓,對應abr坐標軸中一個圓錐

  • 求xoy平面內多個點是否共圓,相當於判斷abr坐標軸中多個圓錐的錐面是否相交

 

基於梯度的霍夫圓檢測

  標准霍夫圓檢測計算量大,比較耗時,基於梯度的霍夫圓檢測是對其的一種改進, 其計算步驟如下:

  1. 得到像素點的梯度方向(邊緣提取過程中可以計算得到

  2. 畫出像素點梯度方向的法線,並計算這些法線的交點作為圓心,用投票器記錄每個交點對應半徑的計數

  3. 投票器中選擇計數最高的半徑,並選擇對應的圓心

   opencv提供了基於梯度的霍夫圓檢測函數HoughCircles

circles    =cv2.HoughCircles(image, method, dp, minDist, param1=100, param2=100, minRadius=0, maxRadius=0)
    image: 單通道灰度圖
    method: 霍夫圓檢測方法,包括HOUGH_GRADIENT和HOUGH_GRADIENT_ALT
    dp: 圖片的分辨率和投票器的分辨率比值,dp=2表示投票器的寬高為圖片的一半;對於HOUGH_GRADIENT_ALT方法,推薦dp=1.5
    minDist: 圓心之間的最小距離,距離太小時會產生很多相交的圓,距離太大時會漏掉正確的圓
    param1:canny邊緣檢測雙閾值中的高閾值,低閾值默認是其一半
    param2: 對於HOUGH_GRADIENT方法,投票器計數閾值(基於圓心和半徑的投票器);對於HOUGH_GRADIENT_ALT方法,表示接近一個圓的程度,param2越趨近1,擬合形狀越趨近於圓,推薦param2=0.9
    minRadius:需要檢測圓的最小半徑
    maxRadius:需要檢測圓的最大半徑,maxRadius<=0時采用圖片的最大尺寸(長或寬)
    (HOUGH_GRADIENT_ALT是對HOUGH_GRADIENT的改進,提取圓效果更好)
    
circles: 返回N個圓的信息,存儲在1*N*3,每個圓組成為(x, y, radius),其中(x, y)為圓心,radius為半徑

  HoughCircles()函數使用代碼和結果如下:

#coding:utf-8
import cv2
# img_path = r"D:\data\boat.png"
img_path = r"D:\data\3317881690.jpg"
img = cv2.imread(img_path)
img2 = img.copy()
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, 2, 30, param1=200, param2=100, minRadius=20)
print(circles)
for circle in circles[0]:
    center_x, center_y, radius = circle
    cv2.circle(img, (center_x, center_y), int(radius),(0, 0, 255), 2)

circles2 = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT_ALT, 2, 30, param1=300, param2=0.85, minRadius=20)
print(circles2)

for circle in circles2[0]:
    center_x, center_y, radius = circle
    cv2.circle(img2, (center_x, center_y), int(radius),(0, 0, 255), 2)

# cv2.namedWindow("img", cv2.WINDOW_NORMAL)
cv2.imshow("img", img)
# cv2.namedWindow("img2", cv2.WINDOW_NORMAL)
cv2.imshow("img2", img2)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.HoughCircles()

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM