Алгоритм определения углов листа бумаги на фото

98

Как лучше всего определить углы накладной / чека / листа бумаги на фотографии? Это должно использоваться для последующей коррекции перспективы перед OCR.

Мой нынешний подход был:

RGB> Серый> Обнаружение Canny Edge с установлением порога> Расширить (1)> Удалить мелкие объекты (6)> очистить объекты границы> выбрать большой блог на основе Convex Area. > [определение угла - не реализовано]

Я не могу не думать, что должен быть более надежный «интеллектуальный» / статистический подход для обработки этого типа сегментации. У меня не так много обучающих примеров, но я, вероятно, смог бы собрать вместе 100 изображений.

Более широкий контекст:

Я использую Matlab для создания прототипа и планирую реализовать систему в OpenCV и Tesserect-OCR. Это первая из ряда проблем обработки изображений, которые мне нужно решить для этого конкретного приложения. Итак, я собираюсь развернуть собственное решение и заново ознакомиться с алгоритмами обработки изображений.

Вот пример изображения, которое я бы хотел обработать с помощью алгоритма: Если вы хотите принять вызов, большие изображения находятся на http://madteckhead.com/tmp

Случай 1
(источник: madteckhead.com )

случай 2
(источник: madteckhead.com )

чехол 3
(источник: madteckhead.com )

чехол 4
(источник: madteckhead.com )

В лучшем случае это дает:

случай 1 - хитрый
(источник: madteckhead.com )

случай 1 - пост хитрый
(источник: madteckhead.com )

случай 1 - крупнейший блог
(источник: madteckhead.com )

Однако он легко терпит неудачу в других случаях:

случай 2 - хитрый
(источник: madteckhead.com )

случай 2 - пост хитрый
(источник: madteckhead.com )

случай 2 - крупнейший блог
(источник: madteckhead.com )

Заранее спасибо за все отличные идеи! Я так люблю!

РЕДАКТИРОВАТЬ: прогресс преобразования Хафа

В: Какой алгоритм будет кластеризовать горизонтальные линии для поиска углов? Следуя советам из ответов, я смог использовать преобразование Хафа, выбрать линии и отфильтровать их. Мой нынешний подход довольно груб. Я сделал предположение, что счет-фактура всегда будет меньше 15 градусов от выравнивания с изображением. Я получаю разумные результаты для строк, если это так (см. Ниже). Но я не совсем уверен в подходящем алгоритме для кластеризации линий (или голосования) для экстраполяции углов. Линии Хафа не непрерывны. А на зашумленных изображениях могут быть параллельные линии, поэтому требуются метрики какой-либо формы или расстояния от начала линии. Любые идеи?

Случай 1 случай 2 чехол 3 чехол 4
(источник: madteckhead.com )

Натан Келлер
источник
1
Да, примерно в 95% случаев он работал. С тех пор мне пришлось отложить код из-за нехватки времени. На каком-то этапе я опубликую продолжение, не стесняйтесь поручить мне, если вам понадобится срочная помощь. Извините за отсутствие хороших последующих действий. Я бы хотел вернуться к работе над этой функцией.
Натан Келлер
Натан, не могли бы вы рассказать о том, как вы в итоге это сделали? Я застрял в той же точке, распознавая углы / внешний контур листа бумаги. Я столкнулся с теми же проблемами, что и вы, поэтому мне было бы очень интересно найти решение.
Tim
6
Сейчас все изображения в этом посте 404.
ChrisF

Ответы:

28

Я друг Мартина, который работал над этим в начале этого года. Это был мой первый проект кодирования, и закончился он немного спешно, поэтому код нуждается в некотором ошибочном ... декодировании ... Я дам несколько советов из того, что я уже видел, что вы делаете, а затем отсортируйте мой код в мой выходной завтра.

Первый совет, OpenCVи pythonони классные, переходите к ним как можно скорее. : D

Вместо того, чтобы удалять мелкие объекты и / или шум, опустите хитрые ограничения, чтобы он принял больше краев, а затем найдите самый большой замкнутый контур (в OpenCV используйте findcontour()с некоторыми простыми параметрами, я думаю, что использовал CV_RETR_LIST). может по-прежнему испытывать затруднения, когда он находится на белом листе бумаги, но определенно давал наилучшие результаты.

