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:
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.
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:
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:
continue
Code 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):
# 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 orig
Code 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:
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.txt
Code 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!
Chào anh,
Mình có cần calib lại tấm ảnh trước không anh ?
Không cần em. Nếu có thì tốt hơn.
lấy khoảng cách giữa hai vật thể trên ảnh thì sao anh
Cũng thế em. Phải có 1 vật làm mốc! Coi khoảng cách giữa 2 vật thể là kích thước của cái đường thằng kia. Quan trọng là tính toán để lấy được toạ độ đường thẳng đó. Em post lên Group trao đổi, chia sẻ: https://facebook.com/groups/miaigroup nhé!
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 ạ ?
À thì em dùng kích thước tờ giấy A4 làm tham chiếu. Vậy thì cừ làm sao ta detect dược tờ giấy A4 trong hình là ta tính toán được. Chi tiết em post lên Group trao đổi, chia sẻ: https://facebook.com/groups/miaigroup cho tiện trao đổi nhé!
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 ạ?
Bạn post lên https://facebook.com/groups/miaigroup trao đổi cho tiện nhé!
anh co video huong dan khong ạ
Bài này anh lại không có. Em có thể hỏi trên Group trao đổi, chia sẻ: https://facebook.com/groups/miaigroup thêm nhé!