OpenCV Adaptive Threshold OCR

Estoy usando OpenCV para preparar imágenes para OCR desde una cámara de iPhone, y he estado teniendo problemas para obtener los resultados que necesito para un escaneo de OCR preciso. Aquí está el código que estoy usando ahora.

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

Este método lleva demasiado tiempo y no me da buenos resultados. enter image description hereenter image description here

¿Alguna sugerencia sobre cómo podría hacer esto más efectivo? Las imágenes provienen de una cámara de iPhone.

Después de usar la sugerencia de 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]; 

Método:

 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;j0.01) // Thresholding parameter (set smaller for lower contrast image) { Res.at(i/blockSide,j/blockSide)=m[0]; }else { Res.at(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); } 

¿Alguna idea de por qué estoy obteniendo este resultado? Los resultados de OCR son bastante buenos, pero sería mejor si pudiera obtener una imagen similar a la que obtuviste. Estoy desarrollando para iOS si eso importa. Tuve que usar cvtColor porque el método espera una imagen de canal único.

Aquí está mi resultado: enter image description here

Aquí está el código:

 #include  #include  #include  #include  #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;i0.01) // Thresholding parameter (set smaller for lower contrast image) { Res.at(i/blockSide,j/blockSide)=m[0]; }else { Res.at(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; } 

CÓDIGO JAVA : Ha pasado mucho tiempo desde que se hizo esta pregunta, pero he reescrito este código de C ++ a Java en caso de que alguien lo necesite (tenía que usarlo para desarrollar una aplicación en el estudio de Android).

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

Como la luz es casi uniforme, y el primer plano se distingue fácilmente con el fondo. Así que creo que solo el umbral directo (usando OTSU) está bien para OCR. (Casi lo mismo con la respuesta de @ Andrey en regiones de texto).

enter image description here


Código OpenCV 3 en Python:

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