Wyodrębnianie tekstu OpenCV

148

Próbuję znaleźć obwiednie tekstu na obrazie i obecnie stosuję następujące podejście:

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

Gdy dostaniesz taki obraz:

wprowadź opis obrazu tutaj

Następnie, kiedy pokażę varMatRegions, otrzymuję ten obraz:

wprowadź opis obrazu tutaj

Jak widać, w pewnym stopniu łączy lewy blok tekstu z nagłówkiem karty, dla większości kart ta metoda działa świetnie, ale na bardziej zajętych kartach może powodować problemy.

Powodem, dla którego te kontury są złe, jest to, że powoduje, że obwiednia konturu prawie zajmuje całą kartę.

Czy ktoś może zasugerować inny sposób znajdowania tekstu, aby zapewnić prawidłowe wykrywanie tekstu?

200 punktów dla każdego, kto znajdzie tekst na karcie powyżej tych dwóch.

wprowadź opis obrazu tutaj wprowadź opis obrazu tutaj

Spinacz
źródło
1
Najłatwiejszy sposób, jaki tu widzę, to zwiększenie kontrastu przed uzyskaniem regionów ...
Paweł Stawarz
3
Fajne pytanie. Dziękuję za opublikowanie go i zorganizowanie nagrody, aby zapewnić tak interesujące odpowiedzi.
Geoff
Nowość w programowaniu. Czy to samo można zrobić dla tekstu w skryptach innych niż angielski, takich jak sanskryt?
Vamshi Krishna

Odpowiedzi:

127

Możesz wykryć tekst, znajdując elementy o bliskiej krawędzi (inspirowane LPD):

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

Stosowanie:

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

Wyniki:

za. element = getStruifyingElement (cv :: MORPH_RECT, cv :: Size (17, 3)); imgOut1 imgOut2

b. element = getStruifyingElement (cv :: MORPH_RECT, cv :: Size (30, 30)); imgOut1 imgOut2

Wyniki są podobne dla drugiego wymienionego obrazu.

LovaBill
źródło
6
Detektor tablic rejestracyjnych.
LovaBill
2
W przypadku niektórych kart obwiednia nie obejmuje całego tekstu, na przykład ucina się pół litery. Takich jak ta karta: i.imgur.com/tX3XrwH.jpg Jak mogę przedłużyć wysokość i szerokość wszystkich obwiedni n? Dzięki za rozwiązanie działa świetnie!
Klip z
4
Powiedz cv::Rect a;. Powiększona o n: a.x-=n/2;a.y-=n/2;a.width+=n;a.height+=n;.
LovaBill
2
Cześć, jak mogę osiągnąć ten sam wynik z Python CV2?
dnth
3
Zarezerwuj . Kod .
LovaBill
128

Użyłem metody opartej na gradiencie w poniższym programie. Dodano powstałe obrazy. Zwróć uwagę, że używam zmniejszonej wersji obrazu do przetwarzania.

wersja c ++

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 <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>

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 morphKernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));
    morphologyEx(small, grad, MORPH_GRADIENT, morphKernel);
    // binarize
    Mat bw;
    threshold(grad, bw, 0.0, 255.0, THRESH_BINARY | THRESH_OTSU);
    // connect horizontally oriented regions
    Mat connected;
    morphKernel = getStructuringElement(MORPH_RECT, Size(9, 1));
    morphologyEx(bw, connected, MORPH_CLOSE, morphKernel);
    // find contours
    Mat mask = Mat::zeros(bw.size(), CV_8UC1);
    vector<vector<Point>> contours;
    vector<Vec4i> 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;
}

wersja Pythona

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)
#For opencv 3+ comment the previous line and uncomment the following line
#_, 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)

wprowadź opis obrazu tutaj wprowadź opis obrazu tutaj wprowadź opis obrazu tutaj

dhanushka
źródło
3
Właśnie przyjrzałem się jego podejściu. Główną różnicą, jaką widzę, jest to, że on używa filtra Sobela, podczas gdy ja używam morfologicznego filtra gradientowego. Myślę, że filtr morfologiczny i downsampling spłaszczają większość niezbyt mocnych krawędzi. Sobel może odebrać więcej hałasu.
dhanushka
1
@ascenator Kiedy łączysz OTSU z typem progu, używa progu Otsu zamiast określonej wartości progowej. Zobacz tutaj .
dhanushka
1
@VishnuJayanand Wystarczy zastosować skalowanie do rect. Jest jeszcze jedna pyrdown, więc pomnożyć x, y, width, heightz rectprzez 4.
dhanushka
1
Czy mógłbyś podać nam trzeci warunek: liczbę znaczących pików w rzucie poziomym lub przynajmniej trochę ołowiu.
ISlimani,
2
@DforTye Weź poziome rzutowanie wypełnionego konturu (cv :: zredukuj), a następnie proguj (powiedzmy, używając średniej lub mediany wysokości). Jeśli wizualizujesz ten wynik, będzie wyglądał jak kod kreskowy. Myślę, że wtedy myślałem o policzeniu liczby słupków i nałożeniu na to progu. Teraz myślę, że jeśli region jest wystarczająco czysty, pomocne może być również przekazanie go do OCR i uzyskanie poziomu ufności dla każdego wykrytego znaku, aby mieć pewność, że region zawiera tekst.
dhanushka
51

