如何使用OpenCV获取图像中所有文本的位置?

9
我有一张包含文字(数字和字母)的图片。我想获取此图中所有文字和数字的位置,并提取出所有文字。

enter image description here

如何获取我的图像中的坐标以及所有文本(数字和字母)?例如:10B、44、16、38、22B等。

你的TensorFlow版本是多少?如果你的版本是2.1,尝试安装2.0版本。 - gellezzz
1
向糟糕的问题投放赏金并不是一个好的做法。你没有展示出如何解决这个问题的知识,看起来你只是试图通过几个声望点来引诱开发者编写完整的解决方案。我并不期望看到完美的答案,但我相信如果你愿意支付人们的时间,在自由职业网站上可以得到更好的解决方案。 - karlphillip
@karlphillip 很抱歉,但我是个初学者,我需要一些入门级的东西,对吧?你能帮我解决这个吗? - user11247278
3个回答

13
这里是使用形态学操作来过滤非文本轮廓的潜在方法。其思路如下:
  1. 获取二进制图像。加载图像, 灰度化, 然后进行Otsu的阈值化

  2. 移除水平和垂直线条。使用水平和垂直内核创建cv2.getStructuringElement(), 然后用cv2.drawContours()移除线条。

  3. 移除对角线,圆形对象和曲线轮廓。使用轮廓面积cv2.contourArea()和轮廓逼近cv2.approxPolyDP()来隔离非文本轮廓。

  4. 提取文本ROI并OCR。找到轮廓并过滤ROI,然后使用Pytesseract进行OCR。


移除绿色高亮显示的水平线

enter image description here

移除了垂直线

enter image description here

移除各种非文本轮廓(如对角线、圆形对象和曲线)

enter image description here

检测到文本区域。

enter image description here

import cv2
import numpy as np
import pytesseract

pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"

# Load image, grayscale, Otsu's threshold
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
clean = thresh.copy()

# Remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15,1))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(clean, [c], -1, 0, 3)

# Remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,30))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(clean, [c], -1, 0, 3)

cnts = cv2.findContours(clean, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    # Remove diagonal lines
    area = cv2.contourArea(c)
    if area < 100:
        cv2.drawContours(clean, [c], -1, 0, 3)
    # Remove circle objects
    elif area > 1000:
        cv2.drawContours(clean, [c], -1, 0, -1)
    # Remove curve stuff
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    x,y,w,h = cv2.boundingRect(c)
    if len(approx) == 4:
        cv2.rectangle(clean, (x, y), (x + w, y + h), 0, -1)

open_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
opening = cv2.morphologyEx(clean, cv2.MORPH_OPEN, open_kernel, iterations=2)
close_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,2))
close = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, close_kernel, iterations=4)
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    area = cv2.contourArea(c)
    if area > 500:
        ROI = image[y:y+h, x:x+w]
        ROI = cv2.GaussianBlur(ROI, (3,3), 0)
        data = pytesseract.image_to_string(ROI, lang='eng',config='--psm 6')
        if data.isalnum():
            cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)
            print(data)

cv2.imwrite('image.png', image)
cv2.imwrite('clean.png', clean)
cv2.imwrite('close.png', close)
cv2.imwrite('opening.png', opening)
cv2.waitKey()

7
好的,这里是另一种可能的解决方案。我知道你使用Python - 我使用C ++。我会给你一些想法,希望如果你愿意的话,你能够实现这个答案。
主要思路是不使用预处理(至少在初始阶段不使用),而是专注于每个目标字符,获取一些属性,并根据这些属性过滤每个blob。
我试图不使用预处理,因为:1)过滤器和形态学阶段可能降低blob的质量,2)您的目标blob似乎表现出我们可以利用的一些特征,主要是:宽高比面积
请看,数字和字母都似乎比宽更高...此外,它们似乎在某个特定区域值内变化。例如,您希望丢弃"太宽""太大"的对象。
我的想法是过滤掉不在预先计算的值范围内的所有内容。我检查了字符(数字和字母),得出了最小、最大面积值和最小宽高比(这里指高度和宽度之间的比率)。

