iOS Tesseract:糟糕的结果
Posted
技术标签:
【中文标题】iOS Tesseract:糟糕的结果【英文标题】:iOS Tesseract: bad results 【发布时间】:2014-06-30 20:41:03 【问题描述】:我刚开始接触 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;
来自项目模板的示例图片
效果很好(这告诉我项目本身设置正确),但每当我尝试使用其他图像时,识别的文本都是一团糟。例如,我尝试拍摄显示示例图像的 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
回应托马斯:
我按照您帖子中的教程进行操作,但在此过程中遇到了几个错误...
UIImage+OpenCV
类别不能在我的 ARC 项目中使用
我无法在我的控制器中导入<opencv2/...>
在我的控制器中,自动完成不提供它(因此[UIImage CVMat]
未定义)
我认为我对OpenCV的集成有问题,即使我跟随你好教程并添加了框架。我还需要在Mac上构建OpenCV,或者是否足以在我的Xcode项目中包含框架?
由于目前我真的不知道您可能认为什么是“重要的”(我已经阅读了几篇文章和教程并尝试了不同的步骤),请随时提问 :)
更新 #2
@Tomas:谢谢,ARC 部分是必不可少的。我的 ViewController 已经重命名为 .mm
。忘记了关于“无法导入OpenCV2 /”的一部分,因为我已经在我的TestApp-Prefix.pch
(如hello-tutorial中所述)。
迎接下一个挑战;)
我注意到,当我使用用相机拍摄的图像时,没有成功计算roi
987654333 @对象的界限。我玩弄了设备方向并在我的视图中放置了UIImage
以查看图像处理步骤,但有时(即使图像正确对齐)这些值也是负数,因为bounds.size()
中的if
-条件-for
-loop 不满足。我遇到的最坏情况:从未触及过 minX/Y 和 maxX/Y。长话短说:从@ 987654338开始的行@抛出异常(断言失败,因为值为987654339 @)。我不知道是否是轮廓的数量,但我假设是因为图像越大,断言异常的可能性就越有可能。
说实话:我没有时间阅读 OpenCV 的文档并了解您的代码的作用,但到目前为止,我认为没有办法解决。似乎不幸的是,我的初始任务(扫描收据,运行OCR,在表中显示项目)需要比我想象的更多资源(=时间)。
【问题讨论】:
【参考方案1】:从 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);
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);
// 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];
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
类别可以在here 中找到。如果您在 ARC 下,请查看 this。
查看this,让您开始在 Xcode 中使用 OpenCV。请注意,OpenCV 是一个 C++ 框架,不能在纯 C(或 Objective-C)源文件中导入。最简单的解决方法是将您的视图控制器从 .m 重命名为 .mm (Objective-C++) 并将其重新导入您的项目中。
【讨论】:
我已经用几个 cmets 更新了笔记部分 谢谢!我赞成您的帖子并再次编辑了我的问题。 如前所述,上面的示例只是一个简单的实现,仅涵盖了测试图像的特定情况。实现在不同条件下工作的预处理算法通常是一项复杂的任务,正如您自己意识到的那样,需要时间【参考方案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;
【讨论】:
【参考方案3】:我已经在 Tesseract 字符识别方面苦苦挣扎了好几个星期。为了让它更好地工作,我学到了以下两件事......
如果您知道要阅读的字体,请清除训练并仅针对该字体重新训练。多种字体会减慢 OCR 处理速度,并且还会增加 Tesseract 决策过程中的歧义。这将提高准确性和速度。
确实需要经过 OCR 处理。您最终会得到一个 Tesseract 识别的字符矩阵。您将需要进一步处理字符以缩小您要阅读的内容。例如,如果您的应用程序正在阅读食品标签,了解构成食品标签的单词和句子的规则将有助于识别构成该标签的一系列字符。
【讨论】:
如何训练具有特殊字符的新语言?你能提供任何例子吗?【参考方案4】:将您的 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];
【讨论】:
【参考方案5】:@FARAZ 的答案的快速等价物
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
【讨论】:
以上是关于iOS Tesseract:糟糕的结果的主要内容,如果未能解决你的问题,请参考以下文章