c++ टेक्स्ट ओपनसीवी निकालना




opencv image-processing (6)

मैं एक छवि में पाठ के बाध्यकारी बक्से खोजने की कोशिश कर रहा हूं और वर्तमान में इस दृष्टिकोण का उपयोग कर रहा हूं:

// calculate the local variances of the grayscale image
Mat t_mean, t_mean_2;
Mat grayF;
outImg_gray.convertTo(grayF, CV_32F);
int winSize = 35;
blur(grayF, t_mean, cv::Size(winSize,winSize));
blur(grayF.mul(grayF), t_mean_2, cv::Size(winSize,winSize));
Mat varMat = t_mean_2 - t_mean.mul(t_mean);
varMat.convertTo(varMat, CV_8U);

// threshold the high variance regions
Mat varMatRegions = varMat > 100;

जब इस तरह की एक छवि दी गई:

फिर जब मैं varMatRegions दिखाता varMatRegions मुझे यह छवि मिलती है:

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

कनेक्ट करने के लिए उन समोच्चों के कारण यह बुरा है कि यह समोच्च के बाउंडिंग बॉक्स को पूरे कार्ड को लगभग ले जाता है।

क्या कोई पाठ का उचित पता लगाने के लिए पाठ को ढूंढने का एक अलग तरीका सुझा सकता है?

200 अंक जो भी इन दोनों के ऊपर कार्ड में पाठ पा सकते हैं।


आप करीबी किनारे तत्वों (एलपीडी से प्रेरित) ढूंढकर टेक्स्ट का पता लगा सकते हैं:

#include "opencv2/opencv.hpp"

std::vector<cv::Rect> detectLetters(cv::Mat img)
{
    std::vector<cv::Rect> boundRect;
    cv::Mat img_gray, img_sobel, img_threshold, element;
    cvtColor(img, img_gray, CV_BGR2GRAY);
    cv::Sobel(img_gray, img_sobel, CV_8U, 1, 0, 3, 1, 0, cv::BORDER_DEFAULT);
    cv::threshold(img_sobel, img_threshold, 0, 255, CV_THRESH_OTSU+CV_THRESH_BINARY);
    element = getStructuringElement(cv::MORPH_RECT, cv::Size(17, 3) );
    cv::morphologyEx(img_threshold, img_threshold, CV_MOP_CLOSE, element); //Does the trick
    std::vector< std::vector< cv::Point> > contours;
    cv::findContours(img_threshold, contours, 0, 1); 
    std::vector<std::vector<cv::Point> > contours_poly( contours.size() );
    for( int i = 0; i < contours.size(); i++ )
        if (contours[i].size()>100)
        { 
            cv::approxPolyDP( cv::Mat(contours[i]), contours_poly[i], 3, true );
            cv::Rect appRect( boundingRect( cv::Mat(contours_poly[i]) ));
            if (appRect.width>appRect.height) 
                boundRect.push_back(appRect);
        }
    return boundRect;
}

उपयोग:

int main(int argc,char** argv)
{
    //Read
    cv::Mat img1=cv::imread("side_1.jpg");
    cv::Mat img2=cv::imread("side_2.jpg");
    //Detect
    std::vector<cv::Rect> letterBBoxes1=detectLetters(img1);
    std::vector<cv::Rect> letterBBoxes2=detectLetters(img2);
    //Display
    for(int i=0; i< letterBBoxes1.size(); i++)
        cv::rectangle(img1,letterBBoxes1[i],cv::Scalar(0,255,0),3,8,0);
    cv::imwrite( "imgOut1.jpg", img1);  
    for(int i=0; i< letterBBoxes2.size(); i++)
        cv::rectangle(img2,letterBBoxes2[i],cv::Scalar(0,255,0),3,8,0);
    cv::imwrite( "imgOut2.jpg", img2);  
    return 0;
}

परिणाम:

ए। तत्व = getStructuringElement (सीवी :: MORPH_RECT, सीवी :: आकार (17, 3));

