Витягування тексту OpenCV


148

Я намагаюся знайти обмежувальні поля тексту у зображенні, і зараз використовую такий підхід:

// 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я отримую це зображення:

введіть тут опис зображення

Як ви бачите, він дещо поєднує лівий блок тексту із заголовком картки, для більшості карт цей метод працює чудово, але на більш зайнятих картках це може спричинити проблеми.

Причина, по якій ці контури погано з’єднуються, полягає в тому, що це робить обмежувальне поле контуру майже зайнятим всією картою.

Чи може хтось запропонувати інший спосіб я знайти текст, щоб забезпечити належне виявлення тексту?

200 балів тому, хто може знайти текст на картці над цими двома.

введіть тут опис зображення введіть тут опис зображення


1
Найпростіший спосіб, який я бачу тут, - це збільшення контрасту перед тим, як отримати регіони ...
Paweł Stawarz

3
Класне питання. Дякуємо, що опублікували його та прийняли баунті для отримання таких цікавих відповідей.
Джефф

Нове в програмуванні. Чи можна зробити те ж саме для тексту в текстах, відмінних від англійської, як санскрит?
Вамші Кришна

Відповіді:


127

Ви можете розпізнати текст, знайшовши тісні елементи (натхнені 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;
}

Використання:

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

Результати:

а. element = getStructuringElement (cv :: MORPH_RECT, cv :: Розмір (17, 3)); imgOut1 imgOut2

б. елемент = getStructuringElement (cv :: MORPH_RECT, cv :: Розмір (30, 30)); imgOut1 imgOut2

Результати аналогічні іншим згаданим зображенням.


6
Детектор номерних знаків.
LovaBill

2
Для деяких карт обмежувальне поле не містить весь текст, наприклад, відрізання половини літери. Як, наприклад, ця картка: i.imgur.com/tX3XrwH.jpg Як я можу розширити кожну обмежувальну рамку висоти та ширини на n? Дякую за рішення, воно чудово працює!
Кліп

4
Скажи cv::Rect a;. Збільшена на n : a.x-=n/2;a.y-=n/2;a.width+=n;a.height+=n;.
LovaBill

2
Привіт, як я можу досягти такого ж результату з python cv2?
dnth


128

Я використовував метод на основі градієнта в нижченаведеній програмі. Додані отримані зображення. Зверніть увагу, що я використовую для обробки зменшену версію зображення.

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

версія пітона

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)

введіть тут опис зображення введіть тут опис зображення введіть тут опис зображення


3
Я просто подивився на його підхід. Основна відмінність, яку я бачу, полягає в тому, що він використовує фільтр Собеля, тоді як я використовую морфологічний градієнтний фільтр. Я думаю, що морфологічний фільтр і зменшення розміщення вирівнюють більшу частину не дуже сильних країв. Sobel може підняти більше шуму.
dhanushka

1
@ascenator Коли ви поєднуєте OTSU з типом порогу, він використовує поріг Otsu замість вказаного порогового значення. Дивіться тут .
данушка

1
@VishnuJayanand Ви просто повинні застосувати масштабування до rect. Там в один pyrdown, так розмножуються x, y, width, heightз rect-4
dhanushka

1
Надайте, будь ласка, третю умову: кількість значних піків у горизонтальній проекції або хоча б деякий відвід.
ISlimani

2
@DforTye Візьміть горизонтальну проекцію заповненого контуру (cv :: зменшення), а потім поріг його (скажімо, за допомогою середньої або середньої висоти). Якщо візуалізувати цей результат, він буде схожий на штрих-код. Я думаю, у той час я думав порахувати кількість барів і накласти на неї поріг. Тепер я думаю, якщо регіон достатньо чистий, це може також допомогти, якщо ми зможемо подати його до OCR та отримати рівень довіри для кожного виявленого символу, щоб бути впевненим, що регіон містить текст.
dhanushka

51

Ось альтернативний підхід, який я використовував для виявлення текстових блоків:

  1. Перетворив зображення в кольори сірого
  2. Застосований поріг (простий двійковий поріг із вибраним значенням 150 як порогове значення)
  3. Застосовується розширення для потовщення ліній у зображенні, що призводить до отримання більш компактних предметів та менше фрагментів білого простору. Використовується високе значення для кількості ітерацій, тому дилатація дуже важка (13 ітерацій, також підібраних для досягнення оптимальних результатів).
  4. Ідентифіковані контури об’єктів у наведеному зображенні за допомогою opencv функції findContours .
  5. Дрю а обмежувальне поле (прямокутник), що описує кожен контурний об’єкт - кожен з них обрамляє блок тексту.
  6. Необов'язково відкидаються області, які навряд чи будуть об’єктом, який ви шукаєте (наприклад, текстові блоки), враховуючи їх розмір, оскільки алгоритм, наведений вище, також може знаходити пересічні або вкладені об'єкти (як і вся верхня область для першої карти), деякі з яких можуть бути нецікавий для своїх цілей.

Нижче наведений код, написаний у python разом із pyopencv, він повинен легко переноситися на 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) 

Оригінальне зображення - це перше зображення у вашій публікації.

Після попередньої обробки (масштаб сірого, порогового та розширеного - так що після кроку 3) зображення виглядало так:

Розширене зображення

