OpenCV自适应阈值OCR

17

我正在使用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);
这种方法太慢了,而且效果不好。 enter image description here enter image description here 你有什么建议能让这个方法更有效吗?这些图片来自于 iPhone 相机。
使用 Andry 的建议后。 enter image description here
    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,因为该方法需要单通道图像。


2
第三个参数不是卷积掩模的半径吗?必须是奇数且非零。 - danh
是的,你说得对,让我去检查一下默认值并尝试一下。编辑:尝试了几个,但结果几乎没有改变,还有其他建议吗? - user3247146
将自适应阈值的块大小参数更改为较高的值,例如25等。 - Abid Rahman K
3个回答

19

这是我的结果:在此输入图像描述

这是代码:

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

13
也许你应该为你的方法和代码添加更多的解释。 - flowfree
1
你可以用以下代码替换这行:patch=I(cv::Rect(j,i,blockSide,blockSide)); - Andrey Smorodov
2
是的,图像必须转换为灰度。我没有这样做是因为Mat Img = imread(“Test2.JPG”,0);以灰度加载图像。 - Andrey Smorodov
2
我不是iOS编程专家,但你的输出图像是彩色图像,所以我认为你在某个地方错过了颜色转灰度的步骤。在运行时使用调试器检查图像类型。这也可能是输出格式图像的问题。在显示之前,你可能需要将结果转换回BGR。 - Andrey Smorodov
1
尝试将比例因子设置为255.0。(cvImage.convertTo(cvImage,CV_8UC3,255.0);) - Andrey Smorodov
显示剩余17条评论

9

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?当我尝试运行你的片段时,我的应用程序会崩溃并显示致命信号11(SIGSEGV)错误。你知道这是为什么吗? - Andrey Mohyla
1
我正在使用OpenCV的2.4.8版本。如果你发现错误的原因,请在评论中写下来,因为对我来说代码没有错误。你应该在谷歌上搜索那个错误代码以获取一些线索。 - Dainius Šaltenis
1
所以我已经将OpenCV更改为2.4.8,现在一切都正常了。我找不到导致崩溃的行,因为这个错误涉及到C++ OpenCV库。 - Andrey Mohyla

2
由于光线几乎均匀,前景与背景很容易区分。因此,我认为直接使用OTSU阈值(与@Andrey在文本区域中的答案几乎相同)对OCR来说是可以的。

enter image description here


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)

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