iOS Tesseract: 结果不佳

11
我刚开始使用Tesseract库,但结果真的很糟糕。我按照Git存储库中的说明进行操作(https://github.com/gali8/Tesseract-OCR-iOS)。我的ViewController使用以下方法开始识别:
Tesseract *t = [[Tesseract alloc] initWithLanguage:@"deu"];
t.delegate = self;

[t setVariableValue:@"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" forKey:@"tessedit_char_whitelist"];
[t setImage:img];

[t recognize];

NSLog( @"Recognized text: %@", [t recognizedText] );

labelRecognizedText.text = [t recognizedText];

t = nil;

项目模板中的示例图片the sample image效果良好(这告诉我项目本身设置正确),但每当我尝试使用其他图片时,识别出来的文本就会变得一团糟。例如,我试图拍摄我的finder显示示例图片:

https://dl.dropboxusercontent.com/u/607872/tesseract.jpg(1.5 MB)

但Tesseract识别成:

Recognized text: s f l TO  if v   Ysssifss f

 ssqxizg ss sfzzlj z

s N T IYIOGY Z I l EY s s

k Es ETL ZHE s UEY

z xhks Fsjs Es z VIII c 

s I   XFTZT c s  h V Ijzs

L s sk  sisijk J

s f s ssj Jss sssHss H VI

s s  H 

i s H st xzs
s s k 4  is x2 IV
Illlsiqss sssnsiisfjlisszxiij s
K

即使字符白名单只包含数字,我得到的结果与图像相差甚远:
Recognized text:       3          74 211  

    1             

         1 1    1    

  3  53 379     1  

3  1   33  5    3 2 
      3          9 73
    1  61 2 2  
    3   1  6   5 212   7 
       1 
4     9 4  
           1  17
 111  11    1 1  11  1 1 1 1
 

我猜测使用的iPad mini相机拍摄照片存在问题,但我无法确定是什么问题以及原因。
有任何提示吗?

更新 #1

回复Tomas:

我按照你的帖子中的教程操作,但是在过程中遇到了几个错误...

  • UIImage+OpenCV类别无法在我的ARC项目中使用
  • 我不能在我的控制器中导入<opencv2/...>,自动补全不提供它(因此[UIImage CVMat]未定义)

我认为我的OpenCV集成出了问题,尽管我遵循了Hello-tutorial并添加了框架。我是否需要在我的Mac上构建OpenCV,还是仅将框架包含在我的Xcode项目中就足够了?

由于我真的不知道您在此时可能会考虑哪些内容“重要”(我已经阅读了几篇帖子和教程,并尝试了不同的步骤),请随意询问 :)


更新 #2

@Tomas:非常感谢,ARC 部分很关键。我的 ViewController 已经被重命名为 .mm。关于"无法导入 opencv2/"的部分可以不用管了,因为我已经在我的 TestApp-Prefix.pch 中包含了它(正如 Hello 教程中所述)。

接下来是下一个挑战 ;)

