iOS 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

работает хорошо (что говорит мне, что сам проект настроен правильно), но всякий раз, когда я попробуйте использовать другие изображения, распознанный текст полная лажа. Например, я попытался сделать снимок моего finder, отображающего образец изображения:

https://dl.dropboxusercontent.com/u/607872/tesseract.jpg (1,5 МБ)

но Тессеракт признает:

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

в ответ на Томаса:

я следил за учебником в вашем посте, но столкнулся с несколькими ошибками на этом пути...

  • the UIImage+OpenCV категория не может использоваться в моем проекте ARC
  • я не могу импортировать <opencv2/...> в мои контроллеры, автозаполнение не предлагает его (и, следовательно,[UIImage CVMat] не определен)

Я думаю, что что-то не так с моей интеграцией OpenCV, хотя я следовал учебнику Hello и добавил фреймворк. Требуется ли мне также создавать OpenCV на моем Mac или достаточно просто включить фреймворк в мой проект Xcode?

поскольку я действительно не знаю, что вы можете считать "важным" на данный момент (я уже прочитал несколько сообщений и учебников и пробовал разные шаги), не стесняйтесь спрашивать:)


обновление #2

@Tomas: спасибо, дуга-часть была необходима. Мой ViewController уже переименован в .mm. Забудьте часть о " не удается импортировать opencv2/", так как я уже включил ее в свой TestApp-Prefix.pch (как указано в Hello-tutorial).

на следующий вызов ;)

я заметил, что когда я использую изображения, сделанные с помощью камеры, границы для roi объект не вычисляется успешно. Я поиграл с ориентацией устройства и поставил UIImage на мой взгляд, чтобы увидеть шаги обработки изображений, но иногда (даже когда изображение правильно выровнено) значения отрицательны, потому что if-условие bounds.size()-for-цикл не выполняется. Худший случай, который у меня был: minX/Y и maxX/Y никогда не касались. Короче говоря: строка, начинающаяся с Mat roi = inranged(cv::Rect( выдает исключение (утверждение не удалось, потому что значения были < 0 ). Я не знайте, имеет ли значение количество контуров, но я предполагаю это, потому что чем больше изображения, тем более вероятно исключение утверждения.

чтобы быть совершенно честным: у меня не было времени прочитать документацию OpenCV и понять, что делает ваш код, но на данный момент я не думаю, что есть способ обойти. Похоже, к сожалению для меня, моя начальная задача (получение сканирования, запуск OCR, отображение элементов в таблице) требует больше ресурсов (=времени), чем я думал.

5 ответов


нет ничего плохого в том, как вы делаете снимки с вашего iPad как такового. Но вы просто не можете бросить такой сложный образ и ожидать, что Тессеракт волшебным образом определит, какой текст извлечь. Взгляните поближе на изображение, и вы заметите, что у него нет равномерной молнии, это очень шумно, поэтому это может быть не лучший образец для начала игры.

в таких сценариях обязательно предварительно обработать изображение, чтобы предоставить библиотеке 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 категория может быть найдена здесь. Если ты под ... Проверка дуги этой.
  • посмотри этой чтобы начать работу с OpenCV в Xcode. Обратите внимание, что OpenCV-это платформа C++, которая не может быть импортирована в простые исходные файлы C (или Objective-C). Самый простой обходной путь-переименовать контроллер представления .м .мм (объективные-C++) и импортировать его в ваш проект.

существует другое поведение результата Тессеракта.

  • это требует хорошего качества изображения означает хорошую видимость текстуры.
  • большой размер изображения занимает много времени, чтобы обработать его также хорошо, чтобы изменить его размер на маленький перед обработкой.
  • это будет хорошо, чтобы выполнить некоторый цветовой эффект на изображение перед отправкой его в тессеракт. Используйте эффекты, которые могут улучшить видимость изображения.
  • существует когда-то другое поведение обработки фото с помощью камеры или альбома камеры.

в случае фотографирования непосредственно с камеры попробуйте функцию ниже.

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

преобразование 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];

Я боролся с распознаванием символов Тессеракта в течение нескольких недель. Вот две вещи, которые я узнал, чтобы заставить его лучше работать...

  1. Если вы знаете, какой шрифт вы будете читать, очистите обучение и переобучите его только для этого шрифта. Несколько шрифтов замедляет обработку OCR, а также увеличивает неоднозначность в процессе принятия решения Tesseract. Это приведет к большей точности и скорости.

  2. после обработки OCR действительно необходимый. Вы получите матрицу символов, которую распознает Тессеракт. Вам нужно будет дополнительно обработать символы, чтобы сузить то, что вы пытаетесь прочитать. Так, например, если ваше приложение читает этикетки продуктов питания, знание правил для слов и предложений, составляющих этикетку, поможет распознать ряд символов, составляющих эту этикетку.


быстрый эквивалент ответа @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

}