OpenCV C ++ / Obj-C : 용지 감지 / 사각형 감지


178

테스트 응용 프로그램에서 OpenCV square-detection 예제를 성공적으로 구현했지만 출력이 매우 지저분하거나 내 코드가 잘못되었으므로 출력을 필터링해야합니다.

기울기 감소를 위해 종이의 네 모서리 지점에 관심 이 있습니다. ) 및 추가 처리 .

입출력: 입출력

원본 이미지 :

딸깍 하는 소리

암호:

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;
}

2012 년 8 월 17 일 편집 :

이미지에 감지 된 사각형을 그리려면이 코드를 사용하십시오.

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;
}


1
더 적절하다고 생각되면 질문 제목 을 용지 감지 와 같은 것으로 조정할 수 있다고 생각합니다.
karlphillip

1
@moosgummi 나는 당신이 구현 한 동일한 기능, 즉 "캡쳐 된 이미지 / 문서의 모서리를 감지"를 찾고 있습니다. 어떻게 이것을 달성 했습니까? iPhone 응용 프로그램에서 OpenCV를 사용할 수 있습니까? 이것 좀 더 나은 방법을 제안하십시오 ..
Ajay Sharma

1
OpenCV로 무언가 해본 적이 있습니까? 전혀 적용되지 않습니까?
karlphillip

6
닫힌 모양 내부의 모든 윤곽을 거부하는 계수를 찾을 때 CV_RETR_EXTERNAL 플래그를 사용할 수 있습니다.
mehfoos yacoob

답변:


162

이것은 Stackoverflow에서 되풀이되는 주제이며 관련 구현을 찾을 수 없으므로 도전을 수락하기로 결정했습니다.

OpenCV에있는 사각형 데모를 수정했으며 아래의 결과 C ++ 코드는 이미지에서 종이를 감지 할 수 있습니다.

void find_squares(Mat& image, vector<vector<Point> >& squares)
{
    // blur will enhance edge detection
    Mat blurred(image);
    medianBlur(image, blurred, 9);

    Mat gray0(blurred.size(), CV_8U), gray;
    vector<vector<Point> > contours;

    // find squares in every color plane of the image
    for (int c = 0; c < 3; c++)
    {
        int ch[] = {c, 0};
        mixChannels(&blurred, 1, &gray0, 1, ch, 1);

        // try several threshold levels
        const int threshold_level = 2;
        for (int l = 0; l < threshold_level; l++)
        {
            // Use Canny instead of zero threshold level!
            // Canny helps to catch squares with gradient shading
            if (l == 0)
            {
                Canny(gray0, gray, 10, 20, 3); // 

                // Dilate helps to remove potential holes between edge segments
                dilate(gray, gray, Mat(), Point(-1,-1));
            }
            else
            {
                    gray = gray0 >= (l+1) * 255 / threshold_level;
            }

            // Find contours and store them in a list
            findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

            // Test contours
            vector<Point> approx;
            for (size_t i = 0; i < contours.size(); i++)
            {
                    // approximate contour with accuracy proportional
                    // to the contour perimeter
                    approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true);

                    // Note: absolute value of an area is used because
                    // area may be positive or negative - in accordance with the
                    // contour orientation
                    if (approx.size() == 4 &&
                            fabs(contourArea(Mat(approx))) > 1000 &&
                            isContourConvex(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);
                    }
            }
        }
    }
}

이 절차가 실행 된 후 용지는 다음에서 가장 큰 사각형이됩니다 vector<vector<Point> >.

opencv 용지 감지

가장 큰 사각형을 찾는 함수를 작성할 수 있습니다. ;)


4
그래서 소스 컨트롤을 사용합니다. 실수로 코드를 수정 한 것을 가장 쉽게 발견 할 수 있습니다. 아무것도 변경하지 않았다면 다른 이미지로 테스트하고 opencv를 다시 컴파일 / 다시 설치하십시오.
karlphillip

2
OpenCV는 모든 플랫폼 (Win / Linux / Mac / iPhone / ...)에서 거의 동일합니다. 차이점은 일부는 OpenCV의 GPU 모듈을 지원하지 않는다는 것입니다. iOS 용 OpenCV를 이미 구축 했습니까 ? 테스트 할 수 있었습니까? 나는 이것들이 더 진보 된 것을 시도하기 전에 대답 해야하는 질문이라고 생각합니다. 걸음마!
karlphillip

