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 Python 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言