Oto alternatywne podejście, którego użyłem do wykrywania bloków tekstu:

  1. Przekonwertowano obraz do skali szarości
  2. Zastosowany próg (prosty próg binarny, z ręcznie wybraną wartością 150 jako wartością progową)
  3. Zastosowano dylatację, aby pogrubić linie obrazu, co prowadzi do bardziej zwartych obiektów i mniejszej liczby fragmentów białych przestrzeni. Zastosowano wysoką wartość liczby iteracji, więc rozszerzanie jest bardzo duże (13 iteracji, również wybranych ręcznie w celu uzyskania optymalnych wyników).
  4. Zidentyfikowano kontury obiektów w otrzymanym obrazie za pomocą funkcji opencv findContours .
  5. Narysowałem obwiednię (prostokąt) opisującą każdy obrysowany obiekt - każdy z nich oprawia blok tekstu.
  6. Opcjonalnie odrzucone obszary, które prawdopodobnie nie są obiektem, którego szukasz (np. Bloki tekstu), biorąc pod uwagę ich rozmiar, ponieważ powyższy algorytm może również znaleźć przecinające się lub zagnieżdżone obiekty (takie jak cały górny obszar pierwszej karty), z których niektóre mogą być nieciekawe dla twoich celów.

Poniżej znajduje się kod napisany w Pythonie z pyopencv, powinien być łatwy do przeniesienia do C ++.

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) 

Oryginalny obraz to pierwszy obraz w Twoim poście.

Po wstępnym przetworzeniu (skala szarości, próg i dylatacja - czyli po kroku 3) obraz wyglądał następująco:

Rozszerzony obraz

Poniżej znajduje się obraz wynikowy („contoured.jpg” w ostatnim wierszu); końcowe obwiednie obiektów na obrazku wyglądają następująco:

wprowadź opis obrazu tutaj

Możesz zobaczyć, że blok tekstu po lewej stronie jest wykrywany jako oddzielny blok, oddzielony od otoczenia.

Używając tego samego skryptu z tymi samymi parametrami (z wyjątkiem typu progowania, który został zmieniony dla drugiego obrazu, jak opisano poniżej), oto wyniki dla pozostałych 2 kart:

wprowadź opis obrazu tutaj

wprowadź opis obrazu tutaj

Dostrajanie parametrów

Parametry (wartość progowa, parametry dylatacji) zostały zoptymalizowane dla tego obrazu i tego zadania (znajdowanie bloków tekstu) i można je w razie potrzeby dostosować do innych obrazów kart lub innych typów obiektów.

Do progowania (krok 2) użyłem czarnego progu. W przypadku obrazów, na których tekst jest jaśniejszy niż tło, takich jak drugi obraz w Twoim poście, należy użyć białego progu, więc zastąp tekst typu sholding na cv2.THRESH_BINARY). Dla drugiego obrazu również zastosowałem nieco wyższą wartość progu (180). Zmiana parametrów wartości progowej i liczby iteracji dylatacji będzie skutkowała różnymi stopniami czułości przy wyznaczaniu obiektów na obrazie.

Znajdowanie innych typów obiektów:

Na przykład zmniejszenie rozszerzenia do 5 iteracji na pierwszym obrazie daje nam dokładniejsze rozgraniczenie obiektów na obrazie, z grubsza znajdując wszystkie słowa na obrazie (zamiast bloków tekstu):

wprowadź opis obrazu tutaj

Znając przybliżony rozmiar słowa, tutaj odrzuciłem obszary, które były zbyt małe (poniżej 20 pikseli szerokości lub wysokości) lub zbyt duże (powyżej 100 pikseli szerokości lub wysokości), aby zignorować obiekty, które prawdopodobnie nie są słowami, aby uzyskać wyniki w powyższy obraz.