Для Houghline2()Transform попробуйте использовать CV_HOUGH_STANDARDвместо CV_HOUGH_PROBABILISTIC, это даст rho и theta , определяя линию в полярных координатах, а затем вы можете сгруппировать линии с определенным допуском к ним.

Моя группировка работала как справочная таблица, для каждой строки, выведенной из преобразования hough, она давала пару ро и тета. Если эти значения были в пределах, скажем, 5% пары значений в таблице, они были отброшены, если они были вне этих 5%, в таблицу добавлялась новая запись.

Тогда вам будет намного проще анализировать параллельные линии или расстояние между линиями.

Надеюсь это поможет.

Дэниел Кроули
источник
Привет, Даниэль, спасибо за участие. Мне нравится твой подход. на самом деле это маршрут, по которому я получаю хорошие результаты на данный момент. Был даже пример OpenCV, который обнаружил прямоугольники. Просто нужно было отфильтровать результаты. как вы сказали, белое на белом трудно обнаружить этим методом. Но это был более простой и менее затратный подход, чем хаф. На самом деле я отказался от грубого подхода в своем алгоритме и выполнил приближение полифонии, взгляните на пример квадратов в opencv. Я бы хотел увидеть вашу реализацию сильного голосования. Заранее спасибо, Натан
Натан Келлер
У меня возникли проблемы с этим подходом, я опубликую решение, если смогу придумать что-то лучшее для использования в будущем,
Аншуман Кумар
@AnshumanKumar, мне действительно нужна помощь с этим вопросом, вы можете мне помочь, пожалуйста? stackoverflow.com/questions/61216402/…
Карлос Диего
19

Студенческая группа в моем университете недавно продемонстрировала приложение для iPhone (и приложение OpenCV на Python), которое они написали именно для этого. Насколько я помню, шаги были примерно такими:

  • Срединный фильтр для полного удаления текста на бумаге (это был рукописный текст на белой бумаге с довольно хорошим освещением и может не работать с печатным текстом, он работал очень хорошо). Причина в том, что это значительно упрощает обнаружение углов.
  • Преобразование Хафа для линий
  • Найдите пики в пространстве аккумулятора преобразования Хафа и проведите каждую линию по всему изображению.
  • Проанализируйте линии и удалите те, которые расположены очень близко друг к другу и находятся под одинаковым углом (сгруппируйте линии в одну). Это необходимо, потому что преобразование Хафа несовершенно, поскольку оно работает в дискретном пространстве выборки.
  • Найдите пары линий, которые примерно параллельны и пересекают другие пары, чтобы увидеть, какие линии образуют четырехугольники.

Это работало довольно хорошо, и они смогли сделать снимок листа бумаги или книги, выполнить определение угла, а затем сопоставить документ на изображении на плоской плоскости почти в реальном времени (для выполнения была единственная функция OpenCV. отображение). Когда я увидел, что он работает, оптического распознавания текста не было.

Мартин Фут
источник
Спасибо за прекрасные идеи, Мартин. Я последовал вашему совету и реализовал подход преобразования Хафа. (См. Результаты выше). Я изо всех сил пытаюсь определить надежный алгоритм, который экстраполирует линии, чтобы найти пересечения. Линий не много, а ложных срабатываний немного. Есть ли у вас какие-нибудь советы, как мне лучше всего объединять и отбрасывать строки? Если ваши ученики заинтересованы, предложите им связаться с ними. Мне бы хотелось услышать их опыт по запуску алгоритмов на мобильной платформе. (Это моя следующая цель). Большое спасибо за ваши идеи.
Натан Келлер
1
Похоже, HT для линий хорошо зарекомендовал себя во всех изображениях, кроме вашего второго, но определяете ли вы пороговый допуск для начального и конечного значений в аккумуляторе? HT на самом деле не определяет начальную и конечную позиции, а скорее значения m и c в y = mx + c. См. Здесь - обратите внимание, что здесь используются полярные координаты в аккумуляторе, а не декартовые. Таким образом, вы можете сгруппировать линии по c, а затем по m, чтобы сделать их более тонкими, и, вообразив, что линии проходят по всему изображению, вы найдете более полезные пересечения.
Martin Foot
@MartinFoot, мне действительно нужна помощь с этим вопросом, вы можете мне помочь, пожалуйста? stackoverflow.com/questions/61216402/…
Карлос Диего
16

