Bài này hướng dẫn các đếm các đồng xu nằm chồng bằng OpenCV. Sử dụng 2 thuật toán chính là Watershed và DistanceTransform.
Đếm đối tượng có hình dạng đơn giản và không dính nhau khá là dễ. Chỉ cần lấy biên từ ảnh nhị phân thì chúng ta có thể biết được số lượng đường biên. Nhưng nếu đối tượng dính nhau hoặc đè chồng, thì khó hơn 1 chút.
Với những lá bài đè chồng lên nhau như hình dưới thì khi nhị phân hóa sẽ thành 1 blob duy nhất. Trong thực tế các dây chuyền sản xuất cũng có những sản phẩm nằm chồng lên nhau đôi chút.
Thuật toán Watershed
Watershed là thuật toán xử lý hình ảnh nhằm tách đối tượng khỏi background. Thuật toán có input là ảnh xám và 1 ảnh gọi là ảnh marker. Ảnh marker là ảnh mà bạn cho thuật toán Watershed biết đâu là đối tượng foreground và background. Ảnh market là ảnh nhị phân tương tự như mask và có cùng size với ảnh xám.
Hình dưới ảnh xám được đánh dấu thủ công để tách con đường ra khỏi cảnh. Đưa ảnh nguồn và ảnh đánh dấu vào thì thuật toán sẽ thực hiện việc phân đoạn con đường như hình 2b.
Hình 2 (a) Ảnh gốc với marker (b) Con đường đã được phân đoạn với thuật toán Watershed
Thuật toán Watershed được giải thích trong tài liệu của OpenCV:
1 2 3 4 5 6 |
cv::watershed( InputArray image, // Input 8 bit 3-channel image InputOutputArray markers, // Input/output 32-bit single-channel image _ // of markers. it should have the same size _ // as the input ) |
Xem file ví dụ watershed.cpp trong thư viện openCV để biết cách dùng Watershed. Chú ý rằng trong bài này tôi tạo ảnh đánh maker tự động
Distance transform
Distance transform sử dụng ảnh nhị phân, với mỗi pixel được thay thế bằng khoảng cách của nó tới pixel background gần nhất. Xem ví dụ:
Hình 3 (a) Ảnh gốc (b) Ảnh sau khi áp dụng Distance transform
Ví dụ khác về ảnh distance transform
Trong OpenCV, distance transform thực hiện bằng hàm:
1 2 3 4 5 6 |
cv::distanceTransform( InputArray src, // 8-bit single-channel image OutputArray dst, // 32-bit floating point single channel image int distanceType, // One of: CV_DIST_L1, CV_DIST_L2, CV_DIST_L3 int maskSize // Size of the distance transform mask ) |
Thuật toán này dùng để tạo ảnh maker cho thuật toán Watershed được mô tả bên dưới.
Tiến hành viết code
Thực hành đếm và tách những đồng xu trong hình dưới
Load ảnh và chuyển sang ảnh nhị phân
Thực hiện phép distance transform và chuẩn hóa kết quả về khoảng [0,1] nên chúng ta có thể nhìn trực quan và phân ngưỡng nó.
Sau đó chúng ta phân ngưỡng để lấy các giá trị từ 0.5 -> 1.0
Tìm biên
Sử dụng hàm findContours() để tìm số countour có trong ảnh. Sau đó vẽ contour để tạo ảnh marker cho thuật toán Watershed. Chúng ta cũng cần vẽ background marker, đó chính là điểm tròn góc trên bên trái. Vẽ background marker sao cho không chồng lên các contour khác là được.
Áp dụng thuật toán Watershed được kết quả
Source đầy đủ ở dưới
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
#include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> int main() { cv::Mat src = cv::imread("coins.jpg"); if (!src.data) return -1; cv::imshow("src", src); // Create binary image from source image cv::Mat bw; cv::cvtColor(src, bw, CV_BGR2GRAY); cv::threshold(bw, bw, 40, 255, CV_THRESH_BINARY); cv::imshow("bw", bw); // Perform the distance transform algorithm cv::Mat dist; cv::distanceTransform(bw, dist, CV_DIST_L2, 3); // Normalize the distance image for range = {0.0, 1.0} // so we can visualize and threshold it cv::normalize(dist, dist, 0, 1., cv::NORM_MINMAX); cv::imshow("dist", dist); // Threshold to obtain the peaks // This will be the markers for the foreground objects cv::threshold(dist, dist, .5, 1., CV_THRESH_BINARY); cv::imshow("dist2", dist); // Create the CV_8U version of the distance image // It is needed for cv::findContours() cv::Mat dist_8u; dist.convertTo(dist_8u, CV_8U); // Find total markers std::vector<std::vector<cv::Point> > contours; cv::findContours(dist_8u, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE); int ncomp = contours.size(); std::cout << "Number of coins: " << ncomp; // Create the marker image for the watershed algorithm cv::Mat markers = cv::Mat::zeros(dist.size(), CV_32SC1); // Draw the foreground markers for (int i = 0; i < ncomp; i++) cv::drawContours(markers, contours, i, cv::Scalar::all(i+1), -1); // Draw the background marker cv::circle(markers, cv::Point(5,5), 3, CV_RGB(255,255,255), -1); cv::imshow("markers", markers*10000); // Perform the watershed algorithm cv::watershed(src, markers); // Generate random colors std::vector<cv::Vec3b> colors; for (int i = 0; i < ncomp; i++) { int b = cv::theRNG().uniform(0, 255); int g = cv::theRNG().uniform(0, 255); int r = cv::theRNG().uniform(0, 255); colors.push_back(cv::Vec3b((uchar)b, (uchar)g, (uchar)r)); } // Create the result image cv::Mat dst = cv::Mat::zeros(markers.size(), CV_8UC3); // Fill labeled objects with random colors for (int i = 0; i < markers.rows; i++) { for (int j = 0; j < markers.cols; j++) { int index = markers.at<int>(i,j); if (index > 0 && index <= ncomp) dst.at<cv::Vec3b>(i,j) = colors[index-1]; else dst.at<cv::Vec3b>(i,j) = cv::Vec3b(0,0,0); } } cv::imshow("dst", dst); cv::waitKey(0); return 0; } |
Anh ơi, 3 dòng khai báo đầu tiên là sao ạ, em khai báo như thế bị lỗi ạ
Dòng 39 có tác dụng gì ạ, em thấy là không dùng đến ạ
mình đã sửa rồi nhé, do sai format html thôi
Dạ, dòng code 77 bị sai anh ạ
thanks bạn đã chỉ lỗi
Bạn dùng OPENCV trên java hay c++, mình cho code vào pthon không đc…
code này bằng C++ nhé
Bạn ơi code của bạn chạy đến đoạn ra hình ảnh thôi chứ chưa đếm đúng k ?
đã bổ sung dòng in ra số lượng coin rồi nhé
Bạn ơi code này chỉ đếm được duy nhất trên ảnh đồng xu này thôi ạ?B có code nào đếm được số lượng vật thể ở ảnh tùy chọn của mình k?
Với mỗi vật thể khác nhau thì phải code lại, nếu bạn muốn đếm vật thể bất kỳ thì có thể sử dụng https://thigiacmaytinh.com/nhan-dien-vat-bang-imageai/ hoặc https://thigiacmaytinh.com/retrain-face-cascade-model/
Ad ơi cho mình hỏi nếu mình thay bằng ảnh đồng xu khác thì cần chỉnh sửa gì thì mới đếm chính xác được v ạ?
Bạn cần đưa hình ảnh lên mới biết được là cần chỉnh sửa gì nhé
a ơi cho e hỏi a dùng phần mềm j để viết code ạ
Sử dụng Visual Studio để viết C++ nhé https://thigiacmaytinh.com/huong-dan-cai-dat-visual-studio-2019/
a còn giữ file project ko cho e xin với ạ
bạn copy code lại rồi save thành file watershed.py là chạy được rồi nhé
nếu code bằng C# thì sao hả a
rất tiếc là không có C#
a cho e hỏi nếu dùng hình ảnh đồng xu khác thì cần sửa code ở những dòng nào ạ. giúp e với!
up hình lên mình xem thử
Hay quá
Anh có code để mô phỏng trên R không anh. Cho em xin với
bài này không có ngôn ngữ R bạn nhé