iT邦幫忙

2022 iThome 鐵人賽

DAY 8
0

套件安裝

  1. scipy:pip install scipy
  2. imutils:pip install imutils


練習用圖

  1. edge.jpg

內容

  1. OpenCV邊緣偵測

    1.1 影像前處理

    def preprocessing(image):
        # 灰階
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        show_img('gray', gray)
    
        # 高斯濾波
        gaussian = cv2.GaussianBlur(gray, (9, 9), 0)
        show_img("gaussian", gaussian)
    
        # 開運算去除白色噪點
        kernel = np.ones((9, 9), np.uint8)
        open = cv2.morphologyEx(gaussian, cv2.MORPH_OPEN, 
                                kernel, iterations=3)
        show_img("open", open)
        return open
    

    1.2 Canny影像邊緣檢測

    def edge_detect(image):
        # 以canny邊緣檢測算法獲取目標(50~100為閾值)
        edged = cv2.Canny(image, 50, 100) #低於50刪除 高於100留下
        show_img("edged", edged)
    
        # 在邊緣圖像中尋找物體輪廓
        contours1, hierarchy = cv2.findContours(edged.copy(), 
                                                cv2.RETR_EXTERNAL,
                                                cv2.CHAIN_APPROX_SIMPLE)
    
        # 物體輪廓由左到右進行排序
        (contours2, _) = contours.sort_contours(contours1)
        # 若輪廓面積小於100,視為噪音濾除
        contours2 = [i for i in contours2 if cv2.contourArea(i) > 100]
    
        # 初始化pixelsPerMetric
        pixelsPerMetric = None
    
        return contours2, pixelsPerMetric
    

    1.3 計算輪廓長度高度,並畫出外切線框

    # 計算物體像素尺寸,並轉換為實際尺寸
    def measure(image, contours2, pixelsPerMetric):
        origin = image.copy()
        for i in contours2:
            # 計算出物品輪廓之外切線框
            box = cv2.minAreaRect(i)
            box = cv2.cv.BoxPoints(box) if imutils.is_cv2()  \
                  else cv2.boxPoints(box)
            box = np.array(box, dtype="int")
    
            # 左上角座標開始順時針排序,並畫出外切線框
            box = perspective.order_points(box)
            cv2.drawContours(origin, [box.astype("int")], -1, (0, 255, 0), 2)
    
            # 畫書外切線框端點
            for (x, y) in box:
                cv2.circle(origin, (int(x), int(y)), 5, (0, 0, 255), -1)
    
            #算出左上和右上端點的中心點、左下和右下端點的中心點
            (tl, tr, br, bl) = box
            (tltrX, tltrY) = midpoint(tl, tr)
            (blbrX, blbrY) = midpoint(bl, br)
    
            # 算出左上和左下端點的中心點、右上和右下端點的中心點
            (tlblX, tlblY) = midpoint(tl, bl)
            (trbrX, trbrY) = midpoint(tr, br)
    
            # 計算兩個中心點距離(dA:寬、dB:長)
            dA = dist.euclidean((tltrX, tltrY), (blbrX, blbrY))
            dB = dist.euclidean((tlblX, tlblY), (trbrX, trbrY))
    
            # 像素值與最左邊物品實際長度比值
            if pixelsPerMetric is None:
                pixelsPerMetric = dB / image_width
    
            # 計算目標的實際大小
            dimA = dA / pixelsPerMetric
            dimB = dB / pixelsPerMetric
    
            # 在圖片中標註物體尺寸
            cv2.putText(origin, "{:.1f}cm".format(dimB),
                        (int(tltrX - 15), int(tltrY - 10)), 
                        cv2.FONT_HERSHEY_SIMPLEX,
                        0.65, (255, 255, 255), 2)
            cv2.putText(origin, "{:.1f}cm".format(dimA),
                        (int(trbrX - 10), int(trbrY)),
                        cv2.FONT_HERSHEY_SIMPLEX,
                        0.65, (255, 255, 255), 2)
    
        show_img("Image", origin)
        return origin
    

    1.4 完整程式碼

    from scipy.spatial import distance as dist
    from imutils import perspective
    from imutils import contours
    import numpy as np
    import imutils
    import cv2
    
    # 讀取中文路徑圖檔(圖片讀取為BGR)
    def cv_imread(image_path):
        image = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), -1)
        image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)
        return image
    
    # 顯示圖檔
    def show_img(name, image):
        cv2.imshow(name, image)
        cv2.waitKey(0)
    
    # 圖片預處理
    def preprocessing(image):
        # 灰階
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        show_img('gray', gray)
    
        # 高斯濾波
        gaussian = cv2.GaussianBlur(gray, (9, 9), 0)
        show_img("gaussian", gaussian)
    
        # 開運算去除白色噪點
        kernel = np.ones((9, 9), np.uint8)
        open = cv2.morphologyEx(gaussian, cv2.MORPH_OPEN, 
                                kernel, iterations=3)
        show_img("open", open)
        return open
    
    # 計算物體中間點
    def midpoint(point1, point2):
        point = ((point1[0]+point2[0])/2, (point1[1]+point2[1])/2)
        return point
    
    def edge_detect(image):
        # 以canny邊緣檢測算法獲取目標(50~100為閾值)
        edged = cv2.Canny(image, 50, 100) #低於50刪除 高於100留下
        show_img("edged", edged)
    
        # 在邊緣圖像中尋找物體輪廓
        contours1, hierarchy = cv2.findContours(edged.copy(), 
                                                cv2.RETR_EXTERNAL,
                                                cv2.CHAIN_APPROX_SIMPLE)
        # 物體輪廓由左到右進行排序
        (contours2, _) = contours.sort_contours(contours1)
        # 若輪廓面積小於100,視為噪音濾除
        contours2 = [i for i in contours2 if cv2.contourArea(i) > 100]
    
        # 初始化 pixels per metric
        pixelsPerMetric = None
    
        return contours2, pixelsPerMetric
    
    # 計算物體像素尺寸,並轉換為實際尺寸
    def measure(image, contours2, pixelsPerMetric):
        origin = image.copy()
        for i in contours2:
            # 計算出物品輪廓之外切線框
            box = cv2.minAreaRect(i)
            box = cv2.cv.BoxPoints(box) if imutils.is_cv2() \
                  else cv2.boxPoints(box)
            box = np.array(box, dtype="int")
    
            # 左上角座標開始順時針排序,並畫出外切線框
            box = perspective.order_points(box)
            cv2.drawContours(origin, [box.astype("int")], -1, (0, 255, 0), 2)
    
            # 畫書外切線框端點
            for (x, y) in box:
                cv2.circle(origin, (int(x), int(y)), 5, (0, 0, 255), -1)
    
            # 算出左上和右上端點的中心點、左下和右下端點的中心點
            (tl, tr, br, bl) = box
            (tltrX, tltrY) = midpoint(tl, tr)
            (blbrX, blbrY) = midpoint(bl, br)
    
            # 算出左上和左下端點的中心點、右上和右下端點的中心點
            (tlblX, tlblY) = midpoint(tl, bl)
            (trbrX, trbrY) = midpoint(tr, br)
    
            # 計算兩個中心點距離(dA:寬、dB:長)
            dA = dist.euclidean((tltrX, tltrY), (blbrX, blbrY))
            dB = dist.euclidean((tlblX, tlblY), (trbrX, trbrY))
    
            # 像素值與最左邊物品實際長度比值
            if pixelsPerMetric is None:
                pixelsPerMetric = dB / image_width
    
            # 計算目標的實際大小
            dimA = dA / pixelsPerMetric
            dimB = dB / pixelsPerMetric
    
            # 在圖片中標註物體尺寸
            cv2.putText(origin, "{:.1f}cm".format(dimB),
                        (int(tltrX - 15), int(tltrY - 10)), 
                        cv2.FONT_HERSHEY_SIMPLEX,
                        0.65, (255, 255, 255), 2)
            cv2.putText(origin, "{:.1f}cm".format(dimA),
                        (int(trbrX - 10), int(trbrY)), 
                        cv2.FONT_HERSHEY_SIMPLEX,
                        0.65, (255, 255, 255), 2)
    
        show_img("Image", origin)
        return origin
    
    if __name__ == "__main__":
        image_path = './edge.jpg'
        image_width = 10.8
        image = cv_imread(image_path)
        show_img("image", image)
        gray = preprocessing(image)
        contours2, pixelsPerMetric = edge_detect(gray)
        measure(image, contours2, pixelsPerMetric)   
    

    1.5 執行結果

    • 讀取edge.jpg原圖

    • 將圖片轉換成灰階

    • 高斯模糊保留圖像主要輪廓,並降低躁點影響。

    • 開運算去除白色噪點(如:滑鼠上白色英文單字)

    • 以Canny邊緣檢測算法獲取待量測物體

    • 計算輪廓長度高度,並畫出外切線框


小結

  1. 下一站,我們前往「OpenCV物件偵測」。

讓我們繼續看下去...


參考資料

  1. Measuring size of objects in an image with OpenCV

上一篇
《第7天》OpenCV影像合成
下一篇
《第9天》OpenCV物件偵測
系列文
Object Detection and Image Processing with Python30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言