Вот что я пришел к выводу после небольшого экспериментирования:

import cv, cv2, numpy as np
import sys

def get_new(old):
    new = np.ones(old.shape, np.uint8)
    cv2.bitwise_not(new,new)
    return new

if __name__ == '__main__':
    orig = cv2.imread(sys.argv[1])

    # these constants are carefully picked
    MORPH = 9
    CANNY = 84
    HOUGH = 25

    img = cv2.cvtColor(orig, cv2.COLOR_BGR2GRAY)
    cv2.GaussianBlur(img, (3,3), 0, img)


    # this is to recognize white on white
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(MORPH,MORPH))
    dilated = cv2.dilate(img, kernel)

    edges = cv2.Canny(dilated, 0, CANNY, apertureSize=3)

    lines = cv2.HoughLinesP(edges, 1,  3.14/180, HOUGH)
    for line in lines[0]:
         cv2.line(edges, (line[0], line[1]), (line[2], line[3]),
                         (255,0,0), 2, 8)

    # finding contours
    contours, _ = cv2.findContours(edges.copy(), cv.CV_RETR_EXTERNAL,
                                   cv.CV_CHAIN_APPROX_TC89_KCOS)
    contours = filter(lambda cont: cv2.arcLength(cont, False) > 100, contours)
    contours = filter(lambda cont: cv2.contourArea(cont) > 10000, contours)

    # simplify contours down to polygons
    rects = []
    for cont in contours:
        rect = cv2.approxPolyDP(cont, 40, True).copy().reshape(-1, 2)
        rects.append(rect)

    # that's basically it
    cv2.drawContours(orig, rects,-1,(0,255,0),1)

    # show only contours
    new = get_new(img)
    cv2.drawContours(new, rects,-1,(0,255,0),1)
    cv2.GaussianBlur(new, (9,9), 0, new)
    new = cv2.Canny(new, 0, CANNY, apertureSize=3)

    cv2.namedWindow('result', cv2.WINDOW_NORMAL)
    cv2.imshow('result', orig)
    cv2.waitKey(0)
    cv2.imshow('result', dilated)
    cv2.waitKey(0)
    cv2.imshow('result', edges)
    cv2.waitKey(0)
    cv2.imshow('result', new)
    cv2.waitKey(0)

    cv2.destroyAllWindows()

Не идеально, но, по крайней мере, работает для всех образцов:

1 2 3 4

Вануан
источник
4
Я работаю над аналогичным проектом. Я запускаю над кодом и получаю ошибку «Нет модуля с именем cv». Я установил версию Open CV 2.4, и импорт cv2 у меня отлично работает.
Навнит Сингх
Не могли бы вы обновить этот код, чтобы он работал? pastebin.com/PMH5Y0M8 он просто показывает мне черную страницу.
the7erm
Есть ли у вас какие - либо идеи о том , как преобразовать следующий код Java: for line in lines[0]: cv2.line(edges, (line[0], line[1]), (line[2], line[3]), (255,0,0), 2, 8) # finding contours contours, _ = cv2.findContours(edges.copy(), cv.CV_RETR_EXTERNAL, cv.CV_CHAIN_APPROX_TC89_KCOS) contours = filter(lambda cont: cv2.arcLength(cont, False) > 100, contours) contours = filter(lambda cont: cv2.contourArea(cont) > 10000, contours)
aurelianr
Вануан. Мне действительно нужна помощь с этим вопросом. Не могли бы вы мне помочь? stackoverflow.com/questions/61216402/…
Карлос Диего
9

Вместо того, чтобы начинать с обнаружения краев, вы можете использовать обнаружение углов.

Marvin Framework предоставляет для этой цели реализацию алгоритма Moravec. Вы можете найти уголки бумаги как отправную точку. Ниже результат алгоритма Моравека:

введите описание изображения здесь

Габриэль Амбросио Арчанхо
источник
4

Также вы можете использовать MSER (Максимально устойчивые экстремальные области) над результатом оператора Собеля, чтобы найти стабильные области изображения. Для каждой области, возвращаемой MSER, вы можете применить аппроксимацию выпуклой оболочки и поли, чтобы получить что-то вроде этого:

