image processing फोटो में पेपर शीट के कोनों का पता लगाने के लिए एल्गोरिदम




image-processing opencv (7)

एक फोटो में चालान / रसीद / शीट-ऑफ-पेपर के कोनों का पता लगाने का सबसे अच्छा तरीका क्या है? इसका उपयोग ओसीआर से पहले, बाद के परिप्रेक्ष्य सुधार के लिए किया जाना है।

मेरा वर्तमान दृष्टिकोण रहा है:

आरजीबी> ग्रे> थ्रेसहोल्डिंग के साथ कैनी एज डिटेक्शन> Dilate (1)> छोटी ऑब्जेक्ट्स निकालें (6)> साफ़ बोर्डर ऑब्जेक्ट्स> कन्वेक्स एरिया के आधार पर बड़े पैमाने पर ब्लॉग चुनें। > [कोने का पता लगाने - लागू नहीं किया गया]

मैं मदद नहीं कर सकता लेकिन सोचता हूं कि इस प्रकार के विभाजन को संभालने के लिए एक और अधिक मजबूत 'बुद्धिमान' / सांख्यिकीय दृष्टिकोण होना चाहिए। मेरे पास बहुत सारे प्रशिक्षण उदाहरण नहीं हैं, लेकिन मुझे शायद 100 छवियां मिल सकती हैं।

व्यापक संदर्भ:

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

यहां कुछ नमूना छवि दी गई है जिन्हें मैं एल्गोरिदम को संभालना चाहता हूं: यदि आप चुनौती लेना चाहते हैं तो बड़ी छवियां http://madteckhead.com/tmp

केस 1 http://madteckhead.com/tmp/IMG_0773_sml.jpg केस 2 http://madteckhead.com/tmp/IMG_0774_sml.jpg केस 3 http://madteckhead.com/tmp/IMG_0775_sml.jpg केस 4 http: / /madteckhead.com/tmp/IMG_0776_sml.jpg

सबसे अच्छे मामले में यह देता है:

केस 1 - कैनी http://madteckhead.com/tmp/IMG_0773_canny.jpg केस 1 - पोस्ट कैनी http://madteckhead.com/tmp/IMG_0773_postcanny.jpg केस 1 - सबसे बड़ा ब्लॉग http://madteckhead.com/tmp/ IMG_0773_blob.jpg

हालांकि यह अन्य मामलों पर आसानी से विफल रहता है:

केस 2 - कैनी http://madteckhead.com/tmp/IMG_0774_canny.jpg केस 2 - पोस्ट कैनी http://madteckhead.com/tmp/IMG_0774_postcanny.jpg केस 2 - सबसे बड़ा ब्लॉग http://madteckhead.com/tmp/ IMG_0774_blob.jpg

सभी महान विचारों के लिए अग्रिम धन्यवाद! मैं बहुत प्रेम करता हूँ!

संपादित करें: आटा ट्रांसफॉर्म प्रगति

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

केस 1 http://madteckhead.com/tmp/IMG_0773_hough.jpg केस 2 http://madteckhead.com/tmp/IMG_0774_hough.jpg केस 3 http://madteckhead.com/tmp/IMG_0775_hough.jpg केस 4 http: / /madteckhead.com/tmp/IMG_0776_hough.jpg


  1. प्रयोगशाला अंतरिक्ष में कनवर्ट करें

  2. केमन्स सेगमेंट 2 क्लस्टर का प्रयोग करें

  3. फिर क्लस्टर (अंतराल) में से एक पर समोच्च या आटा का उपयोग करें

इसके अलावा आप छवि के स्थिर क्षेत्रों को खोजने के लिए सोबेल ऑपरेटर के परिणाम पर MSER (अधिकतम स्थिर चरम क्षेत्रों) का उपयोग कर सकते हैं। एमएसईआर द्वारा लौटाए गए प्रत्येक क्षेत्र के लिए आप इस तरह कुछ प्राप्त करने के लिए उत्तल हल और पॉली सन्निकटन लागू कर सकते हैं:

लेकिन इस तरह की पहचान एक ऐसी तस्वीर से अधिक लाइव डिटेक्शन के लिए उपयोगी है जो हमेशा सर्वोत्तम परिणाम नहीं लौटाती है।


एज डिटेक्शन से शुरू करने के बजाय आप कॉर्नर डिटेक्शन का उपयोग कर सकते हैं।

मार्विन फ्रेमवर्क इस उद्देश्य के लिए मोरावेक एल्गोरिदम का कार्यान्वयन प्रदान करता है। आप शुरुआती बिंदु के रूप में कागजात के कोनों को पा सकते हैं। मोरावेक के एल्गोरिदम के आउटपुट के नीचे:


थोड़ी सी प्रयोग के बाद मैं यहां आया था:

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()

सही नहीं है, लेकिन कम से कम सभी नमूनों के लिए काम करता है:


मेरे विश्वविद्यालय के एक छात्र समूह ने हाल ही में एक आईफोन ऐप (और पायथन ओपनसीवी ऐप) प्रदर्शित किया है जिसे वे वास्तव में करने के लिए लिखा था। जैसा कि मुझे याद है, कदम इस तरह कुछ थे:

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

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


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


यहां आपके पास C ++ का उपयोग करके @ वानुआन कोड है:

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




image-segmentation