У меня есть фотографии поверхности со многими углублениями. В большинстве случаев края канавки образуют параллельные линии, поэтому преобразование Канни и Хафа очень хорошо работает для обнаружения линий и некоторой характеристики. Однако в нескольких местах канавка повреждена, и края больше не параллельны.
Я ищу простой способ проверить, является ли определенный край прямой линией или есть какие-либо промежутки или отклонения от прямой линии. Я имею в виду что-то вроде параметра R квадрат в линейной интерполяции, но здесь мне нужен параметр, который в большей степени зависит от местоположения. Есть ли у вас другие мысли, как охарактеризовать края?
Я прикрепил изображение канавки после обнаружения края. Здесь края прямые, а канавка в порядке. К сожалению, на данный момент у меня нет доступа к фотографиям с поврежденной канавкой. Однако на изображениях с поврежденной канавкой линии будут иметь большие промежутки (не менее 10% от размера изображения) или не будут параллельными.
Основа техники Я делюсь ниже использования cv::HoughLinesP()
найти отрезки в изображении в градациях серого.
Приложение запускается с загрузки входного изображения в градациях серого. Затем он выполняет базовую операцию предварительной обработки для улучшения определенных характеристик изображения, чтобы улучшить обнаружение, выполняемое 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);
На этом этапе все обнаруженные линейные сегменты могут быть записаны в файл для целей визуализации:
На данный момент нам нужно отсортировать наш вектор линий, потому что cv::HoughLinesP()
не делает этого, и нам нужно отсортировать вектор, чтобы можно было идентифицировать группы линий, измеряя и сравнивая расстояние между линиями:
/* 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;
Последняя часть кода выше просто хранит позиции индекса вектора строк в новом vector<int>
поэтому мы знаем, с каких линий начинается новая группа.
Например, предположим, что индексы, хранящиеся в новом векторе: 0 4 8 12
, Помните: они определяют Начните каждой группы. Это означает, что конечные строки групп: 0, 4-1, 4, 8-1, 8, 12-1, 12
,
Зная это, мы пишем следующий код:
/* 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;
}
И полученное изображение:
Это не идеальный матч, но это близко С небольшими изменениями этот подход может стать намного лучше. Я бы начал писать более разумную логику для sort_by_y_coord
, который должен отбрасывать линии, которые имеют небольшие расстояния между координатами X (то есть небольшими отрезками линий), а также линии, которые не идеально выровнены по оси X (как, например, из второй группы в выходном изображении). Это предложение имеет гораздо больше смысла после того, как вы потратите время на оценку первого изображения, сгенерированного приложением.
Удачи.
То, что сразу приходит на ум, будет Hough Transform. Это схема голосования в пространстве строк, которая берет каждую возможную строку и дает вам оценку за нее. В коде, на который я ссылался выше, вы могли бы просто установить пороговое значение, которое приблизительно равно ~ 10% обвитых канавок / линий.