Извлечение текста 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
Самый простой способ, который я вижу здесь, это увеличить контрастность, прежде чем получать регионы ...
Павел Ставарц
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 :: Size (17, 3)); imgOut1 imgOut2

б. element = getStructuringElement (cv :: MORPH_RECT, cv :: Size (30, 30)); imgOut1 imgOut2

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

LovaBill
источник
6
Детектор номерного знака.
LovaBill
2
Для некоторых карточек ограничивающий прямоугольник не содержит весь текст, например, отрывается половина буквы. Например, эта карта: i.imgur.com/tX3XrwH.jpg Как я могу увеличить каждую ограничивающую ограничивающую рамку на высоту и ширину n? Спасибо за решение, оно прекрасно работает!
Клип
4
Say cv::Rect a;. Увеличенная по п: a.x-=n/2;a.y-=n/2;a.width+=n;a.height+=n;.
LovaBill
2
Привет, как я могу достичь того же результата с Python CV2?
dnth
3
Книга . Код .
LovaBill
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)

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

dhanushka
источник
3
Я только взглянул на его подход. Основное отличие, которое я вижу, состоит в том, что он использует фильтр Собела, тогда как я использую фильтр морфологического градиента. Я думаю, что морфологический фильтр и понижающая дискретизация сглаживают большую часть не очень сильных краев. Собел может поднять шум.
дханушка
1
@ascenator Когда вы объединяете OTSU с типом порога, он использует порог Otsu вместо указанного порогового значения. Смотрите здесь .
дханушка
1
@VishnuJayanand Вы просто должны применить масштабирование к rect. Там один pyrdown, так что умножьте x, y, width, heightна rect4.
Дханушка
1
Не могли бы вы предоставить нам третье условие: количество значительных пиков в горизонтальной проекции или хотя бы немного отведения.
Ислимани
2
@DforTye Возьмите горизонтальную проекцию заполненного контура (cv :: redu), затем пороговую его (скажем, с использованием среднего или средней высоты). Если вы визуализируете этот результат, он будет выглядеть как штрих-код. Я думаю, что в то время я думал о подсчете количества баров и установлении порога для него. Теперь я думаю, что если область достаточно чистая, может также помочь, если мы сможем передать ее в OCR и получить уровень достоверности для каждого обнаруженного символа, чтобы быть уверенным, что область содержит текст.
дханушка
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) я использовал черный порог. Для изображений, где текст светлее фона, например второе изображение в вашем сообщении, следует использовать белый порог, поэтому замените тип удержания на cv2.THRESH_BINARY). Для второго изображения я также использовал немного более высокое значение для порога (180). Изменение параметров для порогового значения и количества итераций для расширения приведет к различной степени чувствительности при разграничении объектов на изображении.

Поиск других типов объектов:

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

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

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

Анана
источник
2
Ты великолепен! Я попробую это утром.
Клип
Я добавил еще один шаг для отбрасывания неинтересных объектов; также добавлен пример для идентификации слов или других типов объектов (кроме блоков текста)
анана
Спасибо за подробный ответ, однако я получаю сообщение об ошибке cv2.findContours. Это говорит ValueError: too many values to unpack.
Абхиджит
1
Проблема в том, что функция cv2.findContoursвозвращает 3 аргумента, а оригинальный код захватывает только 2.
Абхиджит,
@Abhijith cv2 во второй версии вернул два аргумента, но теперь, в третьей версии, он возвращает 3
Томаш Гиба
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()

Не самый Pythonic из сценариев, но я попытался максимально приблизиться к исходному коду C ++, чтобы читатели могли им следовать.

Это работает почти так же, как оригинал. Я буду рад прочитать предложения о том, как это можно улучшить / исправить, чтобы полностью соответствовать исходным результатам.

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

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

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

rtkaleta
источник
3
Спасибо за предоставление версии Python. Многие найдут это полезным. +1
дханушка
Какая разница между заполнением контура и его прорисовкой? Я нашел код без фазы заполнения здесь: stackoverflow.com/a/23556997/6837132
SarahData
@SarahM Я не знаю, спрашиваете ли вы об общем различии между рисованием и заливкой (я думаю, довольно очевидно) или об API OpenCV конкретно? Если последний, то смотрите документацию для drawContoursэтого состояния: «Функция рисует контурные контуры на изображении, если толщина> 0, или заполняет область, ограниченную контурами, если толщина <0.» Это сделано для того, чтобы мы могли проверить соотношение ненулевых пикселей, чтобы решить, может ли поле содержать текст.
rtkaleta
15

Вы можете попробовать этот метод , разработанный Chucai Yi и Yingli Tian.

Они также используют программное обеспечение (которое основано на Opencv-1.0 и должно работать на платформе Windows), которое вы можете использовать (хотя исходный код недоступен). Он сгенерирует все текстовые ограничительные рамки (показанные в цветных тенях) на изображении. Применяя свои образцы изображений, вы получите следующие результаты:

Примечание: чтобы сделать результат более надежным, вы можете дополнительно объединить соседние блоки.


Обновление: если ваша конечная цель заключается в распознавании текстов на изображении, вы можете дополнительно проверить gttext , бесплатное программное обеспечение для распознавания текста и инструмент Ground Truthing для цветных изображений с текстом. Исходный код также доступен.

С этим вы можете получить признанные тексты, такие как:

herohuyongtao
источник
gttext для окон. Любое предложение для пользователей 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 # версия ответа от дханушки с использованием 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"))
Хакан Усакли
источник