Tesseract-OCR学习系列(四)API

Other API Examples

参考文档:https://github.com/tesseract-ocr/tesseract/wiki/APIExample

在上一篇中, 我们学习了参考文档中的第一个示例。用CMake构建了工程,并且看了一下例子中调用到的API。在这一篇中,我们继续看一看其它的例子。但如何用CMake构建工程的方法就不赘述了。这里给出我写的例程,若有疑问之处,请阅读Tesseract-OCR学习系列(三)简例以及CMake简要教程这两篇文章。

GetComponentImages example

#include <tesseract/baseapi.h>
#include <leptonica/allheaders.h>
 
int main()
{
    Pix *image = pixRead("D:\\open_source\\tesseract-3.04.01\\tesseract\\testing\\phototest.tif");
    tesseract::TessBaseAPI *api = new tesseract::TessBaseAPI();
    api->Init(NULL, "eng");
    api->SetImage(image);
    Boxa* boxes = api->GetComponentImages(tesseract::RIL_TEXTLINE, true, NULL, NULL);
    printf("Found %d textline image components.\n", boxes->n);
    for (int i = 0; i < boxes->n; i++){
        BOX* box = boxaGetBox(boxes, i, L_CLONE);
        api->SetRectangle(box->x, box->y, box->w, box->h);
        char* ocrResult = api->GetUTF8Text();
        int conf = api->MeanTextConf();
        fprintf(stdout, "Box[%d]: x=%d, y=%d, w=%d, h=%d, confidence: %d, text: %s",
            i, box->x, box->y, box->w, box->h, conf, ocrResult);
    }
}

我们知道,如果要进行字符识别,首先要搜索到文字图块。或者说,找到包含字符的文字图块。这个例子帮助我们将每一个文字图块找到,并对文字图块进行识别。下面来看代码(之前说过的就不说了):

    Pix *image = pixRead("D:\\open_source\\tesseract-3.04.01\\tesseract\\testing\\phototest.tif");
    tesseract::TessBaseAPI *api = new tesseract::TessBaseAPI();
    api->Init(NULL, "eng");
    api->SetImage(image);

已经熟悉了,Pass……

    Boxa* boxes = api->GetComponentImages(tesseract::RIL_TEXTLINE, true, NULL, NULL);

这是最关键的一行代码。GetComponentImages用于查找图像内的图像块,并将分割到的图像块返回给Boxa这个结构中。

  Boxa* GetComponentImages(const PageIteratorLevel level,
                           const bool text_only,
                           Pixa** pixa, int** blockids)

那么,分割到什么程度呢?这是函数的第一个参数来控制的。

enum PageIteratorLevel {
  RIL_BLOCK,     // Block of text/image/separator line.
  RIL_PARA,      // Paragraph within a block.
  RIL_TEXTLINE,  // Line within a paragraph.
  RIL_WORD,      // Word within a textline.
  RIL_SYMBOL     // Symbol/character within a word.
};

也就是说,我们可以分割到一块、一段、一行、一个单词或者一个单字。这特别适合用于做文档的OCR。一份文档,有可能包含图像和大大小小的各种文字。用这个函数,就可以将图像、文字等单独拎出来,然后再分别进行处理。第二个参数,text_only如果是true的话,就表示只返回文字区域坐标,不返回图像区域坐标。pixa用于返回分割出来的图像。这里设为NULL,即表示不需要返回图像。blockids返回的是序列号。这里也不需要,所以设置成NULL。最后,返回值是分割到的矩形数组。

struct Box
{
    l_int32            x;
    l_int32            y;
    l_int32            w;
    l_int32            h;
    l_uint32           refcount;      /* reference count (1 if no clones)  */
 
};
typedef struct Box    BOX;
 
struct Boxa
{
    l_int32            n;             /* number of box in ptr array        */
    l_int32            nalloc;        /* number of box ptrs allocated      */
    l_uint32           refcount;      /* reference count (1 if no clones)  */
    struct Box       **box;           /* box ptr array                     */
};
typedef struct Boxa  BOXA;

接着,进入循环。

for (int i = 0; i < boxes->n; i++){
        BOX* box = boxaGetBox(boxes, i, L_CLONE);
        api->SetRectangle(box->x, box->y, box->w, box->h);
        char* ocrResult = api->GetUTF8Text();
        int conf = api->MeanTextConf();
        fprintf(stdout, "Box[%d]: x=%d, y=%d, w=%d, h=%d, confidence: %d, text: %s",
            i, box->x, box->y, box->w, box->h, conf, ocrResult);
    }

只有两个函数没有见过,一个是boxaGetBox,一个是MeanTextConf

  • boxaGetBox:用于提取矩形数组中的某个矩形。其它参数一眼就看出来了。第三个参数可以选择L_CLONE或者L_COPYL_CLONE是软拷贝,只增加引用数目。L_COPY是硬拷贝,把数据都复制一遍。
  • MeanTextConf:用于返回OCR的平均信心。信心的值最低为0,最高为100。

