Pul*_*gar 12 python opencv machine-learning image-processing deep-learning
我有一张包含文本(数字和字母)的图像。我想获取此图像中所有文本和数字的位置。我也想提取所有文本。
如何获取图像中的坐标以及所有文本(数字和字母)。例如 10B、44、16、38、22B 等
nat*_*ncy 15
这是使用形态学操作过滤掉非文本轮廓的潜在方法。这个想法是:
获取二值图像。加载图像,灰度,然后是 大津阈值
删除水平线和垂直线。使用创建水平和垂直内核,cv2.getStructuringElement然后删除行cv2.drawContours
删除对角线、圆形对象和曲线轮廓。使用轮廓区域cv2.contourArea
和轮廓近似过滤cv2.approxPolyDP
以隔离非文本轮廓
提取文本 ROI 和 OCR。使用Pytesseract查找轮廓并过滤 ROI,然后进行 OCR 。
删除了以绿色突出显示的水平线
删除了垂直线
删除了各种非文本轮廓(对角线、圆形对象和曲线)
检测到的文本区域
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()
Run Code Online (Sandbox Code Playgroud)
好的,这是另一种可能的解决方案。我知道您使用 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" );
Run Code Online (Sandbox Code Playgroud)
凉爽的。我们将使用此图像。您需要检查每个白色斑点,并应用“属性过滤器”。我使用带有统计信息的连接组件来循环遍历每个 blob 并获取其面积和纵横比,在 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…
};
Run Code Online (Sandbox Code Playgroud)
现在,我们将应用属性过滤器。这只是与预先计算的阈值的比较。我使用了以下值:
Minimum Area: 40 Maximum Area:400
MinimumAspectRatio: 1
Run Code Online (Sandbox Code Playgroud)
在for循环中,将当前的 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 );
}
Run Code Online (Sandbox Code Playgroud)
在循环之后,构造过滤后的图像:
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];
}
}
Run Code Online (Sandbox Code Playgroud)
而且……差不多就是这样。您过滤了所有与您要查找的内容不相似的元素。运行算法你会得到这个结果:
我还发现了 blob 的边界框以更好地可视化结果:
如您所见,某些元素未检测到。您可以优化“属性过滤器”以更好地识别您要查找的字符。一个更深层次的解决方案,涉及一点机器学习,需要构建一个“理想特征向量”,从 blob 中提取特征,并通过相似性度量比较两个向量。您还可以应用一些后处理来改善结果...
不管怎样,伙计,你的问题不是微不足道的,也不是容易扩展的,我只是给你一些想法。希望您能够实施您的解决方案。