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

테스트 응용 프로그램에서 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;
}



답변

이것은 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 용지 감지

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


답변

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

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

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

중간 값 필터는 현재 회색조 이미지에서 사소한 세부 사항을 제거하는 데 사용됩니다. 희끄무레 한 용지 내부의가는 선을 제거 할 수 있습니다. 폐기하기 쉬운 작은 연결 부품으로 끝나기 때문에 좋습니다. 중간 값 후에 형태 그라디언트를 적용하십시오 (간단히 dilationerosion ) 결과를 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}]]

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


답변

글쎄, 난 늦었 어


이미지에서 용지는 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’를 사용하는 방법은 무엇입니까?

답변

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

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

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

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

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


답변

문서의 경계 상자를 감지하면 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()


답변

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

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

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

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


답변