c++ ओपनसीवी सी++/ओबीजे-सी: पेपर/स्क्वायर डिटेक्शन की शीट का पता लगाना




objective-c image-processing (4)

कागज की चादर का पता लगाना पुराना स्कूल है। यदि आप स्क्व्यू डिटेक्शन से निपटना चाहते हैं तो बेहतर होगा यदि आप सीधे टेक्स्ट लाइन पहचान के लिए लक्ष्य रखते हैं। इसके साथ आप एक्स्ट्रेमा बाएं, दाएं, ऊपर और नीचे पाएंगे। यदि आप नहीं चाहते हैं तो छवि में किसी भी ग्राफिक्स को छोड़ दें और फिर सबसे अधिक कोण कोण श्रेणी या कोण को खोजने के लिए टेक्स्ट लाइन सेगमेंट पर कुछ आंकड़े करें। इस तरह आप एक अच्छे skew कोण को संकुचित करेंगे। इसके बाद आप इन पैरामीटर को स्क्व्यू कोण और एक्स्ट्रेमा को डेस्क्यू पर डाल दें और छवि को जरूरी चीज़ों को काट लें।

वर्तमान छवि आवश्यकता के लिए, यदि आप CV_RETR_LIST के बजाय CV_RETR_EXTERNAL का प्रयास करते हैं तो यह बेहतर होता है।

किनारों का पता लगाने का एक और तरीका पेपर किनारों पर एक यादृच्छिक वन वर्गीकरण को प्रशिक्षित करना है और फिर किनारे के मानचित्र को प्राप्त करने के लिए क्लासिफायर का उपयोग करना है। यह अब तक एक मजबूत विधि है लेकिन प्रशिक्षण और समय की आवश्यकता है।

यादृच्छिक वन कम विपरीत अंतर परिदृश्यों के साथ काम करेंगे उदाहरण के लिए मोटे तौर पर सफेद पृष्ठभूमि पर श्वेत पत्र।

मैंने अपने परीक्षण अनुप्रयोग में ओपनसीवी स्क्वायर-डिटेक्शन उदाहरण सफलतापूर्वक कार्यान्वित किया, लेकिन अब आउटपुट फ़िल्टर करने की आवश्यकता है, क्योंकि यह शांत गड़बड़ है - या मेरा कोड गलत है?

मुझे पेपर के चार कोने बिंदुओं में स्की कमी (जैसे) और रुचि के लिए दिलचस्पी है ...

इनपुट आउटपुट:

मूल छवि:

click

कोड:

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

17/08/2012 संपादित करें:

छवि पर ज्ञात वर्गों को आकर्षित करने के लिए इस कोड का उपयोग करें:

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

खैर, मुझे देर हो चुकी है।

आपकी छवि में, कागज white , जबकि पृष्ठभूमि colored । तो, HSV color space में पेपर का पता लगाना बेहतर है Saturation(饱和度) चैनल। पहले विकी HSL_and_HSV संदर्भ लें। फिर मैं एक छवि में इस रंगीन सेगमेंट का पता लगाने में अपने उत्तर से अधिकतर विचारों की प्रतिलिपि बनाउंगा

मुख्य कदम:

  1. BGR में पढ़ें
  2. छवि को bgr से hsv space में कनवर्ट करें
  3. थ्रेसहोल्ड एस चैनल
  4. फिर कोनों को प्राप्त करने के लिए अधिकतम बाहरी समोच्च (या Canny , या HoughLines को अपनी पसंद के अनुसार करें, मैं findContours चयन करता findContours )।

यह मेरा परिणाम है:

पायथन कोड (पायथन 3.5 + ओपनसीवी 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)
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. एक छवि में रंगीन सेगमेंट का पता लगाएं
  2. ओपनसीवी एंड्रॉइड में एज डिटेक्शन
  3. ओपनसीवी सी ++ / ओबीजे-सी: पेपर / स्क्वायर डिटेक्शन की शीट का पता लगाना