anana
źródło
2
Jesteś niesamowity! Spróbuję tego rano.
Klip
Dodałem kolejny krok do odrzucania nieciekawych obiektów; dodany również przykład do identyfikacji słów lub innych typów obiektów (niż bloki tekstu)
anana
Dziękuję za szczegółową odpowiedź, jednak pojawia się błąd cv2.findContours. Mówi ValueError: too many values to unpack.
Abhijith
1
Problem polega na tym, że funkcja cv2.findContourszwraca 3 argumenty, a oryginalny kod przechwytuje tylko 2.
Abhijith
@Abhijith cv2 w wersji drugiej zwrócił dwa argumenty, ale teraz w wersji trzeciej zwraca 3
Tomasz Giba
27

Podejście @ dhanushka okazało się najbardziej obiecujące, ale chciałem pobawić się w Pythonie, więc poszedłem do przodu i przetłumaczyłem to dla zabawy:

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)

Teraz, aby wyświetlić obraz:

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

Nie jest to najbardziej Pythonic ze skryptów, ale starałem się jak najbardziej przypominać oryginalny kod C ++, aby czytelnicy mogli go śledzić.

Działa prawie tak dobrze, jak oryginał. Z przyjemnością przeczytam sugestie, jak można to ulepszyć / naprawić, aby w pełni przypominać oryginalne wyniki.

wprowadź opis obrazu tutaj

wprowadź opis obrazu tutaj

wprowadź opis obrazu tutaj

rtkaleta
źródło
3
Dziękujemy za udostępnienie wersji dla języka Python. Wiele osób uzna to za przydatne. +1
dhanushka
jaka jest różnica między wypełnieniem konturu a jego narysowaniem? Znalazłem kod bez fazy wypełniania tutaj: stackoverflow.com/a/23556997/6837132
SarahData
@SarahM Nie wiem, czy pytasz o ogólną różnicę między rysowaniem a wypełnianiem (myślę, że dość oczywiste?), Czy konkretnie o API OpenCV? Jeśli to drugie, zobacz dokumentację dladrawContours tego stanu „Funkcja rysuje kontury na obrazie, jeśli grubość> 0 lub wypełnia obszar ograniczony konturami, jeśli grubość <0”. Zrobiono to, abyśmy mogli sprawdzić stosunek niezerowych pikseli, aby zdecydować, czy pole prawdopodobnie zawiera tekst.
rtkaleta
15

Możesz wypróbować tę metodę opracowaną przez Chucai Yi i Yingli Tian.

Współdzielą również oprogramowanie (które jest oparte na Opencv-1.0 i powinno działać na platformie Windows), z którego możesz korzystać (chociaż nie ma dostępnego kodu źródłowego). Wygeneruje wszystkie pola ograniczające tekst (pokazane w cieniach kolorów) na obrazie. Stosując się do przykładowych obrazów, uzyskasz następujące wyniki:

Uwaga: aby wynik był bardziej wytrzymały, możesz dodatkowo scalić ze sobą sąsiednie pola.


Aktualizacja: Jeśli Twoim ostatecznym celem jest rozpoznanie tekstów na obrazie, możesz dalej wypróbować gttext , który jest darmowym oprogramowaniem OCR i narzędziem Ground Truthing dla kolorowych obrazów z tekstem. Dostępny jest również kod źródłowy.

Dzięki temu możesz uzyskać rozpoznane teksty, takie jak:

herohuyongtao
źródło
gttext jest przeznaczony dla systemu Windows. Wszelkie sugestie dla użytkowników Mac / Linux
Saghir A. Khatri
5

Powyższa wersja kodu JAVA: Dzięki @William

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

I użyj tego kodu w praktyce:

        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);
Fariz Agayev
źródło
2

Implementacja Pythona dla rozwiązania @ dhanushka:

def process_rgb(rgb):
    hasText = False
    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 = True
        idx = hierarchy[0][idx][0]
    return hasText, rgb
Akash Budhia
źródło
dlaczego użyłeś maski?
SarahData,
1
Zduplikowana odpowiedź. Byłoby bardziej przydatne, gdybyś uczestniczył w rozmowie na stackoverflow.com/a/43283990/6809909 .
rtkaleta
2

