Алгоритм виявлення кутів аркуша паперу на фотографії


97

Який найкращий спосіб виявити кути рахунку-фактури / квитанції / аркуша паперу на фотографії? Це потрібно використовувати для подальшої перспективної корекції перед OCR.

Мій поточний підхід:

RGB> Grey> Виявлення канти канти з пороговим значенням> Dilate (1)> Видалення невеликих предметів (6)> очищення об'єктів бордюру> підбір блоків великих розмірів на основі опуклої області. > [виявлення кута - не реалізовано]

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

Ширший контекст:

Я використовую матлаб для прототипу і планую впровадити систему в 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 )

Заздалегідь дякую за всі чудові ідеї! Я ТАК люблю!

EDIT: Прогрес в ході трансформації

Питання: Який алгоритм кластеризує лінії хоуга, щоб знайти кути? Дотримуючись порад із відповідей, я зміг скористатися Hough Transform, вибрати лінії та відфільтрувати їх. Мій сучасний підхід досить грубий. Я зробив припущення, що рахунок-фактура завжди буде менше 15 градусів, не вирівнюючи зображення. Я закінчую розумні результати для рядків, якщо це так (див. Нижче). Але я не зовсім впевнений у підходящому алгоритмі кластеризації ліній (або голосування) для екстраполяції за кутами. Лінії Хафа не є суцільними. І в галасливих зображеннях можуть бути паралельні лінії, тому потрібна певна форма або відстань від метрики початку лінії. Якісь ідеї?

випадок 1 випадок 2 випадок 3 випадок 4
(джерело: madteckhead.com )


1
Так, я працював приблизно в 95% випадків. З тих пір мені довелося зберігати код через нестачу часу. На деякому етапі я опублікую спостереження, сміливо запрошуйте мене, якщо вам потрібна термінова допомога. Вибачте за відсутність хорошого спостереження. Я хотів би повернутися до роботи над цією функцією.
Натан Келлер

Натане, ти можеш, будь-ласка, опублікувати наступні відомості про те, як ти це зробив? Я застряг в тій же точці, розпізнаючи кути / зовнішній контур аркуша паперу. У мене виникають ті самі проблеми, що і ви, тому я дуже зацікавлений у вирішенні.
час

6
Усі образи в цій публікації зараз 404.
ChrisF

Відповіді:


28

Я друг Мартіна, який працював над цим на початку цього року. Це був мій перший проект кодування, який коли-небудь був, і щось закінчилося трохи поспіхом, тому код потребує деякої помилки ... розшифровки ... Я дам кілька порад з того, що я бачив, як ви вже робили, а потім сортуйте мій код у вихідний день завтра.

Перша порада, OpenCVі pythonвони приголомшливі, рухайтеся до них якнайшвидше. : D

Замість видалення дрібних предметів і / або шуму, опустіть обмежувальні обмежувачі, щоб він прийняв більше країв, а потім знайдіть найбільший закритий контур (у OpenCV використанні findcontour()з деякими простими параметрами, я думаю, я використовував CV_RETR_LIST). Може все-таки боротися, коли це на білому аркуші паперу, але безумовно дає найкращі результати.

Для " Houghline2()Трансформації" спробуйте з CV_HOUGH_STANDARDпротилежним до значення CV_HOUGH_PROBABILISTIC, воно дасть значення rho і theta , визначаючи лінію в полярних координатах, а потім ви можете згрупувати лінії в межах певного допуску до них.

Моє групування працювало як таблиця пошуку, для кожного рядка, виведеного з перетворення Hough, це дало б пару rho і theta. Якщо ці значення були в межах, скажімо, 5% пари значень у таблиці, вони були відкинуті, якщо вони були за межами цих 5%, до таблиці було додано новий запис.

Потім можна зробити аналіз паралельних прямих чи відстань між лініями набагато простіше.

Сподіваюся, це допомагає.


Привіт, Даніеле, дякую за участь. Мені подобається, що ти підходиш. це насправді маршрут, Я отримую хороші результати на даний момент. Був навіть приклад OpenCV і те, що виявило прямокутники. Просто довелося зробити деяку фільтрацію результатів. як ви казали, біле на білому важко виявити за допомогою цього методу. Але це був простий і менш затратний підхід, ніж хоуг. Я насправді залишив підхід Hough поза своїм альго і зробив полі-апроксимацію, подивіться на приклад квадратів у opencv. Я хотів би побачити вашу реалізацію голосуючого голосування. Заздалегідь дякую, Натан
Натан Келлер

