简介:面向在 Windows 64 位环境下使用 Qt 开发 OCR 功能的开发者,这份 Qt+Tesseract 编译版本省去了自行处理依赖与工具链的繁琐过程,可直接用于桌面端文字识别模块的集成与验证。压缩包共 916 个文件,约 39.32MB,以 546 个 C/C++ 头文件、72 个动态库、50 个导入库和 71 个 CMake 配置为主,同时包含 pkg-config 的 pc 文件、命令行可执行程序及训练与示例相关数据,覆盖编译产物、链接配置与运行组件。已有 1126 人学习下载,说明其在 Qt 集成 Tesseract 的实践场景中有一定参考价值。对需要快速搭建识别环境、排查库依赖与 CMake 查找路径的开发者而言,可借助其中的头文件、DLL/LIB 与配置脚本完成工程接入,并参考随包工具验证识别流程,减少从源码编译到 Windows 适配的试错成本。
1. 一次“看起来很完整”的 64 位 Qt OCR 环境
有个开发者拿到一套标注着 qt+tesseract 的 windows 64 位编译版本,按说明把 include 和 lib 配好,qmake 通过了,nmake 也通过了,双击 exe 直接闪退,连窗口都没出来;换个工程把 exe 拉起来,识别结果又是清一色的空字符串。这不是代码写错了,大概率是这套 Qt OCR 环境的三个维度没对齐:Qt 走的是哪条编译器线、Tesseract 的库是 32 位还是 64 位、tessdata 到底被谁找到了。桌面端做票据识别、证件扫描、批量图片转文字,这套组合太常见了——一个 exe 加几个 dll,完全不依赖网络接口,离线就能跑。装得顺,半小时出第一行识别结果;装不顺,下午全耗在 DLL 和路径上。下面按我自己的搭建顺序走:先核对版本,再装 Qt 套件,接着把随包的 Tesseract 接进工程,最后是几条血泪踩坑记录和发布前的验证动作。
2. 版本对齐:Qt 套件、MSVC 运行时与 Tesseract 主版本
2.1 三个必须先对齐的维度
第一维是 Qt 大版本线。Qt 5.15 是 LTS,大量存量工程还锁在上面;新装的环境默认给 Qt 6.x。这两个线对 C++ 标准的要求不一样,Qt 6 要 C++17,而 Tesseract 5.x 本身也要求 C++17 编译,两边都在抬标准。工程里如果混着老的 qmake 配置,先确认CONFIG += c++17有没有写,不写的话 Qt 6 那侧直接编不过。
第二维是编译器 ABI,这一维最要命。Windows 下 Qt 官方只出两套:MSVC 和 MinGW。而 Tesseract 在 Windows 上现成的预编译产物绝大多数是 MSVC 编出来的,导出的是 COFF 格式的 .lib 加 MSVC 的符号修饰。Qt 套件如果勾了 MinGW,链接阶段会撞上一大片 undefined reference——不是少链接了库,是符号名根本对不上,连 mangling 规则都不是一套。所以这套 qt+tesseract 的 windows 64 位编译版本,配 Qt 的 MSVC 2019 64-bit 套件是唯一省事的路子。非要用 MinGW,就得自己从源码把 Tesseract 和 Leptonica 重编一遍,那是另一个工作量级的事,不值得为一个已经编好的包去付。
第三维是 Tesseract 主版本和训练数据。4.x 和 5.x 的 TessBaseAPI 基本保持兼容,Init/SetImage/GetUTF8Text/End这套调用几乎不用改,但语言包有代际差异:4.x 之前的老版 traineddata 在 4.x、5.x 里读不进去,初始化会直接失败。手里这套到底是哪个主版本,别信目录名,去看头文件里的版本宏,或者看 lib 文件名里的数字,实在不行用编辑器打开baseapi.h搜一下版本号定义。
2.2 用命令确认手里这套东西真是 64 位
目录名写着 x64 不代表里面就是 x64,混包的情况我见过不止一次。验证要落到命令上,别靠眼睛。
:: 确认 Qt 套件本身 qmake -query QT_INSTALL_PREFIX qmake -query QMAKE_XSPEC qmake -v :: 确认 Tesseract 的库是 x64(在 VS 开发者命令提示符里执行) dumpbin /headers D:\ocr\qt-tesseract-x64\lib\tesseract.lib | findstr /i machineqmake -query QMAKE_XSPEC打出的是 mkspec 名字,win32-msvc说明走 MSVC 线,win32-g++说明走 MinGW 线,一眼就能对上前面说的第二维。dumpbin /headers里的 machine 一栏会写x64或x86,写x86就是 32 位,这种库混进 64 位工程只会得到一串链接错误。注意 dumpbin 依赖 VS 开发者命令提示符提供的环境变量,在普通 cmd 窗口里敲会提示找不到命令,这一点卡过不少人。
顺手再查一遍运行时依赖,把结果记下来,第五章排查闪退时要用:
dumpbin /dependents D:\ocr\qt-tesseract-x64\bin\tesseract.dll输出里会出现VCRUNTIME140.dll、VCRUNTIME140_1.dll、MSVCP140.dll这类名字。它们就是这套库的运行时底座,目标机器上缺了任何一个,程序都起不来。
2.3 解压位置:先定目录再动手
结论先给:路径别带空格,别带中文,别放 OneDrive 这类同步盘。三种情况各有各的坑——空格会让 qmake 的LIBS和INCLUDEPATH需要额外转义,漏一个反斜杠就报找不到文件;中文路径在部分第三方库的窄字符接口上会截断;同步盘会在编译期间锁住 dll,导致链接器写不进输出文件。我一般固定用这样一个结构:
D:\ocr\ qt-tesseract-x64\ <- 随包的编译版本 include\ lib\ bin\ tessdata\ qtocr\ <- 自己的 Qt 工程把 include、lib、bin、tessdata 四块分清楚,后面写 .pro 的时候路径能一眼看明白,出问题也好定位是缺头文件还是缺库。tessdata 单独放在包里,不要塞进 bin 目录,打包的时候容易漏。
3. 装 Qt 套件:安装器勾什么,编译器从哪来
3.1 在线安装器的勾选清单
Qt 官方在线安装器只负责 Qt 库和 Qt Creator,不带编译器。这是新手最容易踩的一步——Qt 装完,打开 Qt Creator 看 Kits 那一页,MSVC 套件前面顶着一个红色感叹号,因为机器上压根没有 MSVC。
勾选顺序我一般这么走:先在 Qt 版本树里选定 5.15.x 或 6.x 一条线,展开后只勾MSVC 2019 64-bit这一项,MinGW、ARM、32 位套件全部不勾,减少后面选错套件的概率;Qt Creator 默认勾着,留着;Sources 和 Qt Debug Information Files 除非要单步调试 Qt 源码,否则不用勾,占空间。安装路径也按第 2 章的规矩,D:\Qt这种短路径最省心。
编译器单独装。Visual Studio 2019 或 2022 的 Build Tools 都行,工作负载勾“使用 C++ 的桌面开发”,右侧组件里确认MSVC v142 x64/x86 生成工具和Windows 10 SDK都在。装完必须重启 Qt Creator,让它重新扫描一遍工具链,否则红色感叹号不会消失。
提示:Qt Creator 的“工具 → 选项 → Kits”里,如果某个套件的编译器一栏显示
<无>,别急着建工程,先在“编译器”页确认自动检测有没有把 MSVC 的 cl.exe 收进来。这一页没配对,后面所有编译都是白费。
3.2 PATH 与命令行构建脚本
GUI 里点构建按钮不需要配 PATH,Qt Creator 会自己设好环境。真正需要 PATH 的是命令行构建,或者你想在外部终端里直接跑 qmake。我习惯写一个 bat 固定住环境,比每次手敲一堆 set 靠谱:
@echo off :: 先引入 MSVC 的编译环境(路径按自己机器改) call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat" :: Qt 的 bin 放最前面,避免拿到系统里其它版本的 Qt set PATH=D:\Qt\5.15.2\msvc2019_64\bin;%PATH% :: Tesseract 的 bin 用于运行时找 dll,编译期找库还是靠 .pro 里的 LIBS set PATH=D:\ocr\qt-tesseract-x64\bin;%PATH% cd /d %~dp0 qmake ..\qtocr\qtocr.pro nmake releasevcvars64.bat是整段脚本的地基,它把 cl.exe、link.exe、dumpbin 和一堆 SDK 路径都设进来,64 位版本对应的是vcvars64而不是vcvars32。PATH 的顺序有讲究:Qt 的 bin 必须排在前面,否则机器上如果还装着别的 Qt 版本,qmake 可能拿到错的那个,编出来的东西链接到一半报版本不匹配。%~dp0是脚本自身所在目录,用相对路径切工程目录,换机器不用改脚本。最后提醒一句,PATH 里加 Tesseract 的 bin 只解决运行期动态库查找,编译期找头文件和 .lib 依然要在工程文件里显式写,这两件事别混。
3.3 十五行代码验证套件能不能用
装完别直接上 OCR 代码,先用一个最小工程确认套件本身是通的。这一步过了,后面接 Tesseract 出问题才能确定是 Tesseract 的锅。
#include <QApplication> #include <QLabel> #include <QSysInfo> // 用来打印构建 ABI int main(int argc, char *argv[]) { QApplication app(argc, argv); // 把 Qt 版本和 ABI 打在窗口上,一眼看出跑的是不是 64 位 MSVC 套件 const QString info = QStringLiteral("Qt %1\n%2") .arg(QString::fromLatin1(QT_VERSION_STR)) .arg(QSysInfo::buildAbi()); QLabel label(info); label.show(); return app.exec(); }对应的工程文件:
QT += core gui widgets CONFIG += c++17 TEMPLATE = app TARGET = qtprobe SOURCES += main.cppQSysInfo::buildAbi()打出来的字符串里带llp64,这是 Windows 64 位的标志,看到它就说明套件选对了;如果是mingw或者 32 位标识,回头改 Kits 重来。窗口能弹出来、能正常关闭,说明 Qt 的 Core、Gui、Widgets 三块都链接正常,DLL 部署也没问题。这一步不过关,别往下走,后面所有报错都会被这个未解决的问题盖住,变成玄学。
4. 接进 Qt 工程:头文件、库文件和 tessdata 三件套
4.1 .pro 工程里的写法
qmake 工程接第三方库就三件事:INCLUDEPATH 给头文件目录、LIBS 给库目录和库名、运行期保证 dll 找得到。
QT += core gui widgets CONFIG += c++17 # 随包目录,按实际位置改 TESS_ROOT = $$PWD/../qt-tesseract-x64 INCLUDEPATH += $$TESS_ROOT/include DEPENDPATH += $$TESS_ROOT/include # MSVC 下 -ltesseract 会去找 tesseract.lib; # MinGW 下会去找 libtesseract.a,命名规则不一样 LIBS += -L$$TESS_ROOT/lib -ltesseract -lleptonica # 可选:构建后把 dll 自动拷到输出目录,省得每次配 PATH win32 { QMAKE_POST_LINK += $$quote(cmd /c copy /y $$shell_path($$TESS_ROOT/bin/*.dll) $$shell_path($$OUT_PWD/release/)) }INCLUDEPATH要指到 include 目录本身,不是它的上一级,否则#include <tesseract/baseapi.h>会找不到。LIBS里-L给目录、-l给库名,库名不带前缀和后缀,链接器自己补——这正是为什么第 2 章要死磕 MSVC:同一条-ltesseract,MSVC 找tesseract.lib,MinGW 找libtesseract.a,两套命名规则不通。QMAKE_POST_LINK那段是便利贴性质的,只处理了 release 目录,Debug 构建要自己再补一条,或者干脆统一用 release 调试。
4.2 CMakeLists 里的写法
Qt 6 的工程越来越多走 CMake,写法对应如下:
cmake_minimum_required(VERSION 3.16) project(qtocr LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) # 让 find_package 找到 Qt,路径写成自己的,用正斜杠 set(CMAKE_PREFIX_PATH "D:/Qt/5.15.2/msvc2019_64") find_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED) set(TESS_ROOT "${CMAKE_SOURCE_DIR}/../qt-tesseract-x64") add_executable(qtocr main.cpp) target_include_directories(qtocr PRIVATE "${TESS_ROOT}/include") target_link_libraries(qtocr PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets "${TESS_ROOT}/lib/tesseract.lib" "${TESS_ROOT}/lib/leptonica.lib" )find_package(Tesseract)这种写法能用的前提是随包带了TesseractConfig.cmake,大多数现成的编译版本不提供这个文件,硬写反而报一串找不到版本的错误。所以我一般直接在target_link_libraries里写 .lib 的绝对路径,不优雅,但一次就对。CMAKE_PREFIX_PATH一定要改成自己机器上的 Qt 路径,并且用正斜杠,反斜杠在 CMake 里是转义符。
4.3 tessdata 的三种给法
tessdata 是语言包目录,里面是一个个.traineddata文件,eng对应eng.traineddata,中文简体是chi_sim。找不到它,Init必失败。
方式一,exe 同级放一个 tessdata 目录。这是发布时最稳的做法,拷贝程序的时候整个目录一起走,不依赖任何环境变量。方式二,Init的第一个参数直接传绝对路径,优先级最高,开发期调试语言包切换最方便。方式三,设环境变量TESSDATA_PREFIX。这个变量的语义在版本演进中调整过,指到 tessdata 本身还是它的上一级,不同版本行为不一样,别凭记忆写,设完用Init的返回值判断就行。
实际优先级我总结成:Init参数 >TESSDATA_PREFIX> exe 同级目录 / 当前工作目录。开发期用方式二,发布切成方式一,环境变量只在临时排查时用,别写进安装文档,否则换台机器就失灵。
4.4 一段能出结果的识别代码
#include <tesseract/baseapi.h> #include <QImage> #include <QDebug> QString ocrFromImage(const QImage &src, const QString &lang) { // 1) 统一转灰度,避开 RGB/BGR 通道顺序导致的识别偏差 QImage img = src.convertToFormat(QImage::Format_Grayscale8); if (img.isNull()) return QString(); tesseract::TessBaseAPI api; // 2) Init 返回 0 才算初始化成功,非 0 基本都是 tessdata 没找到 if (api.Init(nullptr, lang.toUtf8().constData()) != 0) { qWarning() << "Init failed, lang =" << lang; return QString(); } // 3) 灰度图每像素 1 字节;行跨度必须用 bytesPerLine,不能用 width api.SetImage(img.constBits(), img.width(), img.height(), 1, img.bytesPerLine()); char *text = api.GetUTF8Text(); // 这块内存由调用方释放 const QString result = QString::fromUtf8(text ? text : ""); delete[] text; api.End(); return result; }三个参数最容易写错。bytes_per_pixel传 1 表示灰度单通道,如果你传的是 RGB888 的图,这里得写 3,通道顺序还得跟 Tesseract 的期望对上,索性统一灰度最省事。bytes_per_line必须用QImage::bytesPerLine(),因为 QImage 每行末尾有对齐填充,直接传width会让图像整体错位,识别结果就变成一堆乱码。GetUTF8Text返回的char*是new[]出来的,必须delete[],忘了就是内存泄漏,批量处理几千张图会很明显。
还有一个隐形约束:SetImage不拷贝像素数据,只存指针。也就是说img在GetUTF8Text返回之前不能析构。上面这段在函数内是安全的,但如果把QImage放在临时对象里传进来,就容易翻车。
多线程批量处理时记住一条:TessBaseAPI实例不是线程安全的,一个实例只能被一个线程使用。要么每个线程各建一个实例,要么在识别那段加互斥锁,前者性能好得多,代价是每个实例各自占一份内存。
5. 避坑排查:五类装完就翻车的现场
5.1 现象:双击 exe 直接闪退,事件查看器里是 0xc0000135
原因:系统找不到依赖的动态库。开发机上能跑是因为 Qt Creator 启动进程时继承了调试环境的 PATH,一旦脱离 IDE 双击,PATH 里那点配置全部失效。Tesseract 的 dll 和 Leptonica 的 dll 是最常漏的两个,因为 windeployqt 只管 Qt 自己的库。
解决:先用dumpbin /dependents release\qtocr.exe把直接依赖列出来,逐条核对输出目录里有没有对应文件,缺哪个补哪个。临时方案是把qt-tesseract-x64\bin下的 dll 全拷到 exe 同级;正式方案是写进构建脚本,每次构建后自动拷。
5.2 现象:Init 返回非 0,日志里刷 "Error opening data file"
原因:tessdata 路径没对上,或者语言名写错。常见三种情况——TESSDATA_PREFIX指到了 tessdata 的上一级、语言包文件其实是旧版本格式、语言名写成了chi_sim.traineddata而不是chi_sim。最后一种最常见,Init的第二个参数要的是语言名,不是文件名。
解决:开发期直接给绝对路径,api.Init("D:/ocr/qt-tesseract-x64/tessdata", "chi_sim+eng"),先排除路径因素;然后用QDir检查文件是否真实存在;最后核对语言包和 Tesseract 主版本是否同一代,旧格式文件在新引擎里读不进去。
5.3 现象:程序跑起来了,GetUTF8Text 返回空串或者全是乱码
原因:图像数据传错。要么通道顺序不对,把 BGR 当 RGB 塞进去了;要么bytes_per_line传了 width 导致错位;要么图片本身分辨率太低、噪点太多,二值化没做。还有一种情况是只加载了eng却去识别中文,输出自然是空的。
解决:统一走Format_Grayscale8加bytes_per_pixel = 1,把通道问题一次性绕开;识别前对图像做一次二值化或对比度拉伸;扫描件的 DPI 尽量提到 300 左右;语言按需组合成chi_sim+eng,中英混排的票据用这个组合效果最好。
5.4 现象:Debug 构建崩,Release 构建正常
原因:随包的编译版本一般只提供 Release 版的 .lib,工程切到 Debug 时去链接这个 Release 库,或者工程和库的运行时库设置不一致(一个/MD一个/MT),两套 CRT 混在一个进程里,堆分配和释放跨了边界。
解决:这套库统一按 Release 用,工程里CONFIG += release锁死;如果确实需要 Debug 调试,去确认有没有对应的 Debug 库,没有就别硬调,用 Release 加日志打印更实际。运行时库那一项在 VS 工程属性里叫“运行库”,qmake 工程里对应QMAKE_CXXFLAGS_RELEASE里的/MD,两边保持一致。
5.5 现象:本机一切正常,拷到另一台机器就起不来
原因:目标机器没装 VC 运行库,缺VCRUNTIME140.dll或MSVCP140.dll;或者只拷了 exe 忘了带 tessdata,或者带是带了但放在了错误的层级。这类问题在自己机器上永远复现不了,是最费时间的一种。
解决:发布前在一台没装过任何开发工具的干净机器上实测一遍;运行库选择装 vc_redist 或者改用静态链接;tessdata 目录固定放 exe 同级,打包时用脚本一起压缩,别依赖手工拷贝。
6. 装完怎么验证:三组自检和一份发布清单
装完不等于装对。我现在的习惯是走三组自检,每组都能独立暴露一类问题。
第一组查依赖。用dumpbin /dependents列出 exe 的直接依赖,再跑一遍 windeployqt 补齐 Qt 侧的库,最后确认 Tesseract 和 Leptonica 的 dll 都在输出目录:
dumpbin /dependents release\qtocr.exe windeployqt --release --no-translations release\qtocr.exe dir release\*.dll | findstr /i "tesseract leptonica"windeployqt只负责 Qt 自己的模块,Tesseract 和 Leptonica 它一概不管,这一点是打包时最常漏的。--no-translations是省掉用不上的 Qt 翻译文件,包体能小一截。
第二组查识别链路。准备一张内容已知的中英混排图片,跑一遍识别,把结果和预期做包含判断:
// 基线自检:结果里必须出现预设关键词,否则说明语言包没加载全 const QString expected = QStringLiteral("发票"); if (!result.contains(expected)) { qWarning() << "baseline check failed, head:" << result.left(80); }用中英混排的图是有讲究的,它能同时验证chi_sim和eng两个语言包都加载成功。只用英文图测,中文语言包坏掉你也发现不了。
第三组查跨机。把 release 目录整个拷到一台没装过 Qt、没装过 VS 的机器上双击。这一步过不了,前面两组做得再漂亮都没意义。
| 自检项 | 检查内容 | 不通过的典型症状 |
|---|---|---|
| 依赖 | dumpbin 输出逐条核对 | 双击闪退,0xc0000135 |
| 识别 | 中英混排图关键词命中 | 返回空串或乱码 |
| 跨机 | 干净机器双击运行 | 缺 vcruntime,启动失败 |
从那以后我每次配完这套环境,都会先把 exe 单独拷到一台干净机器上双击一次,再回来写业务代码。这个顺序反了,后面每改一行代码你都会怀疑是自己写错了。希望帮到你。
本文还有配套的精品资源,点击获取