Cốt lõi của kỹ thuật Tôi đang chia sẻ dưới đây sử dụng cv::HoughLinesP()
để tìm các đoạn đường kẻ trong hình ảnh thang độ xám.
Ứng dụng bắt đầu bằng cách tải hình ảnh đầu vào dưới dạng thang độ xám. Sau đó, nó thực hiện một thao tác tiền xử lý cơ bản để nâng cao đặc điểm nhất định của hình ảnh, nhằm cải thiện việc phát hiện được thực hiện bởi cv::HoughLinesP()
:
#include <cv.h>
#include <highgui.h>
#include <algorithm>
// Custom sort method adapted from: http://stackoverflow.com/a/328959/176769
// This is used later by std::sort()
struct sort_by_y_coord
{
bool operator()(cv::Vec4i const& a, cv::Vec4i const& b) const
{
if (a[1] < b[1]) return true;
if (a[1] > b[1]) return false;
return false;
}
};
int main()
{
/* Load input image as grayscale */
cv::Mat src = cv::imread("13531682.jpg", 0);
/* Pre-process the image to enhance the characteristics we are interested at */
medianBlur(src, src, 5);
int erosion_size = 2;
cv::Mat element = cv::getStructuringElement(cv::MORPH_CROSS,
cv::Size(2 * erosion_size + 1, 2 * erosion_size + 1),
cv::Point(erosion_size, erosion_size));
cv::erode(src, src, element);
cv::dilate(src, src, element);
/* Identify all the lines in the image */
cv::Size size = src.size();
std::vector<cv::Vec4i> total_lines;
cv::HoughLinesP(src, total_lines, 1, CV_PI/180, 100, size.width/2.f, 20);
int n_lines = total_lines.size();
std::cout << "* Total lines: "<< n_lines << std::endl;
cv::Mat disp_lines(size, CV_8UC1, cv::Scalar(0, 0, 0));
// For debugging purposes, the block below writes all the lines into disp_lines
// for (unsigned i = 0; i < n_lines; ++i)
// {
// cv::line(disp_lines,
// cv::Point(total_lines[i][0], total_lines[i][2]),
// cv::Point(total_lines[i][3], total_lines[i][4]),
// cv::Scalar(255, 0 ,0));
// }
// cv::imwrite("total_lines.png", disp_lines);
Tại thời điểm này, tất cả các đoạn thẳng được phát hiện có thể được viết vào một tập tin để hiển thị mục đích:

Tại thời điểm này chúng ta cần phải sắp xếp vector của chúng ta về đường vì cv::HoughLinesP()
không làm điều đó, và chúng ta cần các vector được sắp xếp để có thể xác định các nhóm dòng, bằng cách đo và so sánh khoảng cách giữa các dòng:
/* Sort lines according to their Y coordinate.
The line closest to Y == 0 is at the first position of the vector.
*/
sort(total_lines.begin(), total_lines.end(), sort_by_y_coord());
/* Separate them according to their (visible) groups */
// Figure out the number of groups by distance between lines
std::vector<int> idx_of_groups; // stores the index position where a new group starts
idx_of_groups.push_back(0); // the first line indicates the start of the first group
// The loop jumps over the first line, since it was already added as a group
int y_dist = 35; // the next groups are identified by a minimum of 35 pixels of distance
for (unsigned i = 1; i < n_lines; i++)
{
if ((total_lines[i][5] - total_lines[i-1][6]) >= y_dist)
{
// current index marks the position of a new group
idx_of_groups.push_back(i);
std::cout << "* New group located at line #"<< i << std::endl;
}
}
int n_groups = idx_of_groups.size();
std::cout << "* Total groups identified: "<< n_groups << std::endl;
Phần cuối cùng của mã trên chỉ lưu trữ vị trí chỉ số của vectơ của dòng trong vector<int>
mới để chúng tôi biết dòng nào bắt đầu một nhóm mới.
Ví dụ: giả sử rằng các chỉ mục được lưu trữ trong vectơ mới là: 0 4 8 12
. Hãy nhớ rằng: họ xác định bắt đầu của mỗi nhóm. Điều đó có nghĩa là các dòng kết thúc của các nhóm là: 0, 4-1, 4, 8-1, 8, 12-1, 12
.
Biết rằng, chúng ta viết đoạn mã sau:
/* Mark the beginning and end of each group */
for (unsigned i = 0; i < n_groups; i++)
{
// To do this, we discard the X coordinates of the 2 points from the line,
// so we can draw a line from X=0 to X=size.width
// beginning
cv::line(disp_lines,
cv::Point(0, total_lines[ idx_of_groups[i] ][7]),
cv::Point(size.width, total_lines[ idx_of_groups[i] ][8]),
cv::Scalar(255, 0 ,0));
// end
if (i != n_groups-1)
{
cv::line(disp_lines,
cv::Point(0, total_lines[ idx_of_groups[i+1]-1 ][9]),
cv::Point(size.width, total_lines[ idx_of_groups[i+1]-1 ][10]),
cv::Scalar(255, 0 ,0));
}
}
// mark the end position of the last group (not done by the loop above)
cv::line(disp_lines,
cv::Point(0, total_lines[n_lines-1][11]),
cv::Point(size.width, total_lines[n_lines-1][12]),
cv::Scalar(255, 0 ,0));
/* Save the output image and display it on the screen */
cv::imwrite("groups.png", disp_lines);
cv::imshow("groove", disp_lines);
cv::waitKey(0);
cv::destroyWindow("groove");
return 0;
}
Và hình ảnh kết quả là:

Nó không phải là một trận đấu hoàn hảo, nhưng nó gần gũi. Với một chút điều chỉnh ở đây và có cách tiếp cận này có thể nhận được tốt hơn nhiều.Tôi sẽ bắt đầu bằng cách viết một logic thông minh hơn cho sort_by_y_coord
, nên loại bỏ các đường có khoảng cách nhỏ giữa các tọa độ X (tức là các đoạn đường nhỏ), và các đường thẳng không được căn chỉnh hoàn hảo trên trục X (giống như đường từ nhóm thứ hai trong hình ảnh đầu ra). Đề xuất này có ý nghĩa hơn sau khi bạn dành thời gian để đánh giá hình ảnh đầu tiên do ứng dụng tạo ra.
Chúc may mắn.
Có thể bạn có thể bao gồm hình ảnh mẫu? –
Chào mừng bạn đến với Stackoverflow. Hãy cẩn thận xem lại câu trả lời của tôi, sau đó bỏ phiếu nếu nó giúp bạn. Bạn có thể nhấp vào hộp kiểm gần đó để chọn nó làm câu trả lời chính thức cho câu hỏi của bạn. Bằng cách làm công cụ này, bạn sẽ giúp khách truy cập trong tương lai như chính mình và chúng tôi bằng cách giữ cho chuỗi này được tổ chức. – karlphillip