Text extrahieren OpenCV

Ich versuche, die umrahmenden Textfelder in einem Bild zu finden und verwende derzeit diesen Ansatz:

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

Wenn Sie ein Bild wie folgt erhalten:

Bildbeschreibung hier eingeben

Wenn ich dann varMatRegions zeige, varMatRegions ich dieses Bild:

Bildbeschreibung hier eingeben

Wie Sie sehen können, kombiniert es den linken Textblock mit dem Header der Karte, für die meisten Karten funktioniert diese Methode gut, aber auf belebten Karten kann es Probleme verursachen.

Der Grund, warum es für diese Konturen nicht möglich ist, eine Verbindung herzustellen, besteht darin, dass die Bounding Box der Kontur fast die gesamte Karte einnimmt.

Kann jemand einen anderen Weg vorschlagen, den Text zu finden, um eine korrekte Erkennung von Text zu gewährleisten?

200 Punkte, wer den Text in der Karte über den beiden findet.

Bildbeschreibung hier eingebenBildbeschreibung hier eingeben

Sie können Text erkennen, indem Sie nahe Kantenelemente finden (inspiriert von einem LPD):

 #include "opencv2/opencv.hpp" std::vector detectLetters(cv::Mat img) { std::vector 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 > 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; } 

Verwendung:

 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 letterBBoxes1=detectLetters(img1); std::vector 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; } 

Ergebnisse:

ein. element = getStructuringElement (cv :: MORPH_RECT, cv :: Größe (17, 3)); imgOut1imgOut2

b. Element = getStructuringElement (cv :: MORPH_RECT, cv :: Größe (30, 30)); imgOut1imgOut2

Die Ergebnisse sind für das andere Bild ähnlich.

Ich habe eine gradientenbasierte Methode im unten stehenden Programm verwendet. Die resultierenden Bilder hinzugefügt. Bitte beachten Sie, dass ich eine verkleinerte Version des Bildes zur Bearbeitung verwende.

C ++ Version

 The MIT License (MIT) Copyright (c) 2014 Dhanushka Dangampola Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "stdafx.h" #include  #include  #include  #include  using namespace cv; using namespace std; #define INPUT_FILE "1.jpg" #define OUTPUT_FOLDER_PATH string("") int _tmain(int argc, _TCHAR* argv[]) { Mat large = imread(INPUT_FILE); Mat rgb; // downsample and use it for processing pyrDown(large, rgb); Mat small; cvtColor(rgb, small, CV_BGR2GRAY); // morphological gradient Mat grad; Mat morphcoreel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3)); morphologyEx(small, grad, MORPH_GRADIENT, morphcoreel); // binarize Mat bw; threshold(grad, bw, 0.0, 255.0, THRESH_BINARY | THRESH_OTSU); // connect horizontally oriented regions Mat connected; morphcoreel = getStructuringElement(MORPH_RECT, Size(9, 1)); morphologyEx(bw, connected, MORPH_CLOSE, morphcoreel); // find contours Mat mask = Mat::zeros(bw.size(), CV_8UC1); vector> contours; vector hierarchy; findContours(connected, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0)); // filter contours for(int idx = 0; idx >= 0; idx = hierarchy[idx][0]) { Rect rect = boundingRect(contours[idx]); Mat maskROI(mask, rect); maskROI = Scalar(0, 0, 0); // fill the contour drawContours(mask, contours, idx, Scalar(255, 255, 255), CV_FILLED); // ratio of non-zero pixels in the filled region double r = (double)countNonZero(maskROI)/(rect.width*rect.height); if (r > .45 /* assume at least 45% of the area is filled if it contains text */ && (rect.height > 8 && rect.width > 8) /* constraints on region size */ /* these two conditions alone are not very robust. better to use something like the number of significant peaks in a horizontal projection as a third condition */ ) { rectangle(rgb, rect, Scalar(0, 255, 0), 2); } } imwrite(OUTPUT_FOLDER_PATH + string("rgb.jpg"), rgb); return 0; } 