ख। तत्व = getStructuringElement (सीवी :: MORPH_RECT, सीवी :: आकार (30, 30));

परिणाम उल्लिखित अन्य छवि के लिए समान हैं।


आप इस विधि को आजमा सकते हैं जिसे चुकाई यी और यिंगली टियां द्वारा विकसित किया गया है।

वे एक सॉफ्टवेयर भी साझा करते हैं (जो ओपनसीवी-1.0 पर आधारित है और इसे विंडोज प्लेटफार्म के तहत चलाना चाहिए।) जिसका उपयोग आप कर सकते हैं (हालांकि कोई स्रोत कोड उपलब्ध नहीं है)। यह छवि में सभी पाठ बाध्यकारी बक्से (रंग छाया में दिखाया गया) उत्पन्न करेगा। अपनी नमूना छवियों पर आवेदन करके, आपको निम्नलिखित परिणाम मिलेंगे:

नोट: परिणाम को और अधिक मजबूत बनाने के लिए, आप आसन्न बक्से को एक साथ विलय कर सकते हैं।

अपडेट करें: यदि आपका अंतिम लक्ष्य छवि में ग्रंथों को पहचानना है, तो आप gttext को आगे देख सकते हैं, जो पाठ के साथ रंग छवियों के लिए एक ओसीआर मुक्त सॉफ्टवेयर और ग्राउंड ट्रुथिंग टूल है। स्रोत कोड भी उपलब्ध है।

इसके साथ, आप मान्यता प्राप्त ग्रंथों को प्राप्त कर सकते हैं जैसे:


@ धुनुष्का के समाधान के लिए पायथन कार्यान्वयन:

def process_rgb(rgb):
hasText = 0
gray = cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY);
morphKernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
grad = cv2.morphologyEx(gray, cv2.MORPH_GRADIENT, morphKernel)
# binarize
_, bw = cv2.threshold(grad, 0.0, 255.0, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
# connect horizontally oriented regions
morphKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1))
connected = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, morphKernel)
# find contours
mask = np.zeros(bw.shape[:2], dtype="uint8");
_,contours, hierarchy = cv2.findContours(connected, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
# filter contours
idx = 0
while idx >= 0:
    x,y,w,h = cv2.boundingRect(contours[idx]);
    # fill the contour
    cv2.drawContours(mask, contours, idx, (255, 255, 255), cv2.FILLED);
    # ratio of non-zero pixels in the filled region
    r = cv2.contourArea(contours[idx])/(w*h)
    if(r > 0.45 and h > 5 and w > 5 and w > h):
        cv2.rectangle(rgb, (x,y), (x+w,y+h), (0, 255, 0), 2)
        hasText = 1
    idx = hierarchy[0][idx][0]
return hasText, rgb

कोड जावा संस्करण से ऊपर: धन्यवाद @ विलियम

public static List<Rect> detectLetters(Mat img){    
    List<Rect> boundRect=new ArrayList<>();

    Mat img_gray =new Mat(), img_sobel=new Mat(), img_threshold=new Mat(), element=new Mat();
    Imgproc.cvtColor(img, img_gray, Imgproc.COLOR_RGB2GRAY);
    Imgproc.Sobel(img_gray, img_sobel, CvType.CV_8U, 1, 0, 3, 1, 0, Core.BORDER_DEFAULT);
    //at src, Mat dst, double thresh, double maxval, int type
    Imgproc.threshold(img_sobel, img_threshold, 0, 255, 8);
    element=Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(15,5));
    Imgproc.morphologyEx(img_threshold, img_threshold, Imgproc.MORPH_CLOSE, element);
    List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
    Mat hierarchy = new Mat();
    Imgproc.findContours(img_threshold, contours,hierarchy, 0, 1);

    List<MatOfPoint> contours_poly = new ArrayList<MatOfPoint>(contours.size());

     for( int i = 0; i < contours.size(); i++ ){             

         MatOfPoint2f  mMOP2f1=new MatOfPoint2f();
         MatOfPoint2f  mMOP2f2=new MatOfPoint2f();

         contours.get(i).convertTo(mMOP2f1, CvType.CV_32FC2);
         Imgproc.approxPolyDP(mMOP2f1, mMOP2f2, 2, true); 
         mMOP2f2.convertTo(contours.get(i), CvType.CV_32S);


            Rect appRect = Imgproc.boundingRect(contours.get(i));
            if (appRect.width>appRect.height) {
                boundRect.add(appRect);
            }
     }

    return boundRect;
}

