Chi tiết cách đo kích thước vật thể bằng OpenCV thuần

Chào tuần mới anh Mì AI, hôm nay chúng ta sẽ sử dụng OpenCV thuần với Python để thử đo kích thước các vật thể trong ảnh nhé.

Việc đo kích thước này mình cũng nói trước luôn mấy điểm:

  • Thứ nhất, việc đo chỉ mang tính chất tham khảo, không thể nào chính xác 100% như mang thước ra đo hoặc dùng các máy đo chuyên dụng
  • Thứ hai, do ở đây mình sử dụng một ảnh bất kì làm sample, không cân chỉnh camera một cách bài bản nên có thể kích thước sẽ ảnh hưởng bởi góc nghiêng của camera.

Tuy nhiên, với mục đích học tập thì hoàn toàn chấp nhận được. Ngoài ra mình cũng đã có 1 bài đo khoảng cách tương tự các bạn cũng có thể đọc để tham khảo nhé: tại đây.

Okie con dê! Bắt đầu tìm hiểu nào!

Phần 1 – Làm sao để đo kích thước vật thể trong ảnh?

Rõ ràng với logic thông thường thì muốn đo kích thước vật thể trong ảnh ta phải biết được các yếu tố như: Khoảng cách từ camera đến vật khi chụp, Tiêu cự của camera, góc chụp…..rất phức tạp như hình dưới:

Nguồn: Tại đây

Tuy nhiên với anh em Mì ngại học toán thì mình sẽ có một cách đơn giản hơn như sau: (bạn nào thích làm phức tạp có thể nghiên cứu cách căn chỉnh camera tại đây).

  • Đầu tiên ta chọn một vật nào đó ta biết kích thước gọi là “Vật tham chiếu“. Ta nên chọn một viên bi tròn hay vật gì đó hình tròn để dễ dàng hơn trong việc tính toán. Giả sử viên bi có đường kính thật $Dr = 2cm = 20mm$
  • Tiếp theo ta đặt Vật tham chiếu đó vào ảnh cùng với các Vật muốn đo kích thước và chụp một tấm.
  • Sau đó, Giả sử ta đo được đường kính trong ảnh của Vật tham chiếu là $Dc = 100 pixel$, ta suy ra kích thước thật của 1 pixel là 20/100 = 0.2mm (goi là số $P$)
  • Bước cuối cung, ta đo kích thước trong ảnh Vật muốn đo bằng pixel, sau đó nhân với $P$ là sẽ ra kích thước thật của vật.

Các bạn đã nắm được tinh thần rồi chứ? Nếu còn vướng mắc thì các bạn cứ post lên Group trao đổi, chia sẻ: https://facebook.com/groups/miaigroup để thảo luận nhé.

Phần 2 – Triển khai bài toán

Việc đầu tiên là ta phải tìm cách đo được kích thước bằng pixel của các vật trong ảnh, đó là yếu tố tiên quyết cho bài toán này.

Giả sử ta có một tấm ảnh như sau và ở đây Vật tham chiếu là đồng xu 1 Rufiuaa của Maldives (kỷ niệm chuyến đi năm ngoái của mình kaka) và Vật cần đo là chiếc USB bên cạnh.

đo kích thước vật thể

Ta đã biết đồng xu này có kích thước thật là 2cm = 20mm. Bây giơ chúng ta sẽ cùng nhau thực hiện các bước nhé.

Tiền xử lý và tìm cạnh

Việc đầu tiên là phải thực hiện chuyển về ảnh xám và tìm cạnh để bước sau tìm contour:

    # Đọc file ảnh
    image = cv2.imread(filename)
    # Chuyển thành ảnh xám
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    # Làm mờ ảnh
    gray = cv2.GaussianBlur(gray, (blur_kernel, blur_kernel), 0)

    # Áp dụng Canny tìm cạnh
    edged = cv2.Canny(gray, canny_low, canny_high)
    edged = cv2.dilate(edged, (d_e_kernel, d_e_kernel), iterations=1)
    edged = cv2.erode(edged, (d_e_kernel, d_e_kernel), iterations=1)Code language: PHP (php)

Sau bước này ta sẽ có được bức ảnh như sau:

đo kích thước vật thể

Nhìn qua ta đã thấy có thể tìm được các contour của đồng xu và cái USB rồi. Chú ý là ở đây do nền của mình khá phức tạp nên việc tìm contour khá khó,các bạn có thể thử với nền đơn giản hơn (đơn sắc, phẳng….) cho dễ nhé.

Tìm contour và xác định kích thước bằng pixel

Rồi, bay giờ ta sẽ tìm các contour trong hình, và loại bỏ các contour nhỏ để giữ lại 2 contour chính là đồng xu và USB.

    # Tìm các Contour trong ảnh
    cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)

    # Sắp xếp các contour từ trái qua phải
    (cnts, _) = contours.sort_contours(cnts)
    P = None

    # Duyệt các contour
    for c in cnts:
        # Nếu contour quá nhỏ -> bỏ qua
        if cv2.contourArea(c) < area_threshold:
            continueCode language: PHP (php)

