사진에서 종이 시트의 모서리를 감지하는 알고리즘


97

사진에서 인보이스 / 영수증 / 종이의 모서리를 감지하는 가장 좋은 방법은 무엇입니까? 이것은 OCR 이전의 후속 원근 교정에 사용됩니다.

내 현재 접근 방식은 다음과 같습니다.

RGB> 회색> 임계 값을 사용한 캐니 가장자리 감지> 확장 (1)> 작은 개체 제거 (6)> 경계 개체 지우기> 볼록 영역을 기반으로 큰 블로그 선택. > [코너 감지-구현되지 않음]

나는 도울 수 없지만 이러한 유형의 세분화를 처리하기 위해 더 강력한 '지능적'/ 통계적 접근 방식이 있어야한다고 생각합니다. 훈련 예제는 많지 않지만 아마 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 )

모든 훌륭한 아이디어에 미리 감사드립니다! 너무 좋아!

편집 : 허프 변환 진행

Q : 모서리를 찾기 위해 허프 라인을 클러스터링하는 알고리즘은 무엇입니까? 답변의 조언에 따라 Hough Transform을 사용하고 선을 선택하고 필터링 할 수있었습니다. 나의 현재 접근 방식은 다소 조잡합니다. 송장이 항상 이미지와 일치하지 않는 15deg 미만일 것이라고 가정했습니다. 이 경우 라인에 대해 합리적인 결과를 얻습니다 (아래 참조). 그러나 모서리를 추정하기 위해 선을 클러스터링 (또는 투표)하는 데 적합한 알고리즘이 확실하지 않습니다. Hough 라인은 연속적이지 않습니다. 노이즈가 많은 이미지에는 평행선이있을 수 있으므로 선 원점 메트릭과의 거리 또는 형태가 필요합니다. 어떤 아이디어?

사례 1 사례 2 사례 3 사례 4
(출처 : madteckhead.com )


1
예, 약 95 %의 경우에서 작동하도록했습니다. 그 이후로 시간 부족으로 코드를 은폐해야했습니다. 나는 어떤 단계에서 후속 조치를 게시 할 것입니다. 긴급한 도움이 필요하면 언제든지 저에게 의뢰하십시오. 좋은 후속 조치가 없어서 죄송합니다. 이 기능에 대해 다시 작업하고 싶습니다.
Nathan Keller

Nathan, 어떻게하게되었는지에 대한 후속 조치를 게시 해 주시겠습니까? 나는 종이의 모서리 / 바깥 쪽 윤곽을 인식하는 동일한 지점에 붙어 있습니다. 나는 당신과 똑같은 문제에 부딪 히므로 해결책에 매우 관심이 있습니다.
tim

6
이 게시물의 모든 이미지는 현재 404입니다.
ChrisF

답변:


28

저는 올해 초이 작업을했던 Martin의 친구입니다. 이것은 저의 첫 코딩 프로젝트 였고 약간 서두르기로 끝났기 때문에 코드에 약간의 errr ... 디코딩이 필요합니다. 제가 이미 봤던 작업에서 몇 가지 팁을 제공 할 것입니다. 내일 쉬는 날에 내 코드를 정렬합니다.

첫 번째 팁, OpenCV그리고 python굉장합니다. 가능한 한 빨리 그들에게 이동하십시오. :디

작은 물체 및 / 또는 소음을 제거하는 대신 캐니 구속을 낮추어 더 많은 모서리를 허용 한 다음 가장 큰 닫힌 윤곽선을 찾습니다 (OpenCV findcontour()에서 몇 가지 간단한 매개 변수와 함께 사용 한다고 생각합니다 CV_RETR_LIST). 흰색 종이에 있으면 여전히 어려움을 겪을 수 있지만 확실히 최상의 결과를 제공했습니다.

(가)의 경우 Houghline2()변환으로 시도 CV_HOUGH_STANDARD받는 반대로 CV_HOUGH_PROBABILISTIC, 그것은 거주고 , ρ세타 값, 극좌표의 라인을 정의하고 그룹화 할 수있는 것과 특정 허용 오차 내에서 라인.

내 그룹화는 hough 변환에서 출력 된 각 행에 대해 rho와 theta 쌍을 제공하는 조회 테이블로 작동했습니다. 이 값이 테이블에있는 값 쌍의 5 % 내에 있으면 버려지고 5 % 밖에 있으면 새 항목이 테이블에 추가됩니다.

