[image-processing] 사진에서 종이 시트의 모서리를 감지하는 알고리즘

사진에서 인보이스 / 영수증 / 종이의 모서리를 감지하는 가장 좋은 방법은 무엇입니까? 이것은 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 )



답변

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

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

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

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

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

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

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


답변

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

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

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


답변

다음은 약간의 실험 끝에 제가 생각 해낸 것입니다.

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


답변

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

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

여기에 이미지 설명 입력


답변

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

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

결과


답변

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


답변

여기 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);