IOS Tesseract: плохие результаты
Я только начал загрязнять руки в библиотеке 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;
Образец изображения из проекта tempalte ![the sample image]()
работает хорошо (это говорит мне, что сам проект настроен правильно), но всякий раз, когда я пытаюсь использовать другие изображения, признанный текст является полным беспорядком. Например, я попытался сделать снимок моего искателя, отображающего образец изображения:
https://dl.dropboxusercontent.com/u/607872/tesseract.jpg (1,5 МБ)
Но 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, которую я сейчас использую, но я не могу понять, что и почему.
Любые подсказки?
Обновление # 1
В ответ на Tomas:
Я следил за учебником в вашем посте, но столкнулся с несколькими ошибками на пути...
- Категория
UIImage+OpenCV
не может использоваться в моем проекте ARC
- Я не могу импортировать
<opencv2/...>
в мои контроллеры, автозаполнение не предлагает (и поэтому [UIImage CVMat]
не определено)
Я думаю, что что-то не так с моей интеграцией OpenCV, хотя я следовал за Hello-tutorial и добавил фреймворк. Должен ли я создавать OpenCV на моем Mac или достаточно просто включить фреймворк в мой проект Xcode?
Так как я действительно не знаю, что вы можете считать "важным" на этом этапе (я уже читал несколько сообщений и руководств и пробовал разные шаги), не стесняйтесь спрашивать:)
Обновление # 2
@Томас: спасибо, ARC-часть была существенной. Мой ViewController уже переименован в .mm
. Забудьте о том, что "невозможно импортировать opencv2/", поскольку я уже включил его в свой TestApp-Prefix.pch
(как указано в Hello-tutorial).
К следующему вызову;)
Я заметил, что когда я использую снимки, сделанные с камерой, оценки для объекта roi
не вычисляются успешно. Я играл с ориентацией устройства и ставил UIImage
на мой взгляд, чтобы увидеть шаги обработки изображения, но иногда (даже когда изображение правильно выровнено) значения отрицательны, поскольку if
-condition в bounds.size()
- for
-loop не выполняется. В худшем случае у меня были: minX/Y и maxX/Y никогда не были затронуты. Короче говоря: строка, начинающаяся с Mat roi = inranged(cv::Rect(
, выдает исключение (утверждение не выполнено, потому что значения были < 0
). Я не знаю, имеет ли значение количество контуров, но я предполагаю, что чем больше изображений, тем вероятнее исключение утверждения.
Чтобы быть абсолютно честным: у меня не было времени прочитать документацию 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);
![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 this.
- Посмотрите этот, чтобы начать работу с OpenCV в Xcode. Обратите внимание, что OpenCV - это среда С++, которая не может быть импортирована в простые исходные файлы C (или Objective-C). Самое легкое обходное решение - переименовать контроллер вида с .m в .mm(Objective-C ++) и reimport в проект.
Ответ 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 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
Преобразуйте свой UIImage из srgb в формат rgb.
если вы используете IOS 5.0 и выше, используйте
использовать #import <Accelerate/Accelerate.h>
else uncomment//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];
Ответ 4
Я боролся с распознаванием символов Tesseract неделями. Вот две вещи, которые я научился, чтобы заставить ее работать лучше...
-
Если вы знаете, какой шрифт вы будете читать, очистите обучение и переучите его только для этого шрифта. Множественные шрифты замедляют обработку OCR, а также увеличивают двусмысленность в процессе принятия решений Tesseract. Это приведет к большей точности и скорости.
-
После обработки OCR действительно необходимо. Вы получите матрицу символов, распознанную Tesseract. Вам нужно будет продолжить обработку символов, чтобы сузить то, что вы пытаетесь прочитать. Например, если ваше приложение читает этикетки с пищевыми продуктами, знание правил для слов и предложений, составляющих этикетку продуктов, поможет распознать ряд символов, которые составляют эту метку.
Ответ 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 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
}