그런 다음 평행선이나 선 사이의 거리를 훨씬 더 쉽게 분석 할 수 있습니다.

도움이 되었기를 바랍니다.


안녕하세요 Daniel, 참여해 주셔서 감사합니다. 나는 당신이 접근하는 것을 좋아합니다. 그것은 실제로 Im이 현재 좋은 결과를 얻는 경로입니다. 사각형을 감지 한 OpenCV 예제도있었습니다. 결과에 대해 몇 가지 필터링을 수행해야했습니다. 흰색의 흰색은이 방법으로 감지하기 어렵다고 말했듯이. 그러나 그것은 허프보다 간단하고 비용이 적게 드는 접근법이었습니다. 나는 실제로 hough 접근 방식을 내 알고리즘에서 벗어 났고 폴리 근사를 수행했으며 opencv의 사각형 예제를 살펴보십시오. 허프 투표의 구현을보고 싶습니다. 미리 감사드립니다. Nathan
Nathan Keller

나는 미래의 참조를 위해 더 나은 뭔가를 고안 할 수 있다면이 방법으로 문제가 있었다, 나는 해결책을 게시 할 예정입니다
Anshuman 쿠마

@AnshumanKumar 저는이 질문에 대한 도움이 정말 필요합니다. 제발 도와 주 시겠어요? stackoverflow.com/questions/61216402/…
Carlos Diego

19

우리 대학의 한 학생 그룹은 최근에 정확히 이것을 수행하기 위해 작성한 iPhone 앱 (및 Python OpenCV 앱)을 시연했습니다. 내가 기억하는 것처럼 단계는 다음과 같았습니다.

  • 용지의 텍스트를 완전히 제거하기위한 중앙값 필터 (조명이 상당히 좋은 백서에 손으로 쓴 텍스트였으며 인쇄 된 텍스트에서는 작동하지 않을 수 있으며 매우 잘 작동했습니다). 그 이유는 코너 감지가 훨씬 쉬워 졌기 때문입니다.
  • 선에 대한 허프 변환
  • Hough Transform 누산기 공간에서 피크를 찾고 전체 이미지에 걸쳐 각 선을 그립니다.
  • 선을 분석하고 서로 매우 가깝고 비슷한 각도에있는 선을 제거합니다 (선을 하나로 묶습니다). 이것은 Hough Transform이 개별 샘플 공간에서 작동하므로 완벽하지 않기 때문에 필요합니다.
  • 대략 평행하고 다른 쌍과 교차하는 선 쌍을 찾아 어떤 선이 쿼드를 형성하는지 확인합니다.

이것은 꽤 잘 작동하는 것 같았고 그들은 종이나 책의 사진을 찍고 모서리 감지를 수행 한 다음 거의 실시간으로 이미지의 문서를 평평한 평면에 매핑 할 수있었습니다 (실행할 단일 OpenCV 기능이있었습니다. 매핑). 작동하는 것을 보았을 때 OCR이 없었습니다.


훌륭한 아이디어 Martin에 감사드립니다. 귀하의 조언을 받아 Hough 변환 접근 방식을 구현했습니다. (위의 결과 참조). 교차점을 찾기 위해 선을 외삽 할 강력한 알고리즘을 결정하기 위해 고군분투하고 있습니다. 줄이 많지 않고 오 탐지가 적습니다. 줄을 병합하고 버리는 가장 좋은 방법에 대한 조언이 있습니까? 학생들이 관심이 있으시면 연락하도록 격려해주십시오. 알고리즘을 모바일 플랫폼에서 실행하는 경험을 듣고 싶습니다. (그것이 나의 다음 목표입니다). 귀하의 아이디어에 감사드립니다.
Nathan Keller 2011

1
선에 대한 HT가 두 번째 이미지를 제외하고 모두 잘 작동 한 것처럼 보이지만 누산기에서 시작 및 끝 값에 대한 임계 값 허용 오차를 정의하고 있습니까? HT는 실제로 시작 및 끝 위치를 정의하지 않고 y = mx + c의 m 및 c 값을 정의합니다. 여기를 참조 하십시오 -이것은 데카르트가 아닌 누산기에서 극좌표를 사용하고 있습니다. 이런 식으로 선을 c로 그룹화 한 다음 m으로 그룹화하여 선을 얇게 만들 수 있으며 선이 전체 이미지에 걸쳐 확장되는 것으로 상상하면 더 유용한 교차점을 찾을 수 있습니다.
Martin Foot