जब तक कि कुछ अन्य आवश्यकता निर्दिष्ट नहीं होती है, मैं बस अपनी रंगीन छवि को ग्रेस्केल में परिवर्तित कर दूंगा और केवल उस के साथ काम करूंगा (3 चैनलों पर काम करने की कोई आवश्यकता नहीं है, इसके विपरीत उपस्थिति पहले से ही बहुत अधिक है)। साथ ही, जब तक कि आकार बदलने के संबंध में कुछ विशिष्ट समस्या न हो, मैं आपकी छवियों के डाउनस्केल्ड संस्करण के साथ काम करूंगा, क्योंकि वे अपेक्षाकृत बड़े हैं और आकार हल होने वाली समस्या को कुछ भी नहीं जोड़ता है। फिर, आखिरकार, आपकी समस्या को मध्यस्थ फ़िल्टर, कुछ बुनियादी रूपरेखा उपकरण, और आंकड़ों (ज्यादातर ओत्सु थ्रेसहोल्डिंग के लिए हल किया जाता है, जो पहले से ही आपके लिए किया जाता है) के साथ हल किया जाता है।

यहां मैं अपनी नमूना छवि और कुछ अन्य छवियों के साथ प्राप्त करता हूं जो कागज़ की एक शीट के साथ मिलती है जो मैंने पाया:

मध्य फिल्टर का उपयोग अब, ग्रेस्केल, छवि से मामूली विवरण को निकालने के लिए किया जाता है। यह संभवतः श्वेत पत्र के अंदर पतली रेखाओं को हटा देगा, जो कि अच्छा है क्योंकि तब आप छोटे जुड़े घटकों के साथ समाप्त हो जाएंगे जिन्हें त्यागना आसान है। औसत के बाद, एक मॉर्फोलॉजिकल ढाल (बस dilation - erosion ) लागू करें और ओत्सु द्वारा परिणाम को बिनराइज़ करें। मोर्फ़ोलॉजिकल ढाल मजबूत किनारों को रखने के लिए एक अच्छी विधि है, इसे और अधिक इस्तेमाल किया जाना चाहिए। फिर, चूंकि यह ढाल समोच्च चौड़ाई में वृद्धि करेगा, एक morphological पतला लागू करें। अब आप छोटे घटकों को त्याग सकते हैं।

इस बिंदु पर, यहां हमारे पास सही छवि (नीली बहुभुज को चित्रित करने से पहले) के साथ है, बाएं को दिखाया नहीं गया है क्योंकि केवल शेष घटक पेपर का वर्णन करने वाला है:

उदाहरणों को देखते हुए, अब एकमात्र मुद्दा छोड़ा गया है जो घटकों और अन्य लोगों की तरह दिखने वाले घटकों के बीच अंतर कर रहा है। यह आवरण और उसके बाउंडिंग बॉक्स के क्षेत्र वाले उत्तल हल के क्षेत्र के बीच अनुपात निर्धारित करने का विषय है; अनुपात 0.7 इन उदाहरणों के लिए ठीक काम करता है। यह मामला हो सकता है कि आपको कागज़ के अंदर मौजूद घटकों को त्यागने की भी आवश्यकता है, लेकिन इस विधि का उपयोग करके इन उदाहरणों में नहीं (फिर भी, यह कदम करना बहुत आसान होना चाहिए क्योंकि विशेष रूप से इसे ओपनसीवी के माध्यम से किया जा सकता है)।

संदर्भ के लिए, यहां गणित में एक नमूना कोड है:

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

यदि वहां कई अलग-अलग स्थितियां हैं जहां पेपर का आयताकार इतना अच्छी तरह परिभाषित नहीं है, या दृष्टिकोण इसे अन्य आकारों से भ्रमित करता है - ये स्थितियां विभिन्न कारणों से हो सकती हैं, लेकिन एक आम कारण खराब छवि अधिग्रहण है - फिर पूर्व संयोजन का प्रयास करें पेपर में वर्णित काम के साथ कदमों को प्रोसेस करना "खिड़की वाले आटा ट्रांसफॉर्म पर आधारित आयत का पता लगाने"।


यह स्टैक ओवरफ्लो में एक आवर्ती विषय है और चूंकि मैं एक प्रासंगिक कार्यान्वयन नहीं ढूंढ पाया, मैंने चुनौती स्वीकार करने का फैसला किया।

मैंने OpenCV में मौजूद वर्ग डेमो में कुछ संशोधन किए हैं और परिणामस्वरूप सी ++ कोड नीचे छवि में पेपर की शीट का पता लगाने में सक्षम है:

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> > में सबसे बड़ा वर्ग होगा:

मैं आपको सबसे बड़ा वर्ग खोजने के लिए फ़ंक्शन लिखने दे रहा हूं। ;)





computer-vision