Wydobywanie tekstu OpenCV

Próbuję znaleźć obwiedniowe pola tekstu na obrazku i obecnie używam tego podejścia:

// 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 podano taki obrazek:

Tutaj wpisz opis obrazka

Wtedy gdy pokazuję varMatRegions dostaję ten obrazek:

Tutaj wpisz opis obrazka

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

Powodem złego połączenia tych konturów jest to, że sprawia, że Obwiednia konturu prawie zajmuje całą kartę.

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

200 wskazuje na to, kto znajdzie tekst na karcie powyżej tych dwóch.

Tutaj wpisz opis obrazkaTutaj wpisz opis obrazka

Author: herohuyongtao, 2014-05-07

7 answers

Możesz wykryć tekst, znajdując bliskie elementy krawędzi (zainspirowane 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;
}

Użycie:

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:

A. element = getStructuringElement(cv::MORPH_RECT, CV:: Size (17, 3) ); imgOut1imgOut2

B. element = getStructuringElement(cv::MORPH_RECT, CV:: Size (30, 30) ); imgOut1imgOut2

Wyniki są podobne dla drugiego wspomnianego obrazu.

 107
Author: William,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-13 14:22:36

Użyłem metody gradientowej w poniższym programie. Dodano wynikowe obrazy. Pamiętaj, że do przetwarzania używam pomniejszonej wersji obrazu.

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)

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)

Tutaj wpisz opis obrazkaTutaj wpisz opis obrazkaTutaj wpisz opis obrazka

 104
Author: dhanushka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-14 11:04:56

Oto alternatywne podejście, które wykorzystałem do wykrywania bloków tekstowych:

  1. konwertowanie obrazu do skali szarości
  2. Applied threshold (prosty próg binarny, z ręcznie dobraną wartością 150 jako wartością progu)
  3. zastosowano dylatację w celu zagęszczenia linii obrazu, co prowadzi do uzyskania bardziej zwartych obiektów i mniej białych fragmentów przestrzeni. Zastosowano wysoką wartość dla liczby iteracji, więc Dylatacja jest bardzo ciężka (13 iteracji, również dobrane ręcznie dla optymalnego wyniki).
  4. zidentyfikowano kontury obiektów w wynikowym obrazie za pomocą funkcji OpenCV findContours.
  5. narysował obwiednię (prostokąt) opisującą każdy wyprofilowany obiekt - każdy z nich obramowuje blok tekstu.
  6. opcjonalnie odrzucane obszary, które są mało prawdopodobne, aby były obiektem, którego szukasz (np. bloki tekstowe), biorąc pod uwagę ich rozmiar, ponieważ powyższy algorytm może również znaleźć przecinające się lub zagnieżdżone obiekty (jak cały górny obszar dla pierwszej karty). co może być nieciekawe dla Twoich celów.

Poniżej znajduje się kod napisany w Pythonie za pomocą pyopencv, powinien być łatwo portowany 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 obrazek jest pierwszym obrazem w Twoim poście.

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

Rozszerzony obraz

Poniżej znajduje się wynikowy obrazek ("wyprofilowany.jpg" w ostatniej linii); ostateczne obwiedni dla obiektów na obrazie wyglądają następująco to:

Tutaj wpisz opis obrazka

Widać, że blok tekstowy po lewej jest wykrywany jako oddzielny blok, oddzielony od jego otoczenia.

Używając tego samego skryptu o tych samych parametrach (z wyjątkiem typu progowego, który został zmieniony dla drugiego obrazu, jak opisano poniżej), Oto wyniki dla pozostałych 2 kart:

Tutaj wpisz opis obrazka

Tutaj wpisz opis obrazka

Dostrajanie parametrów

Parametry (wartość progowa, parametry dylatacji) zostały zoptymalizowane dla tego obrazu i to zadanie (znalezienie bloków tekstowych) i można dostosować, w razie potrzeby, dla innych obrazów kart lub innych typów obiektów, które mają być znalezione.

Dla progu (Krok 2), użyłem czarnego progu. W przypadku obrazków, w których tekst jest lżejszy od tła, np. drugi obrazek w poście, należy użyć białego progu, więc zastąp typ cv2.THRESH_BINARY). Do drugiego obrazu użyłem również nieco wyższej wartości progu (180). Zmiana parametrów dla wartości progowej i liczba iteracji dylatacji spowoduje różny stopień czułości w rozdzielaniu obiektów na obrazie.

Znajdowanie innych typów obiektów:

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

Tutaj wpisz opis obrazka

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 ignorować obiekty, które są mało prawdopodobne, aby były słowami, aby uzyskać wyniki na powyższym obrazie.

 42
Author: anana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-06 20:08:21

@dhanushka podejście pokazało najbardziej obiecujące, ale chciałem się pobawić 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 wyświetl obrazek:

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

Nie najbardziej Pythoniczny ze skryptów, ale starałem się jak najbardziej przypominać oryginalny kod C++ dla czytelników.

Działa prawie tak dobrze jak oryginał. Chętnie przeczytam sugestie, jak można to poprawić/naprawić, aby przypominać Oryginalne wyniki w pełni.

Tutaj wpisz opis obrazka

Tutaj wpisz opis obrazka

Tutaj wpisz opis obrazka

 20
Author: rtkaleta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-07 17:31:32

Możesz wypróbować tę metodę , która została opracowana przez Chucai Yi i Yingli Tian.

Mają również wspólne oprogramowanie (które jest oparte na Opencv-1.0 i powinno działać pod platformą Windows.), którego możesz użyć (choć nie ma dostępnego kodu źródłowego). Spowoduje to wygenerowanie wszystkich obwiedni tekstu (pokazanych w cieniach kolorów) na obrazie. Stosując się do przykładowych obrazów, otrzymasz następujące wyniki:

UWAGA: aby wynik był bardziej wytrzymały, można dalej łączyć sąsiadujące pola razem.


Update: jeśli twoim ostatecznym celem jest rozpoznanie tekstów na obrazku, możesz dalej sprawdzić gttext , który jest wolnym oprogramowaniem OCR i narzędziem Truthing do kolorowych obrazów z tekstem. Dostępny jest również kod źródłowy.

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

 15
Author: herohuyongtao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-09 06:45:37

Powyższy kod w wersji 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żywać 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);
 5
Author: Fariz Agayev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-02 12:25:13

Implementacja Pythona dla rozwiązania @ dhanushka:

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
 1
Author: Akash Budhia,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-07 19:03:07