और अभ्यास में इस कोड का प्रयोग करें:

        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        Mat img1=Imgcodecs.imread("abc.png");
        List<Rect> letterBBoxes1=Utils.detectLetters(img1);

        for(int i=0; i< letterBBoxes1.size(); i++)
            Imgproc.rectangle(img1,letterBBoxes1.get(i).br(), letterBBoxes1.get(i).tl(),new Scalar(0,255,0),3,8,0);         
        Imgcodecs.imwrite("abc1.png", img1);

यहां एक वैकल्पिक दृष्टिकोण है जिसका उपयोग मैंने टेक्स्ट ब्लॉक का पता लगाने के लिए किया था:

  1. छवि को ग्रेस्केल में कनवर्ट किया गया
  2. एप्लाइड threshold (सरल बाइनरी थ्रेसहोल्ड, 150 के थ्रेशोल्ड वैल्यू के रूप में एक हाथ से अंकित मूल्य के साथ)
  3. छवि में लाइनों को मोटा करने के लिए एप्लाइड dilation , जिससे अधिक कॉम्पैक्ट ऑब्जेक्ट्स और कम सफेद स्पेस टुकड़े होते हैं। पुनरावृत्तियों की संख्या के लिए एक उच्च मूल्य का उपयोग किया जाता है, इसलिए फैलाव बहुत भारी होता है (13 पुनरावृत्तियों, इष्टतम परिणामों के लिए भी handpicked)।
  4. findContours फ़ंक्शन का उपयोग करके परिणामस्वरूप छवि में ऑब्जेक्ट्स की पहचान की गई findContours
  5. प्रत्येक घुमावदार वस्तु को घेरने वाला एक बाउंडिंग बॉक्स (आयताकार) खींचा - उनमें से प्रत्येक पाठ का एक ब्लॉक तैयार करता है।
  6. वैकल्पिक रूप से त्याग किए गए क्षेत्र जो आपके द्वारा खोजे जा रहे ऑब्जेक्ट (उदाहरण के लिए टेक्स्ट ब्लॉक) होने की संभावना नहीं है, क्योंकि उपरोक्त एल्गोरिदम भी अंतरण या नेस्टेड ऑब्जेक्ट्स (पहले कार्ड के लिए पूरे शीर्ष क्षेत्र की तरह) ढूंढ सकता है, जिनमें से कुछ हो सकते हैं आपके उद्देश्यों के लिए अनिच्छुक।

नीचे pyopencv के साथ अजगर में लिखा गया कोड है, इसे सी ++ में पोर्ट करना आसान होना चाहिए।

import cv2

image = cv2.imread("card.png")
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) # grayscale
_,thresh = cv2.threshold(gray,150,255,cv2.THRESH_BINARY_INV) # threshold
kernel = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
dilated = cv2.dilate(thresh,kernel,iterations = 13) # dilate
_, contours, hierarchy = cv2.findContours(dilated,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE) # get contours

# for each contour found, draw a rectangle around it on original image
for contour in contours:
    # get rectangle bounding contour
    [x,y,w,h] = cv2.boundingRect(contour)

    # discard areas that are too large
    if h>300 and w>300:
        continue

    # discard areas that are too small
    if h<40 or w<40:
        continue

    # draw rectangle around contour on original image
    cv2.rectangle(image,(x,y),(x+w,y+h),(255,0,255),2)