最后,看一下运行结果:

Result iterator example

这个例子是说,对于OCR的结果,我们可以一个词一个词地遍历了来看。可以看到每一个词的OCR结果、置信度以及在原图中的位置。

#include <tesseract/baseapi.h>
#include <leptonica/allheaders.h>
 
int main()
{
    Pix *image = pixRead("D:\\open_source\\tesseract-3.04.01\\tesseract\\testing\\phototest.tif");
    tesseract::TessBaseAPI *api = new tesseract::TessBaseAPI();
    api->Init(NULL, "eng");
    api->SetImage(image);
    api->Recognize(0);
    tesseract::ResultIterator* ri = api->GetIterator();
    tesseract::PageIteratorLevel level = tesseract::RIL_WORD;
    if (ri != 0)
    {
        do
        {
            const char* word = ri->GetUTF8Text(level);
            float conf = ri->Confidence(level);
            int x1, y1, x2, y2;
            ri->BoundingBox(level, &x1, &y1, &x2, &y2);
            printf("word: '%s';  \tconf: %.2f; BoundingBox: %d,%d,%d,%d;\n",
                word, conf, x1, y1, x2, y2);
            delete[] word;
        } while (ri->Next(level));
    }
}

最为关键的就是下面这两行

    tesseract::ResultIterator* ri = api->GetIterator();
    tesseract::PageIteratorLevel level = tesseract::RIL_WORD;

第一句按照阅读顺序来获取一个OCR结果的迭代器。第二句设置迭代的单位。可用的迭代单位有:

enum PageIteratorLevel {
  RIL_BLOCK,     // Block of text/image/separator line.
  RIL_PARA,      // Paragraph within a block.
  RIL_TEXTLINE,  // Line within a paragraph.
  RIL_WORD,      // Word within a textline.
  RIL_SYMBOL     // Symbol/character within a word.
};

运行下来的部分结果如下:

Orientation and script detection (OSD) example

这个例子讲了如何进行页面的方向检测和文字的方向检测。不知道大家是否与我有同样的疑问,就是页面的方向如果检测出来了,那文字的方向还用检测吗?文字不就是正着的了吗?可是人家说的文字方向检测根本不是说的这个,而是说阅读的方向性。比如,我们知道英文的一行肯定是横着排的,阅读方向是从左到右的。读完上面一行再读下面一行。然而对于古体中文来说,文字是竖着写的,阅读方向是从上到下的,行与行之间呢,是从右往左读的。这里文字的方向检测检测的是这个。

先看代码:

#include <tesseract/baseapi.h>
#include <leptonica/allheaders.h>
 
int main()
{
    const char* inputfile = "D:\\open_source\\tesseract-3.04.01\\tesseract\\testing\\phototest.tif";
    tesseract::Orientation orientation;
    tesseract::WritingDirection direction;
    tesseract::TextlineOrder order;
    float deskew_angle;
 
    PIX *image = pixRead(inputfile);
    tesseract::TessBaseAPI *api = new tesseract::TessBaseAPI();
    api->Init(NULL, "eng");
    api->SetPageSegMode(tesseract::PSM_AUTO_OSD);
    api->SetImage(image);
    api->Recognize(0);
 
    tesseract::PageIterator* it = api->AnalyseLayout();
    it->Orientation(&orientation, &direction, &order, &deskew_angle);
    printf("Orientation: %d;\nWritingDirection: %d\nTextlineOrder: %d\n" \
        "Deskew angle: %.4f\n",
        orientation, direction, order, deskew_angle);
 
}

只看之前没有看到过的。

    api->SetPageSegMode(tesseract::PSM_AUTO_OSD);

这句是重点。它设置了页面分割模式。页面分割有如下的模式可供选择:

enum PageSegMode {
  PSM_OSD_ONLY,       ///< Orientation and script detection only.
  PSM_AUTO_OSD,       ///< Automatic page segmentation with orientation and
                      ///< script detection. (OSD)
  PSM_AUTO_ONLY,      ///< Automatic page segmentation, but no OSD, or OCR.
  PSM_AUTO,           ///< Fully automatic page segmentation, but no OSD.
  PSM_SINGLE_COLUMN,  ///< Assume a single column of text of variable sizes.
  PSM_SINGLE_BLOCK_VERT_TEXT,  ///< Assume a single uniform block of vertically
                               ///< aligned text.
  PSM_SINGLE_BLOCK,   ///< Assume a single uniform block of text. (Default.)
  PSM_SINGLE_LINE,    ///< Treat the image as a single text line.
  PSM_SINGLE_WORD,    ///< Treat the image as a single word.
  PSM_CIRCLE_WORD,    ///< Treat the image as a single word in a circle.
  PSM_SINGLE_CHAR,    ///< Treat the image as a single character.
  PSM_SPARSE_TEXT,    ///< Find as much text as possible in no particular order.
  PSM_SPARSE_TEXT_OSD,  ///< Sparse text with orientation and script det.
  PSM_RAW_LINE,       ///< Treat the image as a single text line, bypassing
                      ///< hacks that are Tesseract-specific.
 