我发现,当我使用相机拍摄的图像时,roi 对象的边界计算不成功。我尝试调整设备方向,并在我的视图中放置一个 UIImage 来查看图像处理步骤,但有时候(即使图像正确对齐),值仍然为负数,因为 if 条件没有得到满足。最糟糕的情况是 minX/Y 和 maxX/Y 从未被触及。长话短说:以 Mat roi = inranged(cv::Rect( 开头的那行代码会抛出异常(断言失败,因为这些值是 < 0)。我不知道轮廓数量是否有影响,但我认为有,因为图像越大,断言异常的可能性就越大。

说实话,我还没有时间阅读OpenCV的文档并理解你的代码是做什么的,但目前来看,我认为没有绕过的办法。很遗憾,我的初始任务(扫描收据、运行OCR、在表格中显示物品)需要比我想象的更多的资源(时间)。
5个回答

7
你从iPad中获取照片的方式本身没有问题。但是,如果你直接将这样一个复杂的图像传送给Tesseract,期望它能自动确定要提取的文本,那就有些困难了。仔细观察一下这个图像,你会发现它的光线不均匀,噪声极大,可能不是开始处理的最佳示例。
在这种情况下,必须对图像进行预处理,以便为tesseract库提供更简单的识别内容。
以下是一个使用OpenCV (http://www.opencv.org)的非常简单的预处理示例,它是一个流行的图像处理框架。这应该能为你提供一个启动点的想法。
#import <TesseractOCR/TesseractOCR.h>
#import <opencv2/opencv.hpp>
#import "UIImage+OpenCV.h"

using namespace cv;

...

// load source image
UIImage *img = [UIImage imageNamed:@"tesseract.jpg"];

Mat mat = [img CVMat];
Mat hsv;

// convert to HSV (better than RGB for this task)
cvtColor(mat, hsv, CV_RGB2HSV_FULL);

// blur is slightly to reduce noise impact
const int blurRadius = img.size.width / 250;
blur(hsv, hsv, cv::Size(blurRadius, blurRadius)); 

// in range = extract pixels within a specified range
// here we work only on the V channel extracting pixels with 0 < V < 120
Mat inranged;
inRange(hsv, cv::Scalar(0, 0, 0), cv::Scalar(255, 255, 120), inranged); 

enter image description here

Mat inrangedforcontours;
inranged.copyTo(inrangedforcontours); // findContours alters src mat

// now find contours to find where characters are approximately located
vector<vector<cv::Point> > contours;
vector<Vec4i> hierarchy;

findContours(inrangedforcontours, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0));

int minX = INT_MAX;
int minY = INT_MAX;
int maxX = 0;
int maxY = 0;

// find all contours that match expected character size
for (size_t i = 0; i < contours.size(); i++)
{
    cv::Rect brect = cv::boundingRect(contours[i]);
    float ratio = (float)brect.height / brect.width;

    if (brect.height > 250 && ratio > 1.2 && ratio < 2.0)
    {
        minX = MIN(minX, brect.x);
        minY = MIN(minY, brect.y);
        maxX = MAX(maxX, brect.x + brect.width);
        maxY = MAX(maxY, brect.y + brect.height);
    }
}

enter image description here

// Now we know where our characters are located
// extract relevant part of the image adding a margin that enlarges area
const int margin = img.size.width / 50;
Mat roi = inranged(cv::Rect(minX - margin, minY - margin, maxX - minX + 2 * margin, maxY - minY + 2 * margin));
cvtColor(roi, roi, CV_GRAY2BGRA);
img = [UIImage imageWithCVMat:roi];

enter image description here

Tesseract *t = [[Tesseract alloc] initWithLanguage:@"eng"];

[t setVariableValue:@"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" forKey:@"tessedit_char_whitelist"];
[t setImage:img];

[t recognize];

NSString *recognizedText = [[t recognizedText] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

if ([recognizedText isEqualToString:@"1234567890"])
    NSLog(@"Yeah!");
else
    NSLog(@"Epic fail...");

注意事项

  • UIImage+OpenCV类别可以在这里找到。如果您使用的是ARC,请查看这里
  • 查看这个链接,以帮助您在Xcode中开始使用OpenCV。请注意,OpenCV是一个C++框架,无法导入到普通的C(或Objective-C)源文件中。最简单的解决方法是将您的视图控制器从.m重命名为.mm(Objective-C++),并重新导入到您的项目中。

我已经更新了注释部分,加入了一些评论。 - Tomas Camin
谢谢!我已经点赞了你的帖子并再次编辑了我的问题。 - Dennis
如上所述,上面的例子只是一个天真的实现,仅涵盖了您测试图像的特定情况。实现适用于不同条件的预处理算法通常是一项复杂的任务,正如您自己意识到的那样,需要时间。 - Tomas Camin

2

Tesseract的结果会有不同的行为。

  • 它需要图像质量较好,即纹理可见性较好。
  • 处理大尺寸图片需要更长时间。在处理之前将其缩小也是一个好方法。
  • 在将图像发送到Tesseract之前最好对图像执行一些颜色效果。使用可以增强图像可见性的效果。
  • 使用相机或相机相册处理照片时,有时会出现不同的处理行为。

如果直接从相机拍摄照片,请尝试以下函数。

- (UIImage *) getImageForTexture:(UIImage *)src_img{
CGColorSpaceRef d_colorSpace = CGColorSpaceCreateDeviceRGB();
/*
 * Note we specify 4 bytes per pixel here even though we ignore the
 * alpha value; you can't specify 3 bytes per-pixel.
 */
size_t d_bytesPerRow = src_img.size.width * 4;
unsigned char * imgData = (unsigned char*)malloc(src_img.size.height*d_bytesPerRow);
CGContextRef context =  CGBitmapContextCreate(imgData, src_img.size.width,
                                              src_img.size.height,
                                              8, d_bytesPerRow,
                                              d_colorSpace,
                                              kCGImageAlphaNoneSkipFirst);

UIGraphicsPushContext(context);
// These next two lines 'flip' the drawing so it doesn't appear upside-down.
CGContextTranslateCTM(context, 0.0, src_img.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Use UIImage's drawInRect: instead of the CGContextDrawImage function, otherwise you'll have issues when the source image is in portrait orientation.
[src_img drawInRect:CGRectMake(0.0, 0.0, src_img.size.width, src_img.size.height)];
UIGraphicsPopContext();

/*
 * At this point, we have the raw ARGB pixel data in the imgData buffer, so
 * we can perform whatever image processing here.
 */

// After we've processed the raw data, turn it back into a UIImage instance.
CGImageRef new_img = CGBitmapContextCreateImage(context);
UIImage * convertedImage = [[UIImage alloc] initWithCGImage:
                            new_img];

CGImageRelease(new_img);
CGContextRelease(context);
CGColorSpaceRelease(d_colorSpace);
free(imgData);
return convertedImage;
}

2

我已经几周都在为Tesseract字符识别苦苦挣扎。以下是我学到的两件事,可以帮助它更好地工作...

  1. 如果您知道将要读取的字体,请清除训练并仅重新训练该字体。多种字体会减慢OCR处理速度,并增加Tesseract决策过程中的模糊性。这将提高准确性和速度。

  2. 在OCR处理之后,真正需要进一步处理字符来缩小您想要读取的范围。例如,如果您的应用程序正在阅读食品标签,了解构成食品标签的单词和句子的规则将有助于识别组成该标签的一系列字符。


我如何训练一个包含特殊字符的新语言?你能提供任何例子吗? - Ramakrishna

1
将您的UIImage从srgb格式转换为rgb格式。
如果您使用的是IOS 5.0及以上版本,请使用:#import <Accelerate/Accelerate.h>
否则,请取消注释//IOS 3.0-5.0。
-(UIImage *) createARGBImageFromRGBAImage: (UIImage*)image 
{   //CGSize size = CGSizeMake(320, 480);
    CGSize dimensions = CGSizeMake(320, 480);
    NSUInteger bytesPerPixel = 4;
    NSUInteger bytesPerRow = bytesPerPixel * dimensions.width;
    NSUInteger bitsPerComponent = 8;

    unsigned char *rgba = malloc(bytesPerPixel * dimensions.width * dimensions.height);
    unsigned char *argb = malloc(bytesPerPixel * dimensions.width * dimensions.height);

    CGColorSpaceRef colorSpace = NULL;
    CGContextRef context = NULL;

    colorSpace = CGColorSpaceCreateDeviceRGB();
    context = CGBitmapContextCreate(rgba, dimensions.width, dimensions.height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault); // kCGBitmapByteOrder32Big
    CGContextDrawImage(context, CGRectMake(0, 0, dimensions.width, dimensions.height), [image CGImage]);
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);

    const vImage_Buffer src = { rgba, dimensions.height, dimensions.width, bytesPerRow };
    const vImage_Buffer dis = { rgba, dimensions.height, dimensions.width, bytesPerRow };
    const uint8_t map[4] = {3,0,1,2};
    vImagePermuteChannels_ARGB8888(&src, &dis, map, kvImageNoFlags);

    //IOS 3.0-5.0
    /*for (int x = 0; x < dimensions.width; x++) {
        for (int y = 0; y < dimensions.height; y++) {
            NSUInteger offset = ((dimensions.width * y) + x) * bytesPerPixel;
            argb[offset + 0] = rgba[offset + 3];
            argb[offset + 1] = rgba[offset + 0];
            argb[offset + 2] = rgba[offset + 1];
            argb[offset + 3] = rgba[offset + 2];
        }
    }*/




    colorSpace = CGColorSpaceCreateDeviceRGB();
    context = CGBitmapContextCreate(dis.data, dimensions.width, dimensions.height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrderDefault); // kCGBitmapByteOrder32Big
    CGImageRef imageRef = CGBitmapContextCreateImage(context);
    image = [UIImage imageWithCGImage: imageRef];
    CGImageRelease(imageRef);
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);

    free(rgba);
    free(argb);

    return image;
}

Tesseract *t = [[Tesseract alloc] initWithLanguage:@"eng"];

[t setVariableValue:@"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" forKey:@"tessedit_char_whitelist"];
[t setImage:[self createARGBImageFromRGBAImage:img]];

[t recognize];

0

这是@FARAZ答案的Swift版本

func getImageForTexture(srcImage: UIImage) -> UIImage{
    let d_colorSpace = CGColorSpaceCreateDeviceRGB()
    let d_bytesPerRow: size_t = Int(srcImage.size.width) * 4
    /*
    * Note we specify 4 bytes per pixel here even though we ignore the
    * alpha value; you can't specify 3 bytes per-pixel.
    */
    let imgData = malloc(Int(srcImage.size.height) * Int(d_bytesPerRow))

    let context = CGBitmapContextCreate(imgData, Int(srcImage.size.width), Int(srcImage.size.height), 8, Int(d_bytesPerRow), d_colorSpace,CGImageAlphaInfo.NoneSkipFirst.rawValue)
    UIGraphicsPushContext(context!)
    // These next two lines 'flip' the drawing so it doesn't appear upside-down.
    CGContextTranslateCTM(context, 0.0, srcImage.size.height)
    CGContextScaleCTM(context, 1.0, -1.0)
    // Use UIImage's drawInRect: instead of the CGContextDrawImage function, otherwise you'll
    srcImage.drawInRect(CGRectMake(0.0, 0.0, srcImage.size.width, srcImage.size.height))
    UIGraphicsPopContext()
    /*
    * At this point, we have the raw ARGB pixel data in the imgData buffer, so
    * we can perform whatever image processing here.
    */

    // After we've processed the raw data, turn it back into a UIImage instance.

    let new_img = CGBitmapContextCreateImage(context)
    let convertedImage = UIImage(CGImage: new_img!)
    return convertedImage

}

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