让我们来处理算法。首先读取图像并将其缩小到原始尺寸的一半。您的图像太大了。将其转换为灰度图像,并通过otsu获得二进制图像,以下是伪代码:

//Read input:
inputImage = imread( "diagram.png" );

//Resize Image;
resizeScale = 0.5;

inputResized = imresize( inputImage, resizeScale );

//Convert to grayscale;
inputGray = rgb2gray( inputResized );

//Get binary image via otsu:
binaryImage = imbinarize( inputGray, "Otsu" );

很好。我们将使用这张图片。您需要检查每个白色斑点,并应用一个“属性过滤器”。我正在使用“带有统计数据的连通组件”来循环遍历每个斑点并获取其面积和长宽比,在C++中可以这样做:
//Prepare the output matrices:
cv::Mat outputLabels, stats, centroids;
int connectivity = 8;

//Run the binary image through connected components:
int numberofComponents = cv::connectedComponentsWithStats( binaryImage, outputLabels, stats, centroids, connectivity );

//Prepare a vector of colors – color the filtered blobs in black
std::vector<cv::Vec3b> colors(numberofComponents+1);
colors[0] = cv::Vec3b( 0, 0, 0 ); // Element 0 is the background, which remains black.

//loop through the detected blobs:
for( int i = 1; i <= numberofComponents; i++ ) {

    //get area:
    auto blobArea = stats.at<int>(i, cv::CC_STAT_AREA);

    //get height, width and compute aspect ratio:
    auto blobWidth = stats.at<int>(i, cv::CC_STAT_WIDTH);
    auto blobHeight = stats.at<int>(i, cv::CC_STAT_HEIGHT);
    float blobAspectRatio = (float)blobHeight/(float)blobWidth;

    //Filter your blobs…

};

现在,我们将应用属性过滤器。这只是与预先计算的阈值进行比较。我使用了以下值:

Minimum Area: 40  Maximum Area:400
MinimumAspectRatio:  1

在您的for循环中,将当前blob属性与这些值进行比较。如果测试结果为正,则将blob“涂黑”。继续在for循环中:
    //Filter your blobs…

    //Test the current properties against the thresholds:
    bool areaTest =  (blobArea > maxArea)||(blobArea < minArea);
    bool aspectRatioTest = !(blobAspectRatio > minAspectRatio); //notice we are looking for TALL elements!

    //Paint the blob black:
    if( areaTest || aspectRatioTest ){
        //filtered blobs are colored in black:
        colors[i] = cv::Vec3b( 0, 0, 0 );
    }else{
        //unfiltered blobs are colored in white:
        colors[i] = cv::Vec3b( 255, 255, 255 );
    }

循环结束后,构建过滤后的图像:
cv::Mat filteredMat = cv::Mat::zeros( binaryImage.size(), CV_8UC3 );
for( int y = 0; y < filteredMat.rows; y++ ){
    for( int x = 0; x < filteredMat.cols; x++ )
    {
        int label = outputLabels.at<int>(y, x);
        filteredMat.at<cv::Vec3b>(y, x) = colors[label];
    }
}

就是这样了。你已经过滤掉了所有与你要查找的内容不相似的元素。运行算法后,你会得到以下结果:

enter image description here

我另外发现了blob的边界框,以更好地可视化结果:

enter image description here

正如您所看到的,一些元素被错误地检测出来。您可以优化“属性过滤器”以更好地识别您要查找的字符。更深入的解决方案需要一些机器学习,需要构建一个“理想特征向量”,从斑点中提取特征,并通过相似性度量比较两个向量。您还可以应用一些后处理方法来改善结果...

3

一种方法是使用滑动窗口(很耗费资源)。

确定图像中字符的大小(所有字符在图像中看起来都是相同大小的),并设置窗口大小。尝试使用Tesseract进行检测(输入图像需要进行预处理)。如果一个窗口连续检测到字符,则存储窗口的坐标。合并坐标并获取字符区域。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接