Hãy tưởng tượng về 1 bản đồ địa hình, trong đó có các đỉnh núi và các thung lũng. Khi chúng ta đổ nước vào đó thì mực nước sẽ luôn bằng nhau vì nước chảy lan đến khi gặp vật cản thì dừng lại. Dựa vào ý tưởng đó chúng ta có thể tách ký tự bằng cách xem các ký tự là các thung lũng, hoặc ngược lại các ký tự là những vùng cao còn nền là thung lũng.
Hình trên là bản đồ địa hình, các đường đồng mức thể hiện mực nước dâng cùng độ cao và các vùng được liên kết với với nhau
Ý tưởng của thuật toán
Thuật toán floodfill hoạt động như sau: user chỉ định 1 điểm và gán 1 màu cho pixel ở đó, thuật toán sẽ tìm tất cả các pixel lân cận có cùng giá trị và tô màu. Nếu user chỉ định cận trên/cận dưới thì thuật toán tìm các pixel có giá trị chênh lệch trong khoảng giá trị cho phép. Kết quả ta được tập hợp các pixels nằm kề nhau với các giá trị giống/gần giống nhau kèm theo 1 hình chữ nhật chứa bao lấy.
Trong lib OpenCV có sẵn sample viết bằng C++ lẫn Python minh hoạ thuật toán này. Thuật toán này chạy được với ảnh binary, xám và màu.
Bạn có thể dùng mspaint, dùng tool Fill with Color (có hình thùng sơn) để hiểu về thuật toán.
Ứng dụng vào tìm ký tự biển số xe
Thuật toán này dùng để tìm các ký tự, các đối tượng foreground trên background. Để tìm được phải chuyển qua ảnh trắng đen, sau đó cần phải sử dụng thuật toán nhiều lần để tìm được tất cả các blob trong ảnh:
- Duyệt qua tất cả các pixels lớn hơn 0 trong ảnh
- Pixel đầu tiên dùng floodfill tô 1 màu, ta được blob đầu tiên
- Qua pixel tiếp theo, nếu khác màu chúng ta đã tô thì tô màu mới, nếu giống màu chúng ta đã từng tô thì bỏ qua
- Lặp lại cho đến hết ảnh
Như vậy, 1 biển số xe sau khi được cắt chuyển thành ảnh trắng đen, sau đó dùng thuật toán floodfill sẽ tách được các ký tự như hình bên dưới
Code c++
Hàm bên dưới tìm tất cả các blob trong ảnh binary. Struct blob có các tham số về kích cỡ Mat chứa nó, hình chữ nhật bao lấy và số điểm tạo thành blob đó.
Blob được định nghĩa là tập hợp các pixel liền kề có giá trị giống (hoặc gần giống nhau, ±2 chẳng hạn). Do bài này tìm trên ảnh nhị phân [0;255] nên các pixel của blob phải giống nhau
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 |
struct Blob { cv::Size matContainSize; cv::Rect boundingRect; std::vector<cv::Point2i> points; Blob(Blob* b); Blob() {}; }; std::vector<Blob> FindBlobs(cv::Mat &matBinary, cv::Size minSize, cv::Size maxSize) { ASSERT(matBinary.channels() == 1, "Image is not binary"); std::vector<Blob> blobs; // Fill the label_image with the blobs // 0 - background // 1 - unlabelled foreground // 2+ - labelled foreground uchar label_count = 2; // starts at 2 because 0,1 are used already for (int y = 0; y < matBinary.rows; y++) { for (int x = 0; x < matBinary.cols; x++) { uchar val = matBinary.at<uchar>(y, x); if (val != 255) continue; cv::Rect rect; cv::floodFill(matBinary, cv::Point(x, y), ++label_count, &rect); if (label_count == 255) label_count = 2; if (minSize.area() > 0 && (rect.width < minSize.width || rect.height < minSize.height)) continue; if(maxSize.area() >0 && (rect.width > maxSize.width || rect.height > maxSize.height)) continue; Blob blob; for (int i = rect.y; i < (rect.y + rect.height); i++) { for (int j = rect.x; j < (rect.x + rect.width); j++) { if (matBinary.at<uchar>(i, j) == label_count) blob.points.push_back(cv::Point2i(j, i)); } } if (blob.points.size() == 0) continue; blob.boundingRect = rect; blob.matContainSize = matBinary.size(); blobs.push_back(blob); } } return blobs; } |
Anh ơi, làm sao để có thể lấy được dữ liệu chính xác hơn ạ. Em làm nhưng 1 biển số xe chỉ lấy được có 6 hoặc 7 ký tự thôi ạ
thì dùng nhiều biển để lấy ra nhiều ký tự
như thế thì em có thể dùng tool Object locator để khoanh vùng ký tự từ biển số xe mà không phải dùng thuật toán floodfill này để tách ký tự cũng được phải không a
đó cũng là 1 cách, cách này mình làm từ thời sinh viên
cv::Size minSize, cv::Size maxSize truyền gì vô vậy anh
Bạn truyền vào giá trị nhỏ nhất & lớn nhất theo sự ước lượng của bạn. Giả sử như bạn nghĩ các điểm nhiễu có giá trị dưới 10px thì truyền vào 10, tương tự như blob bạn cần tìm không lớn quá 5000 pixel thì truyền vào 5000. Nếu bạn để là 0 thì lấy hết tất cả blob
Dạ em cám ơn a.e hỏi ngu ạ.cái cv::Mat &matBinary em truyền vào biển số xe mà sao giá trị val lúc nào cũng khác 255 hết sao nó chạy được a.
tức là chỉ tính các pixel có màu trắng, pixel nào khác 255 thì bỏ qua
Ảnh trắng đen của em nó bị nhiễu nhiều quá.
Có cách nào làm cho nó bớt nhiễu không anh.
Em cám ơn ạ.
Bạn sử dụng hàm blur() trước khi phân ngưỡng nhé