1
@karlphillip 나는이 코드를 테스트했고 종이를 명확하게 감지 할 수 있었지만 너무 많은 시간이 걸립니다. 코드가 정말로 무겁습니까? 이 감지는 비디오 스트림에서 실시간으로 발생하는 SayText라는 앱이 있습니다. 이 코드는 실시간으로는 실용적이지 않습니다. 맞습니까?
alandalusi

1
아마. 이것은 학계의 답변이며 업계에서는 실용적이지 않습니다. 에 위치한 카운터의 정의부터 시작 for (int c = 0; c < 3; c++)하여 이미지의 모든 채널에서 반복하는 모든 종류의 최적화가 있습니다 . 예를 들어, 한 채널에서만 반복하도록 설정할 수 있습니다. :) 투표하는 것을 잊지 마십시오.
karlphillip

3
@SilentPro angle()도우미 기능 입니다. 답변에서 언급 했듯이이 코드는 OpenCV 에있는 samples / cpp / squares.cpp를 기반으로 합니다 .
karlphillip

40

지정되지 않은 다른 요구 사항이 없으면 컬러 이미지를 그레이 스케일로 변환하고 그와 만 작동합니다 (3 채널에서 작업 할 필요가 없으면 이미 대비가 너무 높습니다). 또한 크기 조정과 관련하여 특정 문제가 없으면 이미지의 축소 버전으로 작업하는 것이 좋습니다. 크기가 상대적으로 크고 크기가 해결되는 문제에 아무런 영향을 미치지 않기 때문입니다. 그런 다음 마지막으로 중앙값 필터, 일부 기본 형태 학적 도구 및 통계 (주로 이미 수행 된 Otsu 임계 값에 대한)로 문제를 해결합니다.

다음은 샘플 이미지와 내가 찾은 종이가있는 다른 이미지로 얻은 것입니다.

여기에 이미지 설명을 입력하십시오 여기에 이미지 설명을 입력하십시오

중간 값 필터는 현재 회색조 이미지에서 사소한 세부 사항을 제거하는 데 사용됩니다. 희끄무레 한 용지 내부의가는 선을 제거 할 수 있습니다. 폐기하기 쉬운 작은 연결 부품으로 끝나기 때문에 좋습니다. 중간 값 후에 형태 그라디언트를 적용하십시오 (간단히 dilation-erosion ) 결과를 Otsu로 이진화합니다. 형태 그라디언트는 강한 모서리를 유지하는 좋은 방법이므로 더 많이 사용해야합니다. 그런 다음이 그라디언트는 컨투어 폭을 증가 시키므로 형태 적 숱을 적용하십시오. 이제 작은 구성 요소를 버릴 수 있습니다.

이 시점에서, 위의 오른쪽 이미지 (파란색 다각형을 그리기 전에)로 가지고있는 것은 다음과 같습니다. 남은 구성 요소는 용지를 설명하는 구성 요소뿐이므로 왼쪽은 표시되지 않습니다.

여기에 이미지 설명을 입력하십시오

예를 들어, 이제 남은 유일한 문제는 사각형처럼 보이는 구성 요소와 그렇지 않은 구성 요소를 구별하는 것입니다. 이것은 모양을 포함하는 볼록 껍질의 면적과 경계 상자의 면적 사이의 비율을 결정하는 문제입니다. 이 예에서는 비율 0.7이 잘 작동합니다. 이 방법을 사용하여 종이 안에있는 구성 요소를 버려야하지만이 예에서는 그렇지 않은 구성 요소도 폐기해야 할 수도 있습니다 (그러나이 단계를 수행하는 것은 특히 OpenCV를 통해 직접 수행 할 수 있기 때문에 매우 쉬워야합니다).

참고로 Mathematica의 샘플 코드는 다음과 같습니다.

f = Import["http://thwartedglamour.files.wordpress.com/2010/06/my-coffee-table-1-sa.jpg"]
f = ImageResize[f, ImageDimensions[f][[1]]/4]
g = MedianFilter[ColorConvert[f, "Grayscale"], 2]
h = DeleteSmallComponents[Thinning[
     Binarize[ImageSubtract[Dilation[g, 1], Erosion[g, 1]]]]]