To jest wersja C # odpowiedzi z dhanushka przy użyciu OpenCVSharp

        Mat large = new Mat(INPUT_FILE);
        Mat rgb = new Mat(), small = new Mat(), grad = new Mat(), bw = new Mat(), connected = new Mat();

        // downsample and use it for processing
        Cv2.PyrDown(large, rgb);
        Cv2.CvtColor(rgb, small, ColorConversionCodes.BGR2GRAY);

        // morphological gradient
        var morphKernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new OpenCvSharp.Size(3, 3));
        Cv2.MorphologyEx(small, grad, MorphTypes.Gradient, morphKernel);

        // binarize
        Cv2.Threshold(grad, bw, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);

        // connect horizontally oriented regions
        morphKernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(9, 1));
        Cv2.MorphologyEx(bw, connected, MorphTypes.Close, morphKernel);

        // find contours
        var mask = new Mat(Mat.Zeros(bw.Size(), MatType.CV_8UC1), Range.All);
        Cv2.FindContours(connected, out OpenCvSharp.Point[][] contours, out HierarchyIndex[] hierarchy, RetrievalModes.CComp, ContourApproximationModes.ApproxSimple, new OpenCvSharp.Point(0, 0));

        // filter contours
        var idx = 0;
        foreach (var hierarchyItem in hierarchy)
        {
            idx = hierarchyItem.Next;
            if (idx < 0)
                break;
            OpenCvSharp.Rect rect = Cv2.BoundingRect(contours[idx]);
            var maskROI = new Mat(mask, rect);
            maskROI.SetTo(new Scalar(0, 0, 0));

            // fill the contour
            Cv2.DrawContours(mask, contours, idx, Scalar.White, -1);

            // ratio of non-zero pixels in the filled region
            double r = (double)Cv2.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 */
            )
            {
                Cv2.Rectangle(rgb, rect, new Scalar(0, 255, 0), 2);
            }
        }

        rgb.SaveImage(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "rgb.jpg"));
Diomedes Domínguez
źródło
0

to jest wersja VB.NET odpowiedzi z dhanushka przy użyciu EmguCV .

Kilka funkcji i struktur w EmguCV wymaga innego rozważenia niż wersja C # z OpenCVSharp

Imports Emgu.CV
Imports Emgu.CV.Structure
Imports Emgu.CV.CvEnum
Imports Emgu.CV.Util

        Dim input_file As String = "C:\your_input_image.png"
        Dim large As Mat = New Mat(input_file)
        Dim rgb As New Mat
        Dim small As New Mat
        Dim grad As New Mat
        Dim bw As New Mat
        Dim connected As New Mat
        Dim morphanchor As New Point(0, 0)

        '//downsample and use it for processing
        CvInvoke.PyrDown(large, rgb)
        CvInvoke.CvtColor(rgb, small, ColorConversion.Bgr2Gray)

        '//morphological gradient
        Dim morphKernel As Mat = CvInvoke.GetStructuringElement(ElementShape.Ellipse, New Size(3, 3), morphanchor)
        CvInvoke.MorphologyEx(small, grad, MorphOp.Gradient, morphKernel, New Point(0, 0), 1, BorderType.Isolated, New MCvScalar(0))

        '// binarize
        CvInvoke.Threshold(grad, bw, 0, 255, ThresholdType.Binary Or ThresholdType.Otsu)

        '// connect horizontally oriented regions
        morphKernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, New Size(9, 1), morphanchor)
        CvInvoke.MorphologyEx(bw, connected, MorphOp.Close, morphKernel, morphanchor, 1, BorderType.Isolated, New MCvScalar(0))

        '// find contours
        Dim mask As Mat = Mat.Zeros(bw.Size.Height, bw.Size.Width, DepthType.Cv8U, 1)  '' MatType.CV_8UC1
        Dim contours As New VectorOfVectorOfPoint
        Dim hierarchy As New Mat

        CvInvoke.FindContours(connected, contours, hierarchy, RetrType.Ccomp, ChainApproxMethod.ChainApproxSimple, Nothing)

        '// filter contours
        Dim idx As Integer
        Dim rect As Rectangle
        Dim maskROI As Mat
        Dim r As Double
        For Each hierarchyItem In hierarchy.GetData
            rect = CvInvoke.BoundingRectangle(contours(idx))
            maskROI = New Mat(mask, rect)
            maskROI.SetTo(New MCvScalar(0, 0, 0))

            '// fill the contour
            CvInvoke.DrawContours(mask, contours, idx, New MCvScalar(255), -1)

            '// ratio of non-zero pixels in the filled region
            r = CvInvoke.CountNonZero(maskROI) / (rect.Width * rect.Height)

            '/* assume at least 45% of the area Is filled if it contains text */
            '/* 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 */
            If r > 0.45 AndAlso rect.Height > 8 AndAlso rect.Width > 8 Then
                'draw green rectangle
                CvInvoke.Rectangle(rgb, rect, New MCvScalar(0, 255, 0), 2)
            End If
            idx += 1
        Next
        rgb.Save(IO.Path.Combine(Application.StartupPath, "rgb.jpg"))
Hakan Usakli
źródło