Я успешно реализовал пример определения квадрата OpenCV в своем тестовом приложении, но теперь мне нужно отфильтровать вывод, потому что он довольно грязный - или мой код неверен?
Я заинтересован в четырех угловых пунктах статьи для уменьшения перекоса (как этот ) и дальнейшей обработки ...
Исходное изображение:
Код:
double angle( cv::Point pt1, cv::Point pt2, cv::Point pt0 ) {
double dx1 = pt1.x - pt0.x;
double dy1 = pt1.y - pt0.y;
double dx2 = pt2.x - pt0.x;
double dy2 = pt2.y - pt0.y;
return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}
- (std::vector<std::vector<cv::Point> >)findSquaresInImage:(cv::Mat)_image
{
std::vector<std::vector<cv::Point> > squares;
cv::Mat pyr, timg, gray0(_image.size(), CV_8U), gray;
int thresh = 50, N = 11;
cv::pyrDown(_image, pyr, cv::Size(_image.cols/2, _image.rows/2));
cv::pyrUp(pyr, timg, _image.size());
std::vector<std::vector<cv::Point> > contours;
for( int c = 0; c < 3; c++ ) {
int ch[] = {c, 0};
mixChannels(&timg, 1, &gray0, 1, ch, 1);
for( int l = 0; l < N; l++ ) {
if( l == 0 ) {
cv::Canny(gray0, gray, 0, thresh, 5);
cv::dilate(gray, gray, cv::Mat(), cv::Point(-1,-1));
}
else {
gray = gray0 >= (l+1)*255/N;
}
cv::findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
std::vector<cv::Point> approx;
for( size_t i = 0; i < contours.size(); i++ )
{
cv::approxPolyDP(cv::Mat(contours[i]), approx, arcLength(cv::Mat(contours[i]), true)*0.02, true);
if( approx.size() == 4 && fabs(contourArea(cv::Mat(approx))) > 1000 && cv::isContourConvex(cv::Mat(approx))) {
double maxCosine = 0;
for( int j = 2; j < 5; j++ )
{
double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
maxCosine = MAX(maxCosine, cosine);
}
if( maxCosine < 0.3 ) {
squares.push_back(approx);
}
}
}
}
}
return squares;
}
РЕДАКТИРОВАТЬ 17/08/2012:
Чтобы нарисовать обнаруженные квадраты на изображении, используйте этот код:
cv::Mat debugSquares( std::vector<std::vector<cv::Point> > squares, cv::Mat image )
{
for ( int i = 0; i< squares.size(); i++ ) {
// draw contour
cv::drawContours(image, squares, i, cv::Scalar(255,0,0), 1, 8, std::vector<cv::Vec4i>(), 0, cv::Point());
// draw bounding rect
cv::Rect rect = boundingRect(cv::Mat(squares[i]));
cv::rectangle(image, rect.tl(), rect.br(), cv::Scalar(0,255,0), 2, 8, 0);
// draw rotated rect
cv::RotatedRect minRect = minAreaRect(cv::Mat(squares[i]));
cv::Point2f rect_points[4];
minRect.points( rect_points );
for ( int j = 0; j < 4; j++ ) {
cv::line( image, rect_points[j], rect_points[(j+1)%4], cv::Scalar(0,0,255), 1, 8 ); // blue
}
}
return image;
}
Ответы:
Это повторяющаяся тема в Stackoverflow, и, поскольку я не смог найти подходящую реализацию, я решил принять вызов.
Я внес некоторые изменения в демонстрацию квадратов, присутствующую в OpenCV, и полученный ниже код C ++ может обнаружить лист бумаги на изображении:
После выполнения этой процедуры лист бумаги станет самым большим квадратом в
vector<vector<Point> >
:Я позволяю вам написать функцию, чтобы найти самый большой квадрат. ;)
источник
for (int c = 0; c < 3; c++)
, который отвечает за итерацию на каждом канале изображения. Например, вы можете настроить его на итерацию только на одном канале :) Не забудьте проголосовать.angle()
- это вспомогательная функция . Как указано в ответе, этот код основан на samples / cpp / squares.cpp, присутствующих в OpenCV.Если не указано иное требование, я просто преобразовал бы ваше цветное изображение в оттенки серого и работал бы только с этим (нет необходимости работать с 3 каналами, контрастность слишком высока). Кроме того, если нет особых проблем с изменением размера, я бы работал с уменьшенной версией ваших изображений, так как они относительно велики, а размер ничего не добавляет к решаемой проблеме. Затем, наконец, ваша проблема решается с помощью медианного фильтра, некоторых основных морфологических инструментов и статистики (в основном для порога Otsu, который уже сделан для вас).
Вот то, что я получаю с вашим образцом и другим изображением с листом бумаги, который я нашел вокруг:
Медианный фильтр используется для удаления мелких деталей из изображения, теперь серого. Это, возможно, удалит тонкие линии внутри беловатой бумаги, что хорошо, потому что тогда вы закончите с крошечными соединенными компонентами, которые легко выбросить. После медианы примените морфологический градиент (просто
dilation
-erosion
) и оцифруйте результат по Оцу. Морфологический градиент - это хороший способ сохранить сильные края, его следует использовать чаще. Затем, поскольку этот градиент увеличит ширину контура, примените морфологическое истончение. Теперь вы можете отказаться от мелких компонентов.На данный момент, вот что мы имеем с правым изображением выше (до рисования синего многоугольника), левый не показан, потому что единственный оставшийся компонент - это тот, который описывает статью:
Учитывая примеры, теперь единственной проблемой остается различие между компонентами, которые выглядят как прямоугольники, и компонентами, которые не похожи. Это вопрос определения соотношения между площадью выпуклой оболочки, содержащей форму, и площадью ее ограничительной рамки; соотношение 0,7 прекрасно работает для этих примеров. Возможно, вам также придется отказаться от компонентов, которые находятся внутри документа, но не в этих примерах с помощью этого метода (тем не менее, выполнение этого шага должно быть очень простым, особенно потому, что это можно сделать напрямую через OpenCV).
Для справки вот пример кода в Mathematica:
Если есть более разнообразные ситуации, когда прямоугольник бумаги не так хорошо определен, или подход смешивает его с другими формами - эти ситуации могут произойти по разным причинам, но общей причиной является плохое получение изображения - тогда попробуйте объединить предварительные -процессы обработки с работой, описанной в статье "Обнаружение прямоугольника на основе оконного преобразования Хафа".
источник
Concept is the same
, (Я никогда не использовал Mathematica, поэтому не могу понять код.) И упомянутые вами различия - это различия, но не другой подход или основные. Если вы еще не сделали, например, проверьте это:Ну, я опоздал.
На вашем изображении бумага есть
white
, а фон естьcolored
. Таким образом, лучше обнаружить, что бумага находится вSaturation(饱和度)
каналеHSV color space
. Возьми сначала ссылку на вики HSL_and_HSV . Затем я скопирую большинство идей из моего ответа в этом разделе «Обнаружение цветного сегмента» на изображении .Основные шаги:
BGR
bgr
вhsv
космосCanny
, или,HoughLines
как вам нравится, я выберуfindContours
), приблизительно, чтобы получить углы.Это мой результат:
Код Python (Python 3.5 + OpenCV 3.3):
Связанные ответы:
источник
Вам нужен четырехугольник вместо повернутого прямоугольника.
RotatedRect
даст вам неверные результаты. Также вам понадобится перспективная проекция.В основном, что должно быть сделано:
Я реализовал класс,
Quadrangle
который заботится о преобразовании контура в четырехугольник, а также преобразует его в правильной перспективе.Смотрите рабочую реализацию здесь: Java OpenCV выравнивание контура
источник
После того, как вы обнаружили ограничивающий прямоугольник документа, вы можете выполнить четырехточечное перспективное преобразование, чтобы получить вид сверху сверху вниз на изображение. Это исправит перекос и изолирует только нужный объект.
Входное изображение:
Обнаруженный текстовый объект
Вид сверху текстового документа
Код
источник
Обнаружение листа бумаги - своего рода старая школа. Если вы хотите заняться обнаружением перекоса, то лучше сразу же стремиться к обнаружению текстовых строк. При этом вы получите крайние значения слева, справа, сверху и снизу. Откажитесь от любой графики на изображении, если вы не хотите, а затем сделайте некоторую статистику по сегментам текстовой линии, чтобы найти наиболее часто встречающийся угол или, точнее, угол. Вот как вы будете сужаться до хорошего угла наклона. Теперь после этого вы устанавливаете эти параметры для угла наклона и экстремумов и выравниваете изображение до требуемого.
Что касается текущего требования к изображению, лучше, если вы попробуете CV_RETR_EXTERNAL вместо CV_RETR_LIST.
Другой метод обнаружения ребер - это навести на краю бумаги классификатор случайных лесов, а затем использовать классификатор для получения карты ребер. Это, безусловно, надежный метод, но требует обучения и времени.
Случайные леса будут работать с низкоконтрастными сценариями, например, на белой бумаге с примерно белым фоном.
источник