convexvert = ComponentMeasurements[SelectComponents[
     h, {"ConvexArea", "BoundingBoxArea"}, #1 / #2 > 0.7 &], 
     "ConvexVertices"][[All, 2]]
(* To visualize the blue polygons above: *)
Show[f, Graphics[{EdgeForm[{Blue, Thick}], RGBColor[0, 0, 1, 0.5], 
     Polygon @@ convexvert}]]

종이의 사각형이 잘 정의되지 않은 상황이 더 다양하거나 접근 방식이 다른 모양과 혼동되는 경우-이러한 상황은 여러 가지 이유로 발생할 수 있지만 일반적인 원인은 이미지 획득이 잘못된 것입니다. "윈도우 허프 변환 기반의 사각형 감지"용지에 설명 된 작업으로 처리 단계.


1
귀하의 구현과 위의 구현 (예 : @karlphilip의 답변)에 큰 차이가 있습니까? 3 채널 1 채널과 Mathematica-OpenCV를 제외하고는 빨리 볼 수 없어서 죄송합니다.
Abid Rahman K

2
@AbidRahmanK 그렇습니다. 나는 canny를 사용하여 "여러 임계 값"을 사용하지 않습니다. 다른 차이점이 있지만 의견의 어조로 내 의견에 노력을 기울이는 것은 의미가 없습니다.
mmgp

1
둘 다 먼저 가장자리를 찾아서 어느 가장자리가 정사각형인지 확인합니다. 가장자리를 찾기 위해 사람들은 다른 방법을 사용합니다. 그는 캐니를 사용하고 약간 침식 침식을 사용합니다. "여러 임계 값"은 사각형을 찾는 데 사용되는 OpenCV 샘플에서 얻을 수 있습니다. 가장 중요한 것은 전반적인 개념이 동일하다고 느꼈습니다. "가장자리를 찾아서 사각형을 감지". 그리고 나는 진심으로 그것을 물었다. 나는 당신이 내 의견에서 어떤 "톤"을 얻었는지, 또는 당신이 무엇을 이해했는지 (잘 이해하지 못했는지) 알지 못한다. 이 질문이 진지하다고 생각되면 다른 차이점을 알고 싶습니다. 그렇지 않으면 내 의견을 버립니다.
Abid Rahman K

1
물론 @AbidRahmanK 개념은 동일하며 작업은 동일합니다. 중앙값 필터링을 사용하고, 숱을 사용하고, 그가 몇 가지 임계 값 아이디어를 취한 곳에서 신경 쓰지 않습니다. 여기서는 사용되지 않습니다 (따라서 어떻게 차이가 될 수 없습니까?), 이미지의 크기가 여기에서 조정됩니다. 구성 요소 측정이 다릅니다. "일부 팽창-침식"은 이진 가장자리를 제공하지 않으며, otsu가 사용됩니다. 이것을 언급하는 것은 무의미합니다. 코드가 있습니다.
mmgp

1
K. 감사합니다. 답이 있습니다. Concept is the same. (나는 Mathematica를 사용한 적이 없으므로 코드를 이해할 수 없습니다.) 언급 한 차이점은 차이점이지만 다른 접근법이나 주요 차이점은 아닙니다. 그래도 예를 들어, 다음을 확인하십시오.
Abid Rahman K

14

글쎄, 난 늦었 어


이미지에서 용지는 white이고 배경은 colored입니다. 따라서 용지가 Saturation(饱和度)채널에 있는 것을 감지하는 것이 좋습니다 HSV color space. 먼저 위키 HSL_and_HSV를 참조하십시오 . 그런 다음 이미지의 컬러 세그먼트 감지 에서 내 답변에서 대부분의 아이디어를 복사 합니다 .


주요 단계 :

  1. 읽어 BGR
  2. 이미지 bgrhsv공간으로 변환
  3. S 채널 임계 값
  4. 그런 다음 모서리를 얻을 수있는 최대 외부 윤곽선 (또는 원하는 대로 Canny또는 원하는 HoughLines대로)을 찾으십시오 findContours.

이것은 내 결과입니다.

여기에 이미지 설명을 입력하십시오


파이썬 코드 (Python 3.5 + OpenCV 3.3) :

#!/usr/bin/python3
# 2017.12.20 10:47:28 CST
# 2017.12.20 11:29:30 CST

import cv2
import numpy as np

##(1) read into  bgr-space
img = cv2.imread("test2.jpg")

##(2) convert to hsv-space, then split the channels
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)

##(3) threshold the S channel using adaptive method(`THRESH_OTSU`) or fixed thresh
th, threshed = cv2.threshold(s, 50, 255, cv2.THRESH_BINARY_INV)