Python-Version

 The MIT License (MIT) Copyright (c) 2017 Dhanushka Dangampola Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import cv2 import numpy as np large = cv2.imread('1.jpg') rgb = cv2.pyrDown(large) small = cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY) kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3)) grad = cv2.morphologyEx(small, cv2.MORPH_GRADIENT, kernel) _, bw = cv2.threshold(grad, 0.0, 255.0, cv2.THRESH_BINARY | cv2.THRESH_OTSU) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1)) connected = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, kernel) # using RETR_EXTERNAL instead of RETR_CCOMP contours, hierarchy = cv2.findContours(connected.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) mask = np.zeros(bw.shape, dtype=np.uint8) for idx in range(len(contours)): x, y, w, h = cv2.boundingRect(contours[idx]) mask[y:y+h, x:x+w] = 0 cv2.drawContours(mask, contours, idx, (255, 255, 255), -1) r = float(cv2.countNonZero(mask[y:y+h, x:x+w])) / (w * h) if r > 0.45 and w > 8 and h > 8: cv2.rectangle(rgb, (x, y), (x+w-1, y+h-1), (0, 255, 0), 2) cv2.imshow('rects', rgb) 

Bildbeschreibung hier eingebenBildbeschreibung hier eingebenBildbeschreibung hier eingeben

Hier ist ein alternativer Ansatz, mit dem ich die Textblöcke erkannt habe:

  1. Konvertiert das Bild in Graustufen
  2. Angewandter Schwellenwert (einfache binäre Schwelle, mit einem handverlesenen Wert von 150 als Schwellenwert)
  3. Angewandte Dilatation , um Linien im Bild zu verdichten, was zu kompakteren Objekten und weniger Leerraumfragmenten führt. Verwendet einen hohen Wert für die Anzahl der Iterationen, so ist die Dilatation sehr schwer (13 Iterationen, auch handverlesen für optimale Ergebnisse).
  4. Identifizierte Konturen von Objekten im Ergebnisbild mit der function opencv findContours .
  5. Zeichnet einen Begrenzungsrahmen (Rechteck), der jedes konturierte Objekt umgibt – jeder von ihnen umrahmt einen Textblock.
  6. Optional verworfene Bereiche, bei denen es unwahrscheinlich ist, dass sie das gesuchte Objekt (z. B. Textblöcke) aufgrund ihrer Größe sind, da der obige Algorithmus auch überschneidende oder verschachtelte Objekte (wie den gesamten oberen Bereich der ersten Karte) finden kann, von denen einige sein könnten uninteressant für Ihre Zwecke.

Unten ist der Code geschrieben in Python mit pyopencv, es sollte leicht nach C ++ portieren.

 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) 

Das Originalbild ist das erste Bild in Ihrem Post.

Nach der Vorverarbeitung (Graustufen, Schwellwert und Dilatation - also nach Schritt 3) sah das Bild so aus:

Erweitertes Bild

Unten ist das Ergebnisbild ("contoured.jpg" in der letzten Zeile); Die abschließenden Begrenzungsrahmen für die Objekte im Bild sehen folgendermaßen aus:

Bildbeschreibung hier eingeben

Sie können sehen, dass der Textblock auf der linken Seite als separater Block erkannt wird, der von seiner Umgebung begrenzt ist.

Unter Verwendung des gleichen Skripts mit den gleichen Parametern (außer dem Schwellenwerttyp, der für das zweite Bild wie unten beschrieben geändert wurde), sind hier die Ergebnisse für die anderen 2 Karten:

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Einstellen der Parameter

Die Parameter (Schwellwert, Dilatationsparameter) wurden für dieses Bild und diese Aufgabe optimiert (Textblöcke gefunden) und können bei Bedarf für andere Kartenbilder oder andere Arten von Objekten angepasst werden.