Но этот вид обнаружения полезен для обнаружения в реальном времени более одного изображения, которое не всегда дает лучший результат.

результат

Флайн
источник
1
Не могли бы вы поделиться некоторыми подробностями для этого, возможно, какого-то кода, заранее большое спасибо
Монти,
Я получаю сообщение об ошибке в cv2.CHAIN_APPROX_SIMPLE: слишком много значений для распаковки. Любая идея? Я использую изображение 1024 * 1024 в качестве образца
Правин
1
Спасибо всем, только что разобрался с изменением синтаксиса в текущей ветке Opencv. Answers.opencv.org/question/40329/…
Правин
Разве MSER не предназначен для извлечения капель? Я попробовал, и он обнаруживает только большую часть текста
Аншуман Кумар
3

После обнаружения края используйте преобразование Хафа. Затем поместите эти точки в SVM (поддерживающую векторную машину) с их метками, если в примерах есть плавные линии на них, у SVM не возникнет проблем с разделением необходимых частей примера и других частей. Мой совет по SVM, поставьте такие параметры, как подключение и длина. То есть, если точки связаны и длинные, они, скорее всего, будут линией квитанции. Затем вы можете удалить все остальные точки.

Гефест
источник
Привет, Арес, спасибо за идеи! Я реализовал преобразование Хафа (см. Выше). Я не могу найти надежного способа найти углы с учетом ложных срабатываний и прерывистых линий. Есть ли у вас еще идеи? Прошло некоторое время с тех пор, как я изучал методы SVM. Это контролируемый подход? У меня нет данных для обучения, но я мог бы их сгенерировать. Мне было бы интересно изучить этот подход, так как я хочу узнать больше о SVM. Можете ли вы порекомендовать какие-либо ресурсы. С уважением. Натан
Натан Келлер
3

Вот код @Vanuan на C ++:

cv::cvtColor(mat, mat, CV_BGR2GRAY);
cv::GaussianBlur(mat, mat, cv::Size(3,3), 0);
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Point(9,9));
cv::Mat dilated;
cv::dilate(mat, dilated, kernel);

cv::Mat edges;
cv::Canny(dilated, edges, 84, 3);

std::vector<cv::Vec4i> lines;
lines.clear();
cv::HoughLinesP(edges, lines, 1, CV_PI/180, 25);
std::vector<cv::Vec4i>::iterator it = lines.begin();
for(; it!=lines.end(); ++it) {
    cv::Vec4i l = *it;
    cv::line(edges, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), cv::Scalar(255,0,0), 2, 8);
}
std::vector< std::vector<cv::Point> > contours;
cv::findContours(edges, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_TC89_KCOS);
std::vector< std::vector<cv::Point> > contoursCleaned;
for (int i=0; i < contours.size(); i++) {
    if (cv::arcLength(contours[i], false) > 100)
        contoursCleaned.push_back(contours[i]);
}
std::vector<std::vector<cv::Point> > contoursArea;

for (int i=0; i < contoursCleaned.size(); i++) {
    if (cv::contourArea(contoursCleaned[i]) > 10000){
        contoursArea.push_back(contoursCleaned[i]);
    }
}
std::vector<std::vector<cv::Point> > contoursDraw (contoursCleaned.size());
for (int i=0; i < contoursArea.size(); i++){
    cv::approxPolyDP(Mat(contoursArea[i]), contoursDraw[i], 40, true);
}
Mat drawing = Mat::zeros( mat.size(), CV_8UC3 );
cv::drawContours(drawing, contoursDraw, -1, cv::Scalar(0,255,0),1);
GBF_Gabriel
источник
Где определение переменных строк? Должны быть строки std :: vector <cv :: Vec4i>;
Can Ürek
@ CanÜrek Вы правы. std::vector<cv::Vec4i> lines;объявлен в моем проекте в глобальной области видимости.
GBF_Gabriel
1
  1. Преобразовать в лабораторное пространство

  2. Использовать кластер сегмента 2 kmeans

  3. Затем используйте контуры или hough на одном из кластеров (внутренний)
пользователь3452134
источник