首頁 > 軟體

Python基於隨機取樣一至性實現擬合橢圓(優化版)

2022-11-17 14:00:57

上次版本如果在沒有找到輪廓或輪廓的點集數很小無法擬合橢圓或在RANSAC中尋找最優解時會死迴圈中,優化後的程式碼

import cv2
import os
import numpy as np
import matplotlib.pyplot as plt
import math
from Ransac_Process import RANSAC
 
 
 
 
def cul_area(x_mask, y_mask, r_circle, mask):
    mask_label = mask.copy()
    num_area = 0
    for xm in range(x_mask+r_circle-10, x_mask+r_circle+10):
        for ym in range(y_mask+r_circle-10, y_mask+r_circle+10):
            # print(mask[ym, xm])
            if (pow((xm-x_mask), 2) + pow((ym-y_mask), 2) - pow(r_circle,  2)) == 0 and mask[ym, xm][0] == 255:
                num_area += 1
                mask_label[ym, xm] = (0, 0, 255)
    cv2.imwrite('./test2/mask_label.png', mask_label)
    print(num_area)
    return num_area
 
def mainFigure(img, point0):
 
    point_center = []
    # cv2.imwrite('./test2/img_source.png', img)
    img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    # cv2.imwrite('./test2/img_hsv.png', img_hsv)
    w, h = img.shape[1], img.shape[0]
    w_hsv, h_hsv = img_hsv.shape[1], img_hsv.shape[0]
    for i_hsv in range(w_hsv):
        for j_hsv in range(h_hsv):
            if img_hsv[j_hsv, i_hsv][0] < 200 and img_hsv[j_hsv, i_hsv][1] < 130 and img_hsv[j_hsv, i_hsv][2] > 120:
                # if hsv[j_hsv, i_hsv][0] < 100 and hsv[j_hsv, i_hsv][1] < 200 and hsv[j_hsv, i_hsv][2] > 80:
                img_hsv[j_hsv, i_hsv] = 255, 255, 255
            else:
                img_hsv[j_hsv, i_hsv] = 0, 0, 0
    # cv2.imwrite('./test2/img_hsvhb.png', img_hsv)
    # cv2.imshow("hsv", img_hsv)
    # cv2.waitKey()
 
    # 灰度化處理影象
    grayImage = cv2.cvtColor(img_hsv, cv2.COLOR_BGR2GRAY)
    # mask = np.zeros((grayImage.shape[0], grayImage.shape[1]), np.uint8)
    # mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
    # cv2.imwrite('./mask.png', mask)
 
    # 嘗試尋找輪廓
    contours, hierarchy = cv2.findContours(grayImage, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
 
    # 合併輪廓
    if len(contours) > 1:
        contours_merge = np.vstack([contours[0], contours[1]])
        for i in range(2, len(contours)):
            contours_merge = np.vstack([contours_merge, contours[i]])
        # cv2.drawContours(img, contours_merge, -1, (0, 255, 255), 1)
        # # cv2.imwrite('./test2/img_res.png', img)
        # cv2.imshow("contours_merge", img)
        # cv2.waitKey()
    elif len(contours) == 1:
        contours_merge = contours[0]
    else:
        print("No contours!")
        return 0,0
 
 
    # RANSAC擬合
    points_data = np.reshape(contours_merge, (-1, 2))  # ellipse edge points set
 
    # print("points_data", len(points_data))
    # 2.Ransac fit ellipse param
    # Ransac = RANSAC(data=points_data, threshold=0.1, P=.99, S=.5, N=10)
    Ransac = RANSAC(data=points_data, threshold=0.5, P=.98, S=.6, N=10)
 
    ellipse_values = Ransac.execute_ransac()
    # 檢測到輪廓裡數量太少(<5)則無法擬合橢圓
    if ellipse_values is None:
        return 0,0
 
    (X, Y), (LAxis, SAxis), Angle = ellipse_values
    # print( (X, Y), (LAxis, SAxis))
    # 擬合圓
    cv2.ellipse(img, ((X, Y), (LAxis, SAxis), Angle), (0, 0, 255), 1, cv2.LINE_AA)  # 畫圓
    cv2.circle(img, (int(X), int(Y)), 3, (0, 0, 255), -1)  # 畫圓心
    point_center.append(int(X))
    point_center.append(int(Y))
 
    # 直接擬合
    # rrt = cv2.fitEllipse(contours_merge)  # x, y)代表橢圓中心點的位置(a, b)代表長短軸長度,應注意a、b為長短軸的直徑,而非半徑,angle 代表了中心旋轉的角度
    # # print("rrt", rrt)
    # cv2.ellipse(img, rrt, (255, 0, 0), 1, cv2.LINE_AA)  # 畫圓
    # x, y = rrt[0]
    # cv2.circle(img, (int(x), int(y)), 3, (255, 0, 0), -1)  # 畫圓心
    # point_center.append(int(x))
    # point_center.append(int(y))
    # # print("no",(x,y))
    #
    # # 兩種方法座標的距離
    # dis_two_method = math.sqrt(math.pow(X - x, 2) + math.pow(Y - y, 2))
    # print("兩種方法座標的距離", dis_two_method)
 
    cv2.imshow("fit circle", img)
    cv2.waitKey(3)
    # cv2.imwrite("./test2/fitcircle.png", img)
 
 
    return point_center[0], point_center[1]
 
if __name__ == "__main__":
 
 
    # 測試所有圖片
    mainFolder = "./Images/save_img"
    myFolders = os.listdir(mainFolder)
    print("myFolders", myFolders)
    myImageList = []
    path = ''
    for folder in myFolders:
        path = mainFolder + '/' + folder
 
        myImageList = os.listdir(path)
        # print(myImageList)
        # print(f'Tatal images deteted is  {len(myImageList)}')
 
        i = 0
        for imagN in myImageList:
            curImg = cv2.imread(f'{path}/{imagN}')
            # images.append(curImg)
            print(f'{path}/{imagN}')
            point0 = [0, 0]
            cir_x, cir_y = mainFigure(curImg, point0)
            print("This is ", i, "圓心為",(cir_x, cir_y))
            i += 1
 
    # # 測試2
    # for i in range(1,6):
    #     imageName = "s"
    #     imageName += str(i)
    #     path = './Images/danHoles/' + imageName + '.png'
    #     print(path)
    #     img = cv2.imread(path)
    #     point0 = [0, 0]
    #     cir_x, cir_y = mainFigure(img, point0)
 
    # # 測試1
    # img = cv2.imread('./Images/danHoles/s6.png')
    # point0 = [0, 0]
    # cir_x, cir_y = mainFigure(img, point0)

Ransac_Process.py

import cv2
import math
import random
import numpy as np
from numpy.linalg import inv, svd, det
import time
 
class RANSAC:
    def __init__(self, data, threshold, P, S, N):
        self.point_data = data  # 橢圓輪廓點集
        self.length = len(self.point_data)  # 橢圓輪廓點集長度
        self.error_threshold = threshold  # 模型評估誤差容忍閥值
 
        self.N = N  # 隨機取樣數
        self.S = S  # 設定的內點比例
        self.P = P  # 採得N點去計算的正確模型概率
        self.max_inliers = self.length * self.S  # 設定最大內點閥值
        self.items = 8
 
        self.count = 0  # 內點計數器
        self.best_model = ((0, 0), (1e-6, 1e-6), 0)  # 橢圓模型記憶體
 
    def random_sampling(self, n):
        # 這個部分有修改的空間,這樣迴圈次數太多了,可以看看別人改進的ransac擬合橢圓的論文
        """隨機取n個資料點"""
        all_point = self.point_data
 
        if len(all_point) >= n:
            select_point = np.asarray(random.sample(list(all_point), n))
            return select_point
        else:
            print("輪廓點數太少,數量為", len(all_point))
            return None
 
    def Geometric2Conic(self, ellipse):
        # 這個部分參考了GitHub中的一位大佬的,但是時間太久,忘記哪個人的了
        """計算橢圓方程係數"""
        # Ax ^ 2 + Bxy + Cy ^ 2 + Dx + Ey + F
        (x0, y0), (bb, aa), phi_b_deg = ellipse
 
        a, b = aa / 2, bb / 2  # Semimajor and semiminor axes
        phi_b_rad = phi_b_deg * np.pi / 180.0  # Convert phi_b from deg to rad
        ax, ay = -np.sin(phi_b_rad), np.cos(phi_b_rad)  # Major axis unit vector
 
        # Useful intermediates
        a2 = a * a
        b2 = b * b
 
        # Conic parameters
        if a2 > 0 and b2 > 0:
            A = ax * ax / a2 + ay * ay / b2
            B = 2 * ax * ay / a2 - 2 * ax * ay / b2
            C = ay * ay / a2 + ax * ax / b2
            D = (-2 * ax * ay * y0 - 2 * ax * ax * x0) / a2 + (2 * ax * ay * y0 - 2 * ay * ay * x0) / b2
            E = (-2 * ax * ay * x0 - 2 * ay * ay * y0) / a2 + (2 * ax * ay * x0 - 2 * ax * ax * y0) / b2
            F = (2 * ax * ay * x0 * y0 + ax * ax * x0 * x0 + ay * ay * y0 * y0) / a2 + 
                (-2 * ax * ay * x0 * y0 + ay * ay * x0 * x0 + ax * ax * y0 * y0) / b2 - 1
        else:
            # Tiny dummy circle - response to a2 or b2 == 0 overflow warnings
            A, B, C, D, E, F = (1, 0, 1, 0, 0, -1e-6)
 
        # Compose conic parameter array
        conic = np.array((A, B, C, D, E, F))
        return conic
 
    def eval_model(self, ellipse):
        # 這個地方也有很大修改空間,判斷是否內點的條件在很多改進的ransac論文中有說明,可以多看點論文
        """評估橢圓模型,統計內點個數"""
        # this an ellipse ?
        a, b, c, d, e, f = self.Geometric2Conic(ellipse)
        E = 4 * a * c - b * b
        if E <= 0:
            # print('this is not an ellipse')
            return 0, 0
 
        #  which long axis ?
        (x, y), (LAxis, SAxis), Angle = ellipse
        LAxis, SAxis = LAxis / 2, SAxis / 2
        if SAxis > LAxis:
            temp = SAxis
            SAxis = LAxis
            LAxis = temp
 
        # calculate focus
        Axis = math.sqrt(LAxis * LAxis - SAxis * SAxis)
        f1_x = x - Axis * math.cos(Angle * math.pi / 180)
        f1_y = y - Axis * math.sin(Angle * math.pi / 180)
        f2_x = x + Axis * math.cos(Angle * math.pi / 180)
        f2_y = y + Axis * math.sin(Angle * math.pi / 180)
 
        # identify inliers points
        f1, f2 = np.array([f1_x, f1_y]), np.array([f2_x, f2_y])
        f1_distance = np.square(self.point_data - f1)
        f2_distance = np.square(self.point_data - f2)
        all_distance = np.sqrt(f1_distance[:, 0] + f1_distance[:, 1]) + np.sqrt(f2_distance[:, 0] + f2_distance[:, 1])
 
        Z = np.abs(2 * LAxis - all_distance)
        delta = math.sqrt(np.sum((Z - np.mean(Z)) ** 2) / len(Z))
 
        # Update inliers set
        inliers = np.nonzero(Z < 0.8 * delta)[0]
        inlier_pnts = self.point_data[inliers]
 
        return len(inlier_pnts), inlier_pnts
 
    def execute_ransac(self):
        Time_start = time.time()
        while math.ceil(self.items):
            # print(self.max_inliers)
 
            # 1.select N points at random
            select_points = self.random_sampling(self.N)
            # 當從輪廓中採集的點不夠擬合橢圓時跳出迴圈
            if select_points is None or len(select_points) < 5:
                # print(select_points)
                return None
            else:
                # 2.fitting N ellipse points
                ellipse = cv2.fitEllipse(select_points)
 
                # 3.assess model and calculate inliers points
                inliers_count, inliers_set = self.eval_model(ellipse)
 
                # 4.number of new inliers points more than number of old inliers points ?
                if inliers_count > self.count:
                    if len(inliers_set) > 4:
                        ellipse_ = cv2.fitEllipse(inliers_set)  # fitting ellipse for inliers points
                        self.count = inliers_count  # Update inliers set
                        self.best_model = ellipse_  # Update best ellipse
                        # print("self.count", self.count)
 
                    # 5.number of inliers points reach the expected value
                    if self.count > self.max_inliers:
                        # print('the number of inliers: ', self.count)
                        break
 
                    # Update items
                    # print(math.log(1 - pow(inliers_count / self.length, self.N)))
                    if math.log(1 - pow(inliers_count / self.length, self.N)) != 0:
                        self.items = math.log(1 - self.P) / math.log(1 - pow(inliers_count / self.length, self.N))
 
            Time_end = time.time()
            # print(Time_end - Time_start )
            if Time_end - Time_start >= 4:
                # print("time is too long")
                break
 
        return self.best_model
 
 
if __name__ == '__main__':
 
 
    # 1.find ellipse edge line
    contours, hierarchy = cv2.findContours(grayImage, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
 
    # 2.Ransac fit ellipse param
    points_data = np.reshape(contours, (-1, 2))  # ellipse edge points set
    Ransac = RANSAC(data=points_data, threshold=0.5, P=.99, S=.618, N=10)
    (X, Y), (LAxis, SAxis), Angle = Ransac.execute_ransac()

以上就是Python基於隨機取樣一至性實現擬合橢圓(優化版)的詳細內容,更多關於Python擬合橢圓的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com