Внизу наведене зображення ("contoured.jpg" в останньому рядку); остаточні обмежувальні поля для об'єктів на зображенні виглядають так:

введіть тут опис зображення

Ви можете бачити, що текстовий блок зліва виявляється як окремий блок, відмежований від його оточення.

Використовуючи той самий скрипт з тими ж параметрами (за винятком типу порогу, який був змінений для другого зображення, як описано нижче), ось результати для інших 2 карт:

введіть тут опис зображення

введіть тут опис зображення

Налаштування параметрів

Параметри (значення порогу, параметри дилатації) були оптимізовані для цього зображення та цього завдання (знаходження текстових блоків) і можуть бути скориговані, якщо потрібно, для зображень інших карт або інших типів об'єктів, які можна знайти.

Для визначення порогу (крок 2) я використовував чорний поріг. Для зображень, де текст світліший за фоновий, наприклад другого зображення у вашій публікації, слід використовувати білий поріг, тому замініть тип зберігання на cv2.THRESH_BINARY). Для другого зображення я також використав трохи вище значення порогу (180). Змінення параметрів порогового значення та кількості повторень для розширення призведе до різного ступеня чутливості при розміщенні об'єктів на зображенні.

Пошук інших типів об'єктів:

Наприклад, зменшення розширення до 5 ітерацій на першому зображенні дає нам більш тонке розмежування об’єктів на зображенні, приблизно знаходячи всі слова на зображенні (а не текстові блоки):

введіть тут опис зображення

Знаючи приблизний розмір слова, тут я відкинув ділянки, які були занадто маленькими (нижче 20 пікселів шириною або висотою) або занадто великими (понад 100 пікселів шириною або висотою), щоб ігнорувати об'єкти, які навряд чи будуть словами, щоб отримати результати в наведене вище зображення.


2
Ти дивовижний! Я спробую це вранці.
Кліп

Я додав ще один крок для відкидання нецікавих об’єктів; також додали приклад для визначення слів або інших типів предметів (ніж блоки тексту)
anana

Дякую за детальну відповідь, однак я отримую помилку в cv2.findContours. Це говорить ValueError: too many values to unpack.
Абхіджіт

1
Проблема полягає в тому, що функція cv2.findContoursповертає 3 аргументи, а оригінальний код фіксує лише 2.
Abhijith

@Abhijith cv2 у версії 2 повернув два аргументи, але тепер, у версії 3, він повертається 3
Tomasz Giba

27

Підхід @ dhanushka показав найбільш обіцянку, але я хотів пограти в Python, тому пішов наперед і переклав це для забави:

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

Не найпітонічніші сценарії, але я намагався якомога ближче нагадувати оригінальний код C ++, щоб читачі дотримувалися.

Він працює майже так само добре, як оригінал. Я буду радий прочитати пропозиції, як це можна вдосконалити / виправити так, щоб повністю нагадувати початкові результати.

введіть тут опис зображення

введіть тут опис зображення

введіть тут опис зображення


3
Дякуємо, що надали версію python. Багато людей вважають це корисним. +1
dhanushka

яка різниця між заповненням контуру та його малюванням? Я знайшов код без фази наповнення тут: stackoverflow.com/a/23556997/6837132
SarahData

@SarahM Я не знаю, чи запитуєте ви про загальну різницю між малюнком і заповненням (я думаю, досить очевидно?) Або API OpenCV конкретно? Якщо останній бачить документи для drawContoursцього стану, "Функція малює контури контуру на зображенні, якщо товщина> 0 або заповнює область, обмежену контурами, якщо товщина <0." Це зроблено, щоб ми могли перевірити співвідношення ненульових пікселів, щоб вирішити, чи вікно містить текст.
рткалета

15

Ви можете спробувати цей метод, який розробили Чукай Ії та Інглі Тянь.

Вони також діляться програмним забезпеченням (яке базується на Opencv-1.0, і воно повинно працювати на платформі Windows.), Яким ви можете користуватися (хоча вихідний код не доступний). Це створить усі текстові рамки (зображені кольоровими тінями) на зображенні. Застосувавши до своїх зразків зображень, ви отримаєте такі результати:

Примітка: щоб зробити результат більш надійним, ви можете додатково об'єднати сусідні поля разом.


Оновлення: Якщо ваша кінцева мета - розпізнати тексти на зображенні, ви можете додатково перевірити gttext , який є безкоштовним програмним забезпеченням OCR та інструментом Ground Truthing для кольорових зображень з текстом. Вихідний код також доступний.

За допомогою цього ви можете отримати розпізнані тексти, такі як:


gttext призначений для Windows. Будь-яка пропозиція для користувачів Mac / Linux
Сагір А. Хатрі

5

Вище коду версія JAVA: Спасибі @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;
}

І використовуйте цей код на практиці:

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

2

Реалізація Python для рішення @ 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

чому використовували маску?
SarahData

1
Дублююча відповідь. Було б корисніше, якби ви сприяли розмові на сайті stackoverflow.com/a/43283990/6809909 .
rtkaleta

2

Це C # версія відповіді від dhanushka за допомогою 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"));

0

це VB.NET версія відповіді від dhanushka за допомогою EmguCV .

Кілька функцій та структур у EmguCV потребують іншого розгляду, ніж версія C # з 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"))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.