Pupil detection from an eye image
Phát hiện đồng tử từ mắt người
In this tutorial I will show you a simple algorithm for detecting pupil from an eye image using its characteristics: round black circle located in the center of the eye. The idea came from CodeProject which I improve the algorithm a little bit.
Trong bài này tôi sẽ cho các bạn xem thuật toán đơn giản để phát hiện đồng tử từ mắt người sử dụng các đặc điểm của nó: vòng tròn đen nằm giữa mắt. Ý tưởng này đến từ Codeproject mà tôi sẽ nâng cao thuật toán này lên 1 chút.
The algorithm – Thuật toán:
1. Load the source image – Load ảnh.
2. Invert it – nghịch đảo giá trị ảnh (âm bản).
3. Convert to grayscale – Chuyển sang ảnh xám.
4. Convert to binary image by thresholding it – Chuyển từ ảnh nhị phân bằng cách phân ngưỡng.
5. Find all blobs – Tìm tất cả đốm màu.
6. Remove noise by filling holes in each blob – Loại bỏ nhiễu bằng cách phủ đầy các lỗ trống trong mỗi đốm màu
7. Get blob which is big enough and has round shape – Lấy đốm màu đủ lớn và có vòng tròn bao quanh.
I will be using the image from Wikimedia Commons below as the source image.
Tôi sẽ dùng ảnh từ trang Wikimedia bên dưới làm ảnh gốc
First, load the source image.
Đầu tiên là load ảnh gốc
1 2 3 |
cv::Mat src = cv::imread("eye_image.jpg"); if (src.empty()) return -1; |
Invert the image and convert to grayscale. To invert an image, use the ~ (inversion) operator. See Matrix Expression from the OpenCV documentation for more information.
Nghịch đảo ảnh và chuyển sang ảnh xám. Để nghịch đảo ảnh, sử dụng dấu ngã. Xem các phép toán ma trận trong tài liệu của OpenCV để biết thêm
1 2 |
cv::Mat gray; cv::cvtColor(~src, gray, CV_BGR2GRAY); |
Convert to binary image by thresholding it. Chuyển sang ảnh nhị phân bằng cách phân ngưỡng nó.
cv::threshold(gray, gray, 220, 255, cv::THRESH_BINARY);
As you see there are a lot of objects in the thresholded image but what we want is the round shaped object. Also note that in some cases there are holes in the round object. We need to remove the holes so we can get the correct circle area later.
Như bạn thấy rất nhiều thứ trong ảnh đã phân ngưỡng nhưng chúng ta cần một hình tròn. Và cũng chú ý là trong vài trường hợp có lỗ trong hình tròn. Chúng ta cần loại bỏ lỗ nên có thể lấy vòng tròn chính xác sau.
Remove the noise by drawing the interiors with white color.
Loại bỏ nhiễu bằng cách phủ màu trắng.
1 2 3 4 5 6 |
// Find all contours std::vector<std::vector<cv::Point> > contours; cv::findContours(gray.clone(), contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); // Fill holes in each contour cv::drawContours(gray, contours, -1, CV_RGB(255,255,255), -1); |
It is in perfect shape now. Next we just need to loop each blob. Skip small blobs and get blob with round shape.
Bây giờ nó là hình hoàn hảo. Tiếp theo chúng ta sẽ lặp qua các đốm màu. Bỏ qua những đốm nhỏ và và lấy đốm có dạng hình tròn.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
for (int i = 0; i < contours.size(); i++) { double area = cv::contourArea(contours[i]); // Blob area cv::Rect rect = cv::boundingRect(contours[i]); // Bounding box int radius = rect.width/2; // Approximate radius // Look for round shaped blob if (area >= 30 && std::abs(1 - ((double)rect.width / (double)rect.height)) <= 0.2 && std::abs(1 - (area / (CV_PI * std::pow(radius, 2)))) <= 0.2) { cv::circle(src, cv::Point(rect.x + radius, rect.y + radius), radius, CV_RGB(255,0,0), 2); } } |
The result is pretty good. But since the algorithm is very simple, it may be failed for more noisy images. The full source code is available on Github.
Kết quả rất tốt. Nhưng thuật toán này rất đơn giản, có thể sẽ sai nếu ảnh có nhiều nhiễu hơn. Code đầy đủ ở bên 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 |
/** * Program to detect pupil, based on * http://www.codeproject.com/Articles/137623/Pupil-or-Eyeball-Detection-and-Extraction-by-C-fro * with some improvements. */ #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/highgui/highgui.hpp> #include <cmath> int main(int argc, char** argv) { // Load image cv::Mat src = cv::imread("eye_image.jpg"); if (src.empty()) return -1; // Invert the source image and convert to grayscale cv::Mat gray; cv::cvtColor(~src, gray, CV_BGR2GRAY); // Convert to binary image by thresholding it cv::threshold(gray, gray, 220, 255, cv::THRESH_BINARY); // Find all contours std::vector<std::vector<cv::Point> > contours; cv::findContours(gray.clone(), contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); // Fill holes in each contour cv::drawContours(gray, contours, -1, CV_RGB(255,255,255), -1); for (int i = 0; i < contours.size(); i++) { double area = cv::contourArea(contours[i]); cv::Rect rect = cv::boundingRect(contours[i]); int radius = rect.width/2; // If contour is big enough and has round shape // Then it is the pupil if (area >= 30 && std::abs(1 - ((double)rect.width / (double)rect.height)) <= 0.2 && std::abs(1 - (area / (CV_PI * std::pow(radius, 2)))) <= 0.2) { cv::circle(src, cv::Point(rect.x + radius, rect.y + radius), radius, CV_RGB(255,0,0), 2); } } cv::imshow("image", src); cv::waitKey(0); return 0; } |
Có thể viết bằng Python không ạ???
bài dịch nên chỉ có bằng C++ thôi bạn nhé