Với các contour tìm được, chúng ta sẽ thực hiện tính toán kích thước của nó trong bằng, tính bằng Pixel. Ở đây chúng ta sử dụng một khái niệm MinAreaRect chứ ko phải Bounding Box nhé.

Các bạn có thể phân biệt theo hình sau (đỏ là MinAreaRect và xanh là bounding box):

đo kích thước vật thể
Nguồn: Tại đây
    # Lấy minRect
    box = cv2.minAreaRect(c)
    # Lấy tọa độ các đỉnh của MinRect
    box = cv2.boxPoints(box)
    box = np.array(box, dtype="int")

    # Sắp xếp các điểm theo trình tự
    box = perspective.order_points(box)

    # Vẽ contour
    cv2.drawContours(orig, [box.astype("int")], -1, (0, 255, 0), 2)

    # Tinh toán 4 trung diểm của các cạnh
    (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)

    # Tính độ dài 2 chiều
    dc_W = dist.euclidean((tltrX, tltrY), (blbrX, blbrY))
    dc_H = dist.euclidean((tlblX, tlblY), (trbrX, trbrY))Code language: PHP (php)

Sau bước này ta đã có được kích thước Vật tham chiếu (đồng xu) bằng Pixel, ta sẽ tình toán số $P$ (xem lại phần 1 nếu không nhớ $P$là gì)

$$P = Real size / Size in pixel$$

Vậy ta tính tiếp nào:

P = ref_width / dc_H

Và kích thước thật của các Vật cần đo (USB) là:

dr_W = dc_W * P
dr_H = dc_H * P

Sau khi đã tính toán xong thì chúng ta chỉ cần vẽ lên ảnh cho đẹp, kẻ các khung contour là okie.

Full code của hàm này là:

def find_object_in_pix(orig, edge, area_threshold=3000):
    # Tìm các Contour trong ảnh
    cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = imutils.grab_contours(cnts)

    # Sắp xếp các contour từ trái qua phải
    (cnts, _) = contours.sort_contours(cnts)
    P = None

    # Duyệt các contour
    for c in cnts:
        # Nếu contour quá nhỏ -> bỏ qua
        if cv2.contourArea(c) < area_threshold:
            continue
            
        # Tính toán 2 chiều bằng Pixel
        dc_W, dc_H, tltrX, tltrY, trbrX, trbrY = get_distance_in_pixels(orig, c)

        # Nếu là đồng xu
        if P is None:
            # Cập nhật số P
            P = ref_width / dc_H
            # Gán luôn kích thước thật bằng số đã biết
            dr_W = ref_width
            dr_H = ref_width
        else: # Nếu là các vật khác
            # Tính toán kích thước thật dựa vào kích thước pixel và số P
            dr_W = dc_W * P
            dr_H = dc_H * P

        # Ve kich thuoc len hinh
        cv2.putText(orig, "{:.1f} mm".format(dr_H), (int(tltrX - 15), int(tltrY - 10)), cv2.FONT_HERSHEY_SIMPLEX, 1,
                    (0, 0, 255), 2)
        cv2.putText(orig, "{:.1f} mm".format(dr_W), (int(trbrX + 10), int(trbrY)), cv2.FONT_HERSHEY_SIMPLEX, 1,
                    (0, 0, 255), 2)

    return origCode language: PHP (php)

Và bước cuối cùng là show ảnh đã vẽ vời các kiểu lên màn hình:

đo kích thước vật thể

Mình có chia sẻ github có code sẵn để các bạn tải về chạy thử nhé: tại đây . Chú ý cài thư viện bằng lệnh:

pip install -r setup.txtCode language: CSS (css)

Done rồi,! Vậy là mình dã guide các bạn dùng OpenCV để đo kích thước các vật thể trong camera được rồi. Các bạn có thể phát triển, ứng dụng cho các bài toán riêng của các bạn nhé.

Chúc các bạn thành công!

Hãy join cùng cộng đồng Mì AI nhé!

Fanpage: http://facebook.com/miaiblog
Group trao đổi, chia sẻ: https://www.facebook.com/groups/miaigroup
Website: https://miai.vn
Youtube: http://bit.ly/miaiyoutube

Cảm ơn bài tham khảo cực hay của tác giả Adrian Rosebrock!

Related Post

10 Replies to “Chi tiết cách đo kích thước vật thể bằng OpenCV thuần”

      1. Em chào anh,
        Nếu vật cần đo kích thước đặt trên vật tham chiếu là tờ A4 thì xử lý như nào được ạ ?

  1. anh ơi em có một yêu cầu khó hơn về bài toán này đó là đo kích thước ảnh mà không sử dụng đến vật tham chiếu thì phải làm thế nào ạ?

Leave a Reply

Your email address will not be published. Required fields are marked *