@MartinFoot이 질문에 대한 도움이 정말 필요합니다. 제발 도와 주 시겠어요? stackoverflow.com/questions/61216402/…
Carlos Diego

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 삼 4


4
비슷한 프로젝트를 진행 중입니다. 코드 위를 실행하면 "No module named cv"라는 오류 메시지가 표시됩니다. Open CV 2.4 버전을 설치했고 cv2 가져 오기가 완벽하게 작동합니다.
Navneet Singh

이 코드가 작동하도록 업데이트 해주시겠습니까? pastebin.com/PMH5Y0M8 그냥 검은 색 페이지 만 표시됩니다.
the7erm 19

: 당신은 자바에 다음 코드를 변환하는 방법에 대한 어떤 생각이 있나요 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

Vanuan 저는이 질문에 대해 정말 도움이 필요합니다. 도와 주 시겠어요? stackoverflow.com/questions/61216402/…
Carlos Diego

9

가장자리 감지에서 시작하는 대신 모서리 감지를 사용할 수 있습니다.

Marvin Framework 는 이러한 목적으로 Moravec 알고리즘의 구현을 제공합니다. 논문의 모서리를 출발점으로 찾을 수 있습니다. Moravec의 알고리즘 출력 아래 :

여기에 이미지 설명 입력


4

또한 Sobel 연산자 결과에 대해 MSER (최대 안정 극단 영역)를 사용하여 이미지의 안정적인 영역을 찾을 수 있습니다. MSER에서 반환 된 각 영역에 대해 볼록 껍질 및 폴리 근사를 적용하여 다음과 같은 일부를 얻을 수 있습니다.

그러나 이러한 종류의 감지는 항상 최상의 결과를 반환하지 않는 단일 사진보다 라이브 감지에 유용합니다.

결과


1
이 코드에 대한 자세한 내용을 공유해 주
Monty

cv2.CHAIN_APPROX_SIMPLE에서 압축을 풀기에 너무 많은 값을 말하는 오류가 발생합니다. 어떤 생각? 1024 * 1024 이미지를 샘플로 사용하고 있습니다
Praveen

1
감사합니다. 현재 Opencv 브랜치에서 구문 변경 사항을 알아 냈습니다. answers.opencv.org/question/40329/…
Praveen

MSER는 Blob을 추출하기위한 것이 아닙니까? 나는 그것을 시도하고 텍스트의 대부분을 단지 감지
Anshuman 쿠마을

3

에지 감지 후 Hough Transform을 사용합니다. 그런 다음 해당 포인트를 레이블과 함께 SVM (지원 벡터 머신)에 넣습니다. 예제에 부드러운 선이 있으면 SVM은 예제에서 필요한 부분과 다른 부분을 나누는 데 어려움이 없습니다. SVM에 대한 나의 조언은 연결 및 길이와 같은 매개 변수를 입력하십시오. 즉, 포인트가 연결되고 길면 영수증의 선이 될 가능성이 높습니다. 그런 다음 다른 모든 점을 제거 할 수 있습니다.


안녕하세요 Ares, 귀하의 아이디어에 감사드립니다! Hough 변환을 구현했습니다 (위 참조). 잘못된 긍정과 비 연속적인 선이 주어지면 모서리를 찾는 강력한 방법을 찾을 수 없습니다. 추가 아이디어가 있습니까? SVM 기술을 살펴본 지 오래되었습니다. 감독 된 접근 방식입니까? 훈련 데이터는 없지만 일부를 생성 할 수 있습니다. SVM에 대해 더 많이 배우고 싶은만큼 접근 방식을 탐구하는 데 관심이 있습니다. 자원을 추천 해 주시겠습니까? 감사합니다. 나단
나단 켈러

3

여기 C ++를 사용하는 @Vanuan 코드가 있습니다.

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 2014-08-18

@ CanÜrek 당신이 맞습니다. std::vector<cv::Vec4i> lines;내 프로젝트에서 전역 범위로 선언되었습니다.
GBF_Gabriel 2014 년

1
  1. 실험실 공간으로 전환

  2. kmeans 세그먼트 2 클러스터 사용

  3. 그런 다음 클러스터 중 하나 (내부)에 윤곽선 또는 허프를 사용합니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.