# write original image with added contours to disk  
cv2.imwrite("contoured.jpg", image) 

मूल छवि आपकी पोस्ट में पहली छवि है।

प्रीप्रोकैसिंग के बाद (ग्रेस्केल, थ्रेसहोल्ड और फैलाएं - तो चरण 3 के बाद) छवि इस तरह दिखती है:

नीचे परिणामस्वरूप छवि (अंतिम पंक्ति में "contoured.jpg" है); छवि में वस्तुओं के लिए अंतिम बाउंडिंग बॉक्स इस तरह दिखते हैं:

आप बाईं ओर स्थित टेक्स्ट ब्लॉक को एक अलग ब्लॉक के रूप में देख सकते हैं, जो इसके आसपास से सीमित है।

उसी पैरामीटर के साथ एक ही स्क्रिप्ट का उपयोग करना (नीचे वर्णित दूसरी छवि के लिए थ्रेसहोल्डिंग प्रकार को छोड़कर), अन्य 2 कार्ड के परिणाम यहां दिए गए हैं:

पैरामीटर ट्यूनिंग

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

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

अन्य ऑब्जेक्ट प्रकार ढूंढना:

उदाहरण के लिए, पहली छवि में 5 पुनरावृत्तियों में फैलाव को कम करने से हमें छवि में वस्तुओं की एक अधिक अच्छी सीमा मिलती है, लगभग छवि में सभी शब्दों को ढूंढना (टेक्स्ट ब्लॉक के बजाए):

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


@ धुनुष्का के दृष्टिकोण ने सबसे अधिक वादा दिखाया लेकिन मैं पाइथन में चारों ओर खेलना चाहता था इसलिए आगे बढ़ गया और इसे मज़े के लिए अनुवादित किया:

import cv2
import numpy as np
from cv2 import boundingRect, countNonZero, cvtColor, drawContours, findContours, getStructuringElement, imread, morphologyEx, pyrDown, rectangle, threshold

large = imread(image_path)
# downsample and use it for processing
rgb = pyrDown(large)
# apply grayscale
small = cvtColor(rgb, cv2.COLOR_BGR2GRAY)
# morphological gradient
morph_kernel = getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
grad = morphologyEx(small, cv2.MORPH_GRADIENT, morph_kernel)
# binarize
_, bw = threshold(src=grad, thresh=0, maxval=255, type=cv2.THRESH_BINARY+cv2.THRESH_OTSU)
morph_kernel = getStructuringElement(cv2.MORPH_RECT, (9, 1))
# connect horizontally oriented regions
connected = morphologyEx(bw, cv2.MORPH_CLOSE, morph_kernel)
mask = np.zeros(bw.shape, np.uint8)
# find contours
im2, contours, hierarchy = findContours(connected, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
# filter contours
for idx in range(0, len(hierarchy[0])):
    rect = x, y, rect_width, rect_height = boundingRect(contours[idx])
    # fill the contour
    mask = drawContours(mask, contours, idx, (255, 255, 2555), cv2.FILLED)
    # ratio of non-zero pixels in the filled region
    r = float(countNonZero(mask)) / (rect_width * rect_height)
    if r > 0.45 and rect_height > 8 and rect_width > 8:
        rgb = rectangle(rgb, (x, y+rect_height), (x+rect_width, y), (0,255,0),3)

अब छवि प्रदर्शित करने के लिए:

from PIL import Image
Image.fromarray(rgb).show()

स्क्रिप्ट का सबसे पायथन नहीं है, लेकिन मैंने पाठकों के अनुसरण के लिए यथासंभव मूल सी ++ कोड जैसा दिखने की कोशिश की।

यह लगभग मूल के साथ ही काम करता है। मुझे सुझावों को पढ़ने में खुशी होगी कि इसे मूल परिणामों के समान रूप से कैसे सुधार / तय किया जा सकता है।







bounding-box