Für die Schwellenwertbildung (Schritt 2) habe ich einen schwarzen Schwellenwert verwendet. Bei Bildern, bei denen der Text heller als der Hintergrund ist (z. B. das zweite Bild in Ihrem Beitrag), sollte ein weißer Schwellenwert verwendet werden. Ersetzen Sie daher den Sholding-Typ durch cv2.THRESH_BINARY ). Für das zweite Bild habe ich auch einen etwas höheren Wert für den Schwellenwert (180) verwendet. Die Variation der Parameter für den Schwellenwert und die Anzahl der Iterationen für die Dilatation führt zu unterschiedlichen Empfindlichkeiten bei der Begrenzung von Objekten im Bild.

Andere Objekttypen finden:

Wenn Sie zum Beispiel die Erweiterung auf 5 Iterationen im ersten Bild verringern, erhalten Sie eine feinere Abgrenzung von Objekten im Bild, wobei Sie grob alle Wörter im Bild finden (anstatt Textblöcke):

Bildbeschreibung hier eingeben

Da ich die ungefähre Größe eines Wortes kannte, habe ich Bereiche verworfen, die zu klein (unter 20 Pixel Breite oder Höhe) oder zu groß (über 100 Pixel Breite oder Höhe) waren, um Objekte zu ignorieren, die wahrscheinlich keine Wörter sind das obige Bild.

@ dhanushkas Ansatz war am vielversprechendsten, aber ich wollte in Python herumspielen, also ging es weiter und übersetzte es aus Spaß:

 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) 

Jetzt um das Bild anzuzeigen:

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

Nicht die meisten Pythonic Skripte, aber ich habe versucht, den ursprünglichen C ++ Code so genau wie möglich für die Leser zu folgen.

Es funktioniert fast so gut wie das Original. Ich bin glücklich, Vorschläge zu lesen, wie es verbessert / behoben werden könnte, um die ursprünglichen Ergebnisse vollständig zu ähneln.

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Sie können diese Methode ausprobieren, die von Chucai Yi und Yingli Tian entwickelt wurde.

Sie teilen auch eine Software (die auf Opencv-1.0 basiert und sie sollte unter Windows-Plattform laufen.), Die Sie verwenden können (obwohl kein Quellcode verfügbar ist). Es erzeugt alle Textbegrenzungsfelder (in farbigen Schatten dargestellt) im Bild. Wenn Sie auf Ihre Beispielbilder anwenden, erhalten Sie folgende Ergebnisse:

Hinweis: Um das Ergebnis robuster zu machen, können Sie benachbarte Felder weiter zusammenführen.


Update: Wenn es Ihr Ziel ist, die Texte im Bild zu erkennen, können Sie gtext , ein OCR-freies Software- und Ground-Truthing-Werkzeug für Farbbilder mit Text, auschecken. Quellcode ist ebenfalls verfügbar.

Mit diesem können Sie anerkannte Texte erhalten wie:

Über Code JAVA-Version: Danke @William

 public static List detectLetters(Mat img){ List 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 contours = new ArrayList(); Mat hierarchy = new Mat(); Imgproc.findContours(img_threshold, contours,hierarchy, 0, 1); List contours_poly = new ArrayList(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; } 

Und verwende diesen Code in der Praxis:

  System.loadLibrary(Core.NATIVE_LIBRARY_NAME); Mat img1=Imgcodecs.imread("abc.png"); List 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); 

Python-Implementierung für die Lösung von @dhanushka:

 def process_rgb(rgb): hasText = 0 gray = cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY); morphcoreel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3)) grad = cv2.morphologyEx(gray, cv2.MORPH_GRADIENT, morphcoreel) # binarize _, bw = cv2.threshold(grad, 0.0, 255.0, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # connect horizontally oriented regions morphcoreel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1)) connected = cv2.morphologyEx(bw, cv2.MORPH_CLOSE, morphcoreel) # 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