##(4) find all the external contours on the threshed S
#_, cnts, _ = cv2.findContours(threshed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cv2.findContours(threshed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]

canvas  = img.copy()
#cv2.drawContours(canvas, cnts, -1, (0,255,0), 1)

## sort and choose the largest contour
cnts = sorted(cnts, key = cv2.contourArea)
cnt = cnts[-1]

## approx the contour, so the get the corner points
arclen = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02* arclen, True)
cv2.drawContours(canvas, [cnt], -1, (255,0,0), 1, cv2.LINE_AA)
cv2.drawContours(canvas, [approx], -1, (0, 0, 255), 1, cv2.LINE_AA)

## Ok, you can see the result as tag(6)
cv2.imwrite("detected.png", canvas)

관련 답변 :

  1. OpenCV를 사용하여 이미지에서 컬러 패치를 감지하는 방법은 무엇입니까?
  2. OpenCV를 사용하여 컬러 배경에서 가장자리 감지
  3. OpenCV C ++ / Obj-C : 용지 감지 / 사각형 감지
  4. 다른 OpenCV 버전에서 'cv2.findContours'를 사용하는 방법은 무엇입니까?

S 공간을 사용해 보았지만 여전히 성공하지 못했습니다. 이것을보십시오 : stackoverflow.com/questions/50699893/…
hchouhan02

3

회전 사각형 대신 사각형 이 필요합니다 . RotatedRect잘못된 결과를 제공합니다. 또한 투시 투영이 필요합니다.

기본적으로 수행해야 할 일은 다음과 같습니다.

  • 모든 다각형 세그먼트를 반복하고 거의 비슷한 세그먼트를 연결합니다.
  • 가장 큰 4 개의 선 세그먼트를 갖도록 정렬하십시오.
  • 이 선을 교차하면 가장 가능성이 높은 4 개의 모퉁이 점이 있습니다.
  • 모퉁이 점에서 수집 된 원근과 알려진 객체의 종횡비로 매트릭스를 변환합니다.

수업을 진행했습니다 Quadrangle윤곽선을 사각형으로 변환하고 올바른 관점으로 변환 를 했습니다.

여기에서 작동하는 구현을 참조하십시오. Java OpenCV 기울기 보정


1

문서의 경계 상자를 감지하면 4 점 투시 변환 을 수행하여 이미지의 하향식 조감도를 얻을 수 있습니다. 이렇게하면 기울어 짐을 해결하고 원하는 객체 만 분리합니다.


입력 이미지 :

감지 된 텍스트 개체

텍스트 문서의 하향식보기

암호

from imutils.perspective import four_point_transform
import cv2
import numpy

# Load image, grayscale, Gaussian blur, Otsu's threshold
image = cv2.imread("1.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (7,7), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

# Find contours and sort for largest contour
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
displayCnt = None

for c in cnts:
    # Perform contour approximation
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    if len(approx) == 4:
        displayCnt = approx
        break

# Obtain birds' eye view of image
warped = four_point_transform(image, displayCnt.reshape(4, 2))

cv2.imshow("thresh", thresh)
cv2.imshow("warped", warped)
cv2.imshow("image", image)
cv2.waitKey()

-1

종이를 감지하는 것은 다소 오래된 학교입니다. 기울어 짐 감지 기능을 사용하려면 텍스트 라인 감지를 바로 목표로하는 것이 좋습니다. 이것으로 극한을 왼쪽, 오른쪽, 위쪽 및 아래쪽으로 가져옵니다. 원하지 않는 경우 이미지의 그래픽을 버리고 텍스트 선 세그먼트에 대한 통계를 수행하여 가장 많이 발생하는 각도 범위 또는 각도를 찾으십시오. 이것이 좋은 기울기 각도로 좁히는 방법입니다. 이제이 매개 변수를 기울이기 각도와 극한값으로 기울여 이미지를 기울이고 필요한 부분을 잘라냅니다.

현재 이미지 요구 사항은 CV_RETR_LIST 대신 CV_RETR_EXTERNAL을 사용하는 것이 좋습니다.

가장자리를 감지하는 또 다른 방법은 종이 가장자리에서 임의의 포리스트 분류기를 훈련시킨 다음 분류기를 사용하여 가장자리 맵을 얻는 것입니다. 이것은 훨씬 강력한 방법이지만 훈련과 시간이 필요합니다.

임의 포리스트는 대략 흰색 배경의 백서와 같이 대비가 낮은 시나리오에서 작동합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.