【问题标题】:Extracting text OpenCV提取文本 OpenCV
【发布时间】:2014-06-23 17:45:43
【问题描述】:

我正在尝试在图像中找到文本的边界框,目前正在使用这种方法:

// 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 分。

【问题讨论】:

  • 我在这里看到的最简单的方法是在获取区域之前增加对比度......
  • 很酷的问题。感谢您发布它并提供赏金以确保获得如此有趣的回复。
  • 编程新手。可以对梵文等英语以外的脚本中的文本进行相同的处理吗?

标签: c++ opencv image-processing text bounding-box


【解决方案1】:

我在下面的程序中使用了基于渐变的方法。添加了生成的图像。请注意,我使用的是缩小版的图像进行处理。

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

python版本

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)

【讨论】:

  • 我刚刚看了他的做法。我看到的主要区别是他使用的是 Sobel 滤波器,而我使用的是形态梯度滤波器。我认为形态过滤器和下采样可以消除很多不那么强的边缘。索贝尔可能会听到更多的噪音。
  • @ascenator 当您将 OTSU 与阈值类型结合使用时,它使用 Otsu 的阈值而不是指定的阈值。见here
  • @VishnuJayanand 你只需要将缩放应用到rect。有一个pyrdown,因此将rect 中的x, y, width, height 乘以4。
  • 能否请您提供第三个条件:水平投影中显着峰的数量或至少一些领先。
  • @DforTye 获取填充轮廓的水平投影(cv::reduce),然后对其进行阈值化(例如,使用平均高度或中值高度)。如果您将此结果可视化,它会看起来像条码。我想,当时我正在考虑计算酒吧的数量,并对其设置一个门槛。现在我认为,如果该区域足够干净,如果我们可以将其提供给 OCR 并获得每个检测到的字符的置信度以确保该区域包含文本,这也可能会有所帮助。
【解决方案2】:

您可以通过查找靠近的边缘元素来检测文本(灵感来自 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;
}

结果:

一个。元素 = getStructuringElement(cv::MORPH_RECT, cv::Size(17, 3) );

b.元素 = getStructuringElement(cv::MORPH_RECT, cv::Size(30, 30) );

上面提到的其他图像的结果相似。

【讨论】:

  • 车牌检测器。
  • 对于某些卡片,边界框不会包含所有文本,例如半个字母被截断。如这张卡片:i.imgur.com/tX3XrwH.jpg 如何将每个边界边界框的高度和宽度扩展n?感谢您的解决方案,效果很好!
  • cv::Rect a;。放大倍数:a.x-=n/2;a.y-=n/2;a.width+=n;a.height+=n;.
  • 嗨,我如何使用 python cv2 实现相同的结果?
  • BookCode.
【解决方案3】:

这是我用来检测文本块的另一种方法:

  1. 将图像转换为灰度
  2. 已应用threshold(简单的二进制阈值,阈值是精心挑选的150)
  3. 应用dilation 来加粗图像中的线条,从而产生更紧凑的对象和更少的空白碎片。使用了较高的迭代次数值,因此膨胀非常重(13 次迭代,也是为获得最佳结果而精心挑选的)。
  4. 使用 opencv findContours 函数识别结果图像中对象的轮廓。
  5. 画了一个bounding box(矩形)包围每个轮廓对象 - 每个对象都框住了一个文本块。
  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 步),我使用了黑色阈值。对于文本比背景浅的图像,例如您帖子中的第二张图像,应使用白色阈值,因此将 sholding 类型替换为cv2.THRESH_BINARY)。对于第二张图像,我还使用了稍高的阈值 (180)。改变阈值的参数和扩张的迭代次数将导致在图像中划分对象的灵敏度不同。

查找其他对象类型:

例如,将第一张图像中的膨胀减少到 5 次迭代可以让我们更精细地划分图像中的对象,粗略地找到图像中的所有单词(而不是文本块):

知道一个单词的粗略大小,在这里我丢弃了太小(低于 20 像素宽度或高度)或太大(高于 100 像素宽度或高度)的区域,以忽略不太可能是单词的对象,得到上图中的结果。

【讨论】:

  • 你太棒了!我会在早上试试这个。
  • 我添加了另一个丢弃无趣对象的步骤;还添加了用于识别单词或其他类型对象(而不是文本块)的示例
  • 感谢您的详细回答,但我在cv2.findContours 中收到错误消息。它说ValueError: too many values to unpack
  • 问题是函数cv2.findContours返回3个参数,而原始代码只捕获了2个。
  • @Abhijith cv2 在版本二中返回了两个参数,但现在,在版本三中,它返回 3
【解决方案4】:

@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++ 代码以供读者遵循。

