OpenCV 自适应阈值 OCR
Posted
技术标签:
【中文标题】OpenCV 自适应阈值 OCR【英文标题】:OpenCV Adaptive Threshold OCR 【发布时间】:2014-04-03 01:23:04 【问题描述】:我正在使用 OpenCV 为 iPhone 相机的 OCR 准备图像,但我无法获得准确 OCR 扫描所需的结果。这是我现在使用的代码。
cv::cvtColor(cvImage, cvImage, CV_BGR2GRAY);
cv::medianBlur(cvImage, cvImage, 0);
cv::adaptiveThreshold(cvImage, cvImage, 255, CV_ADAPTIVE_THRESH_MEAN_C, CV_THRESH_BINARY, 5, 4);
此方法花费的时间有点太长,并且没有给我带来好的结果。
关于如何使这更有效的任何建议?图片来自 iPhone 相机。
在使用了 Andry 的建议之后。
cv::Mat cvImage = [self cvMatFromUIImage:image];
cv::Mat res;
cv::cvtColor(cvImage, cvImage, CV_RGB2GRAY);
cvImage.convertTo(cvImage,CV_32FC1,1.0/255.0);
CalcBlockMeanVariance(cvImage,res);
res=1.0-res;
res=cvImage+res;
cv::threshold(res,res, 0.85, 1, cv::THRESH_BINARY);
cv::resize(res, res, cv::Size(res.cols/2,res.rows/2));
image = [self UIImageFromCVMat:cvImage];
方法:
void CalcBlockMeanVariance(cv::Mat Img,cv::Mat Res,float blockSide=21) // blockSide - the parameter (set greater for larger font on image)
cv::Mat I;
Img.convertTo(I,CV_32FC1);
Res=cv::Mat::zeros(Img.rows/blockSide,Img.cols/blockSide,CV_32FC1);
cv::Mat inpaintmask;
cv::Mat patch;
cv::Mat smallImg;
cv::Scalar m,s;
for(int i=0;i<Img.rows-blockSide;i+=blockSide)
for (int j=0;j<Img.cols-blockSide;j+=blockSide)
patch=I(cv::Rect(j,i,blockSide,blockSide));
cv::meanStdDev(patch,m,s);
if(s[0]>0.01) // Thresholding parameter (set smaller for lower contrast image)
Res.at<float>(i/blockSide,j/blockSide)=m[0];
else
Res.at<float>(i/blockSide,j/blockSide)=0;
cv::resize(I,smallImg,Res.size());
cv::threshold(Res,inpaintmask,0.02,1.0,cv::THRESH_BINARY);
cv::Mat inpainted;
smallImg.convertTo(smallImg,CV_8UC1,255);
inpaintmask.convertTo(inpaintmask,CV_8UC1);
inpaint(smallImg, inpaintmask, inpainted, 5, cv::INPAINT_TELEA);
cv::resize(inpainted,Res,Img.size());
Res.convertTo(Res,CV_32FC1,1.0/255.0);
知道为什么我会得到这个结果吗? OCR 结果非常好,但如果我能得到与你得到的图像相似的图像会更好。如果这很重要,我正在为 ios 开发。我不得不使用cvtColor
,因为该方法需要一个单通道图像。
【问题讨论】:
第三个参数不是卷积掩码的半径吗?必须是奇数且非零。 是的,你说得对,让我去看看默认值是什么,然后试试。编辑:尝试了一些,几乎没有改变结果,还有什么? 将自适应阈值的blocksize参数改为一些更高的值,比如25等 【参考方案1】:这是我的结果:
代码如下:
#include <iostream>
#include <vector>
#include <stdio.h>
#include <stdarg.h>
#include "opencv2/opencv.hpp"
#include "fstream"
#include "iostream"
using namespace std;
using namespace cv;
//-----------------------------------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------------------------------
void CalcBlockMeanVariance(Mat& Img,Mat& Res,float blockSide=21) // blockSide - the parameter (set greater for larger font on image)
Mat I;
Img.convertTo(I,CV_32FC1);
Res=Mat::zeros(Img.rows/blockSide,Img.cols/blockSide,CV_32FC1);
Mat inpaintmask;
Mat patch;
Mat smallImg;
Scalar m,s;
for(int i=0;i<Img.rows-blockSide;i+=blockSide)
for (int j=0;j<Img.cols-blockSide;j+=blockSide)
patch=I(Range(i,i+blockSide+1),Range(j,j+blockSide+1));
cv::meanStdDev(patch,m,s);
if(s[0]>0.01) // Thresholding parameter (set smaller for lower contrast image)
Res.at<float>(i/blockSide,j/blockSide)=m[0];
else
Res.at<float>(i/blockSide,j/blockSide)=0;
cv::resize(I,smallImg,Res.size());
cv::threshold(Res,inpaintmask,0.02,1.0,cv::THRESH_BINARY);
Mat inpainted;
smallImg.convertTo(smallImg,CV_8UC1,255);
inpaintmask.convertTo(inpaintmask,CV_8UC1);
inpaint(smallImg, inpaintmask, inpainted, 5, INPAINT_TELEA);
cv::resize(inpainted,Res,Img.size());
Res.convertTo(Res,CV_32FC1,1.0/255.0);
//-----------------------------------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------------------------------
int main( int argc, char** argv )
namedWindow("Img");
namedWindow("Edges");
//Mat Img=imread("D:\\ImagesForTest\\BookPage.JPG",0);
Mat Img=imread("Test2.JPG",0);
Mat res;
Img.convertTo(Img,CV_32FC1,1.0/255.0);
CalcBlockMeanVariance(Img,res);
res=1.0-res;
res=Img+res;
imshow("Img",Img);
cv::threshold(res,res,0.85,1,cv::THRESH_BINARY);
cv::resize(res,res,cv::Size(res.cols/2,res.rows/2));
imwrite("result.jpg",res*255);
imshow("Edges",res);
waitKey(0);
return 0;
还有 Python 端口:
import cv2 as cv
import numpy as np
#-----------------------------------------------------------------------------------------------------
#
#-----------------------------------------------------------------------------------------------------
def CalcBlockMeanVariance(Img,blockSide=21): # blockSide - the parameter (set greater for larger font on image)
I=np.float32(Img)/255.0
Res=np.zeros( shape=(int(Img.shape[0]/blockSide),int(Img.shape[1]/blockSide)),dtype=np.float)
for i in range(0,Img.shape[0]-blockSide,blockSide):
for j in range(0,Img.shape[1]-blockSide,blockSide):
patch=I[i:i+blockSide+1,j:j+blockSide+1]
m,s=cv.meanStdDev(patch)
if(s[0]>0.001): # Thresholding parameter (set smaller for lower contrast image)
Res[int(i/blockSide),int(j/blockSide)]=m[0]
else:
Res[int(i/blockSide),int(j/blockSide)]=0
smallImg=cv.resize(I,(Res.shape[1],Res.shape[0] ) )
_,inpaintmask=cv.threshold(Res,0.02,1.0,cv.THRESH_BINARY);
smallImg=np.uint8(smallImg*255)
inpaintmask=np.uint8(inpaintmask)
inpainted=cv.inpaint(smallImg, inpaintmask, 5, cv.INPAINT_TELEA)
Res=cv.resize(inpainted,(Img.shape[1],Img.shape[0] ) )
Res=np.float32(Res)/255
return Res
#-----------------------------------------------------------------------------------------------------
#
#-----------------------------------------------------------------------------------------------------
cv.namedWindow("Img")
cv.namedWindow("Edges")
Img=cv.imread("F:\\ImagesForTest\\BookPage.JPG",0)
res=CalcBlockMeanVariance(Img)
res=1.0-res
Img=np.float32(Img)/255
res=Img+res
cv.imshow("Img",Img);
_,res=cv.threshold(res,0.85,1,cv.THRESH_BINARY);
res=cv.resize(res,( int(res.shape[1]/2),int(res.shape[0]/2) ))
cv.imwrite("result.jpg",res*255);
cv.imshow("Edges",res)
cv.waitKey(0)
【讨论】:
也许您应该为您的方法和代码添加更多解释。 看这里:***.com/questions/12781874/… 您可以使用 cv::Rect 提取补丁(注意行和列与宽度和高度的顺序不同)。 您可以将这一行替换为:patch=I(cv::Rect(j,i,blockSide,blockSide)); 是的,图像必须转换为灰度。我没有这样做是因为 Mat Img=imread("Test2.JPG",0);以灰度加载图像。 我不是 iOS 编程专家,但您的输出图像是彩色图像,所以我认为您在某处错过了颜色到灰色的转换。在运行时使用调试器检查图像类型。输出格式图像也可能有问题。可能您需要在显示之前将结果转换回 BGR。【参考方案2】:JAVA 代码:自从提出这个问题以来已经过去了很长时间,但是我已经将这段代码从 C++ 重写为 Java,以防有人需要它(我需要用它来开发一个android studio 上的应用程序)。
public Bitmap Thresholding(Bitmap bitmap)
Mat imgMat = new Mat();
Utils.bitmapToMat(bitmap, imgMat);
imgMat.convertTo(imgMat, CvType.CV_32FC1, 1.0 / 255.0);
Mat res = CalcBlockMeanVariance(imgMat, 21);
Core.subtract(new MatOfDouble(1.0), res, res);
Imgproc.cvtColor( imgMat, imgMat, Imgproc.COLOR_BGRA2BGR);
Core.add(imgMat, res, res);
Imgproc.threshold(res, res, 0.85, 1, Imgproc.THRESH_BINARY);
res.convertTo(res, CvType.CV_8UC1, 255.0);
Utils.matToBitmap(res, bitmap);
return bitmap;
public Mat CalcBlockMeanVariance (Mat Img, int blockSide)
Mat I = new Mat();
Mat ResMat;
Mat inpaintmask = new Mat();
Mat patch;
Mat smallImg = new Mat();
MatOfDouble mean = new MatOfDouble();
MatOfDouble stddev = new MatOfDouble();
Img.convertTo(I, CvType.CV_32FC1);
ResMat = Mat.zeros(Img.rows() / blockSide, Img.cols() / blockSide, CvType.CV_32FC1);
for (int i = 0; i < Img.rows() - blockSide; i += blockSide)
for (int j = 0; j < Img.cols() - blockSide; j += blockSide)
patch = new Mat(I,new Rect(j,i, blockSide, blockSide));
Core.meanStdDev(patch, mean, stddev);
if (stddev.get(0,0)[0] > 0.01)
ResMat.put(i / blockSide, j / blockSide, mean.get(0,0)[0]);
else
ResMat.put(i / blockSide, j / blockSide, 0);
Imgproc.resize(I, smallImg, ResMat.size());
Imgproc.threshold(ResMat, inpaintmask, 0.02, 1.0, Imgproc.THRESH_BINARY);
Mat inpainted = new Mat();
Imgproc.cvtColor(smallImg, smallImg, Imgproc.COLOR_RGBA2BGR);
smallImg.convertTo(smallImg, CvType.CV_8UC1, 255.0);
inpaintmask.convertTo(inpaintmask, CvType.CV_8UC1);
Photo.inpaint(smallImg, inpaintmask, inpainted, 5, Photo.INPAINT_TELEA);
Imgproc.resize(inpainted, ResMat, Img.size());
ResMat.convertTo(ResMat, CvType.CV_32FC1, 1.0 / 255.0);
return ResMat;
【讨论】:
你用的是什么版本的openCV?当我尝试运行您的 sn-p 时,我的应用程序因致命信号 11 (SIGSEGV) 错误而崩溃。你知道为什么会这样吗? 我使用的是 2.4.8 版本的 OpenCV。您应该在谷歌中搜索该错误代码以获取一些线索,因为我的代码可以正常工作。如果您发现导致错误的原因,请将其写入 cmets。 所以我已将 OpenCV 更改为 2.4.8,现在一切正常)我找不到导致崩溃的行,因为此错误涉及 C++ openCV 库。【参考方案3】:由于光线几乎均匀,前景与背景很容易区分。所以我认为直接阈值(使用 OTSU)对于 OCR 是可以的。 (与@Andrey 在文本区域中的答案几乎相同)。
Python 中的 OpenCV 3 代码:
#!/usr/bin/python3
# 2018.01.17 16:41:20 CST
import cv2
import numpy as np
img = cv2.imread("ocr.jpg")
gray = cv2.cvtColor(median, cv2.COLOR_BGR2GRAY)
th, threshed = cv2.threshold(gray,127,255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
print(th)
cv2.imwrite("res.png", threshed)
【讨论】:
以上是关于OpenCV 自适应阈值 OCR的主要内容,如果未能解决你的问题,请参考以下文章
youcans 的 OpenCV 例程200篇166.自适应阈值处理
OpenCV adaptiveThreshold(自适应阈值)
opencv阈值处理--threshold函数自适应阈值处理Otsu处理(大津法)
OpenCV-自适应阈值函数cv::adaptiveThreshold