У мене виникли проблеми з таким підходом, я опублікую рішення, якщо зможу придумати щось краще для подальшої довідки
Аншуман Кумар

@AnshumanKumar Мені дуже потрібна допомога з цим питанням, чи можете ви мені допомогти? stackoverflow.com/questions/61216402/…
Карлос Дієго

19

Нещодавно студентська група в моєму університеті продемонструвала додаток iPhone (і додаток python OpenCV), яке вони написали, щоб зробити саме це. Як я пам’ятаю, кроки були приблизно такими:

  • Середній фільтр для повного видалення тексту на папері (це рукописний текст на білому папері з досить хорошим освітленням і може не працювати з друкованим текстом, він спрацював дуже добре). Причиною було те, що це значно полегшує виявлення кутів.
  • Hough Трансформація для ліній
  • Знайдіть вершини в просторі акумулятора Hough Transform і намалюйте кожну лінію на всьому зображенні.
  • Проаналізуйте лінії та видаліть всі, які дуже близькі один до одного та знаходяться під схожим кутом (згрупуйте лінії в одну). Це необхідно, оскільки трансформація Hough не є досконалою, оскільки вона працює в дискретному просторі зразка.
  • Знайдіть пари ліній, які приблизно паралельні, і які перетинаються інші пари, щоб побачити, які лінії утворюють квадри.

Це, здавалося, спрацювало досить добре, і вони змогли сфотографувати аркуш паперу чи книги, виконати кутове виявлення, а потім перенести документ у зображенні на плоску площину майже в режимі реального часу (була одна функція OpenCV для виконання відображення). Коли я побачив, що це працює, не було OCR.


Дякую за чудові ідеї Мартіне. Я скористався вашою порадою і застосував підхід трансформації Хаффа. (Див. Результати вище). Я намагаюся визначити надійний алгоритм, який буде екстраполювати лінії, щоб знайти перехрестя. Рядок не так багато, і кілька помилкових позитивних результатів. Чи є у вас поради, як я можу найкраще об'єднати та відкинути лінії? Якщо ваші студенти зацікавлені, будь ласка, заохочуйте їх контактувати. Я хотів би почути їх досвід щодо отримання алгоритмів для роботи на мобільній платформі. (Це моя наступна мета). Велике спасибі за ваші ідеї.
Nathan Keller

1
Схоже, що HT для ліній добре працює у всіх, крім вашого другого зображення, але ви визначаєте порогову допуску для початкових та кінцевих значень у акумуляторі? HT насправді не визначає початкові та кінцеві позиції, скоріше значення m та c у y = mx + c. Дивіться тут - зауважте, що для цього використовують полярні координати в акумуляторі, а не декартові. Таким чином ви можете згрупувати лінії по c, а потім по m, щоб їх витончити, і уявивши лінії, що розширюються по всьому зображенню, ви знайдете більше корисних перетинів.
Мартін Фут

@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 пропонує реалізацію алгоритму Моравеца. Ви можете знайти куточки паперів як вихідну точку. Нижче виходу алгоритму Моравека:

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


4

Також ви можете використовувати MSER (максимально стійкі крайні регіони) над оператором Sobel, щоб знайти стабільні області зображення. Для кожної області, що повертається MSER, ви можете застосувати опуклий корпус та полі-наближення, щоб отримати подібне:

Але такий спосіб виявлення корисний для виявлення в реальному часі, ніж однієї картини, яка не завжди дає найкращий результат.

результат


1
Ви можете поділитися деталями цього, можливо, деякого коду, дякую заздалегідь
Монті

Я отримую помилку в cv2.CHAIN_APPROX_SIMPLE, що говорить занадто багато значень для розпакування. Будь-яка ідея? Я використовую образ 1024 * 1024 як свій зразок
Praveen

1
Спасибі всім, просто зрозумів, зміни синтаксису в поточному OpenCV гілки answers.opencv.org/question/40329 / ...
Praveen

Чи не MSER призначений для видобування крапель? Я спробував це, і він виявляє більшість тексту лише
Аншуман Кумар

3

Після виявлення краю використовуйте Hough Transform. Потім поставте ці точки у 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);

Де визначається змінне рядків? Має бути std :: vector <cv :: Vec4i> рядки;
Can Ürek

@ CanÜrek Ви маєте рацію. std::vector<cv::Vec4i> lines;в моєму проекті оголошено в глобальному масштабі.
GBF_Gabriel

1
  1. Перетворити в лабораторний простір

  2. Використовуйте кластер kmeans segment 2

  3. Потім використовуйте контури або Hough на одному з кластерів (інтеральний)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.