它的效果几乎和原版一样好。我很乐意阅读如何改进/修复它以完全类似于原始结果的建议。

【讨论】:

  • 感谢提供python版本。许多人会发现这很有用。 +1
  • 填充轮廓和绘制轮廓有什么区别?我在这里找到了一个没有填充阶段的代码:stackoverflow.com/a/23556997/6837132
  • @SarahM 我不知道您是在询问绘图和填充之间的通用差异(我认为很明显?)还是 OpenCV API?如果是后者,则查看drawContoursdocs 状态“如果厚度> 0,该函数将在图像中绘制轮廓轮廓,如果厚度
【解决方案5】:

你可以试试初才易和田英利开发的this method

他们还共享一个您可以使用的软件(基于 Opencv-1.0,它应该在 Windows 平台下运行。)您可以使用(尽管没有可用的源代码)。它将生成图像中的所有文本边界框(以彩色阴影显示)。通过应用到您的示例图像,您将获得以下结果:

注意:为了使结果更加健壮,您可以进一步将相邻的框合并在一起。


更新:如果您的最终目标是识别图像中的文本,您可以进一步查看gttext,这是一款免费的 OCR 软件和用于带文本的彩色图像的 Ground Truthing 工具。源代码也可用。

有了这个,你可以得到被识别的文本,比如:

【讨论】:

  • gttext 适用于 Windows。对 Mac/Linux 用户的任何建议
【解决方案6】:

以上代码JAVA版本: 谢谢@威廉

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

【讨论】:

    【解决方案7】:

    这是来自 dhanushka 的 answer 的 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"));
    

    【讨论】:

      【解决方案8】:

      @dhanushka 解决方案的 Python 实现:

      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
      

      【讨论】:

      【解决方案9】:

      您可以使用 python 实现SWTloc

      完全披露:我是这个库的作者

      要做到这一点:-

      第一张和第二张图片

      注意这里的 text_mode 是 'lb_df',它代表 Light Background Dark Foreground,即这张图片中的文字将比背景颜色更深

      from swtloc import SWTLocalizer
      from swtloc.utils import imgshowN, imgshow
      
      swtl = SWTLocalizer()
      # Stroke Width Transform
      swtl.swttransform(imgpaths='img1.jpg', text_mode = 'lb_df',
                        save_results=True, save_rootpath = 'swtres/',
                        minrsw = 3, maxrsw = 20, max_angledev = np.pi/3)
      imgshow(swtl.swtlabelled_pruned13C)
      
      # Grouping
      respacket=swtl.get_grouped(lookup_radii_multiplier=0.9, ht_ratio=3.0)
      grouped_annot_bubble = respacket[2]
      maskviz = respacket[4]
      maskcomb  = respacket[5]
      
      # Saving the results
      _=cv2.imwrite('img1_processed.jpg', swtl.swtlabelled_pruned13C)
      imgshowN([maskcomb, grouped_annot_bubble], savepath='grouped_img1.jpg')
      


      第三张图片

      注意这里的 text_mode 是 'db_lf',代表 Dark Ba​​ckground Light Foreground,即这张图片中的文字将比背景颜色更浅

      from swtloc import SWTLocalizer
      from swtloc.utils import imgshowN, imgshow
      
      swtl = SWTLocalizer()
      # Stroke Width Transform
      swtl.swttransform(imgpaths=imgpaths[1], text_mode = 'db_lf',
                    save_results=True, save_rootpath = 'swtres/',
                    minrsw = 3, maxrsw = 20, max_angledev = np.pi/3)
      imgshow(swtl.swtlabelled_pruned13C)
      
      # Grouping
      respacket=swtl.get_grouped(lookup_radii_multiplier=0.9, ht_ratio=3.0)
      grouped_annot_bubble = respacket[2]
      maskviz = respacket[4]
      maskcomb  = respacket[5]
      
      # Saving the results
      _=cv2.imwrite('img1_processed.jpg', swtl.swtlabelled_pruned13C)
      imgshowN([maskcomb, grouped_annot_bubble], savepath='grouped_img1.jpg')
      

      您还会注意到完成的分组不是那么准确,为了获得所需的结果,因为图像可能会有所不同,请尝试调整 swtl.get_grouped() 函数中的分组参数。

      【讨论】:

        【解决方案10】:

        这是来自 dhanushka 的 answer 的 VB.NET 版本,使用 EmguCV

        EmguCV 中的一些函数和结构需要与带有 OpenCVSharp 的 C# 版本不同的考虑

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

        【讨论】:

          猜你喜欢
          • 2016-03-20
          • 1970-01-01
          • 2014-05-17
          • 2018-10-06
          • 1970-01-01
          • 2017-01-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多