  PSM_COUNT           ///< Number of enum entries.
};

多提一句,如需使用OSD功能,则需要下载osd.traineddata

    api->Recognize(0);

这一句当然是用来根据之前的设定来进行识别的。

    tesseract::PageIterator* it = api->AnalyseLayout();

这一句根据之前SetPageSegMode的设定来运行页面的布局分析。这句话其实也可以在Recognize前面进行。

    it->Orientation(&orientation, &direction, &order, &deskew_angle);

这个函数用来获取页面和文字的方向。其签名如下:

void Orientation(tesseract::Orientation *orientation,
                   tesseract::WritingDirection *writing_direction,
                   tesseract::TextlineOrder *textline_order,
                   float *deskew_angle) const;

其中,

  • tesseract::Orientation指的是页面的方向。
  • tesseract::WritingDirection指的是书写方向。(比如刚刚说的英文是从左到右,中文是从上到下)
  • tesseract::TextlineOrder指的是一行一行的方向。(比如刚刚说的英文是从上往下阅读,中文是从右往左阅读)
  • ** deskew_angle**是指倾斜角度。因为排出来的图片也不可能完全是正着的。这里可以计算出偏转的角度。

运行的结果如下:

Example of iterator over the classifier choices for a single symbol

这个例子可以帮助我们学习如何找到一个识别对象的其它候选结果。

#include <tesseract/baseapi.h>
#include <leptonica/allheaders.h>
 
int main()
{
    Pix *image = pixRead("D:\\open_source\\tesseract-3.04.01\\tesseract\\testing\\phototest.tif");
    tesseract::TessBaseAPI *api = new tesseract::TessBaseAPI();
    api->Init(NULL, "eng");
    api->SetImage(image);
    api->SetVariable("save_blob_choices", "T");
    api->SetRectangle(37, 228, 548, 31);
    api->Recognize(NULL);
 
    tesseract::ResultIterator* ri = api->GetIterator();
    tesseract::PageIteratorLevel level = tesseract::RIL_SYMBOL;
    if (ri != 0)
    {
        do
        {
            const char* symbol = ri->GetUTF8Text(level);
            float conf = ri->Confidence(level);
            if (symbol != 0)
            {
                printf("symbol %s, conf: %f", symbol, conf);
                bool indent = false;
                tesseract::ChoiceIterator ci(*ri);
                do
                {
                    if (indent) printf("\t\t ");
                    printf("\t- ");
                    const char* choice = ci.GetUTF8Text();
                    printf("%s conf: %f\n", choice, ci.Confidence());
                    indent = true;
                } while (ci.Next());
            }
            printf("------------------------------------------\n");
            delete[] symbol;
        } while (ri->Next(level));
    }
}

在编译的时候出现了写问题。问题在于:

                tesseract::ChoiceIterator ci(*ri);

这个类在dll中没有。没有的原因是,这个类根本就没有被导出来!如果需要导出这个类,那么就需要在tesseract的源代码中修改一下,然后再重新编译。

修改方法为,在ltrresultiterator.h头文件中,将:

class ChoiceIterator {

修改为:

class TESS_API ChoiceIterator {

然后要记得重新编译哦!并且将生成的动态库覆盖原来的动态库。目前我们还不太熟悉的API如下:

    api->SetVariable("save_blob_choices", "T");

这个函数的作用是设置内部的参数。(不过话说我怎么知道内部有哪些参数,这些参数又有什么意义啊!)设置"save_blob_choices"的目的是将候选项全部保存下来。

                tesseract::ChoiceIterator ci(*ri);

这是一个迭代器,通过这个迭代器,可以将每一个候选的结果都打印出来。

部分结果如下:

好了,就写到这儿吧。可以看出,Tesseract的应用是非常灵活的。下面一段时间,我希望自己可以慢慢了解Tesseract-OCR的算法原理。这不是一件容易的事。这个系列可能要暂停一段时间了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,979评论 4 60
  • 转载自:https://github.com/Tim9Liu9/TimLiu-iOS 目录 UI下拉刷新模糊效果A...
    袁俊亮技术博客阅读 11,902评论 9 105
  • 设想中的坚持写作,并没有发生。转眼过去了那么久,似乎别无所得。调动不起情绪,生硬地拉扯几句文字。我也不知道自己在写...
    远古礼仪阅读 185评论 0 0
  • 在天晴了的时候 在天晴了的时候 墙角的风筝张望着 酵了一冬的情绪 趁风而逃 在蔚蓝映衬里四下招摇 在天晴了的时候 ...
    大侠小花卷阅读 375评论 0 2
  • #转型与蜕变30天自由写作第13篇# 看到今天的命题——生命中的最后一个月,心情立刻沉重下来了。这让我想起了看过的...
    旅途中的文森特阅读 789评论 1 5