本文展示如何用 C++ 结合 OpenCV 做图像预处理,再调用 Tesseract OCR 识别验证码。适用于希望在高性能后端或本地服务里集成 OCR 的场景。方案包含:
环境与依赖安装
图像预处理(灰度、二值化、形态学去噪、放大)
使用 Tesseract API 调用(设定白名单、PSM)
完整 C++ 示例与 CMake 构建
批量识别、并发与部署建议
评估方法与常见问题排查
1 环境准备
1.1 安装系统依赖(Ubuntu 示例)
sudo apt update
sudo apt install -y build-essential cmake pkg-config git
sudo apt install -y libopencv-dev
sudo apt install -y tesseract-ocr libtesseract-dev libleptonica-dev
macOS(Homebrew):
brew install opencv tesseract
确保 pkg-config --modversion opencv4、tesseract --version 可用。
1.2 创建工程目录
captcha_cpp_ocr/
├── CMakeLists.txt
├── src/
│ └── main.cpp
└── samples/
└── captcha.png
2 图像预处理思路(为什么要预处理)
Tesseract 对清晰、对比高、字符分离的图像效果最好。典型预处理步骤:
放缩(resize)——放大小字体提高识别率
灰度化(cv::cvtColor)
高斯模糊(可选,用于去噪)
自适应/固定阈值二值化(cv::adaptiveThreshold 或 cv::threshold)
形态学操作(开/闭运算)去除噪点或断连字符
倾斜校正(如果需要)
3 完整 C++ 示例代码(main.cpp)
把以下文件保存为 src/main.cpp。
// main.cpp
// 依赖: OpenCV, Tesseract (libtesseract)
// 编译: 使用 CMake(见项目 CMakeLists.txt)
include
include
include
include
include <opencv2/opencv.hpp>
include <tesseract/baseapi.h>
include <leptonica/allheaders.h>
namespace fs = std::filesystem;
// ---------- 图像预处理函数 ----------
cv::Mat preprocess(const cv::Mat &src) {
cv::Mat gray;
if (src.channels() == 3) {
cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
} else {
gray = src.clone();
}
// 放大 - 提高小字体识别率
const double scale = 2.0;
cv::Mat resized;
cv::resize(gray, resized, cv::Size(), scale, scale, cv::INTER_CUBIC);// 可选: 高斯去噪
cv::Mat blurred;
cv::GaussianBlur(resized, blurred, cv::Size(3, 3), 0);// 自适应阈值(二值化)
cv::Mat bw;
cv::adaptiveThreshold(blurred, bw, 255,cv::ADAPTIVE_THRESH_GAUSSIAN_C,cv::THRESH_BINARY_INV, 11, 2);// 形态学处理:先开运算去小噪点,再闭运算连接字符可能断裂边缘
cv::Mat morph;
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2,2));
cv::morphologyEx(bw, morph, cv::MORPH_OPEN, kernel);
cv::morphologyEx(morph, morph, cv::MORPH_CLOSE, kernel);// 可选: 再次去小区域噪点
std::vector<std::vector<cv::Point>> contours;
cv::findContours(morph.clone(), contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
for (auto &c : contours) {double area = cv::contourArea(c);if (area < 20) {cv::drawContours(morph, std::vector<std::vector<cv::Point>>{c}, -1, cv::Scalar(0), cv::FILLED);}
}// 最终返回二值图 - 转回白底黑字(Tesseract 对白底黑字通常表现更稳)
cv::Mat final;
cv::bitwise_not(morph, final); // invert to white background, black text
return final;
}
// ---------- 使用 Tesseract 识别单图 ----------
std::string recognizeWithTesseract(const cv::Mat &img, tesseract::TessBaseAPI &api) {
// 将 OpenCV Mat 转为 Pix*(Leptonica)
Pix *pix = pixCreate(img.cols, img.rows, 8);
for (int y = 0; y < img.rows; ++y) {
memcpy(pixGetData(pix) + y * pixGetWpl(pix) * 4, img.ptr(y), img.cols);
}
// 也可使用 pixReadMem or pixCreateHeader + pixSetData,以上为简化示例
api.SetImage(pix);
api.Recognize(0);
char *out = api.GetUTF8Text();
std::string result;
if (out) {result = std::string(out);delete[] out;
}
pixDestroy(&pix);
// 清理并返回
// 去掉换行与非字母数字字符
std::string cleaned;
for (char c : result) {if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))cleaned.push_back(c);
}
return cleaned;
}
int main(int argc, char** argv) {
if (argc < 2) {
std::cout << "Usage: " << argv[0] << " <image_or_folder>\n";
return 1;
}
std::string path = argv[1];
tesseract::TessBaseAPI api;
// 初始化 tesseract:NULL 表示使用默认 TESSDATA_PREFIX 环境变量或安装路径
if (api.Init(NULL, "eng", tesseract::OEM_LSTM_ONLY)) {std::cerr << "Could not initialize tesseract.\n";return -1;
}
// 白名单:只识别大写字母和数字(根据需求调整)
api.SetVariable("tessedit_char_whitelist", "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
// 可设置页面分割模式:单行/单词等,常用 PSM_SINGLE_LINE 或 PSM_SINGLE_BLOCK
api.SetPageSegMode(tesseract::PSM_SINGLE_LINE);// 如果是文件夹,批量识别
fs::path p(path);
std::vector<fs::path> images;
if (fs::is_directory(p)) {for (auto &entry : fs::directory_iterator(p)) {if (!entry.is_regular_file()) continue;std::string ext = entry.path().extension().string();std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp") {images.push_back(entry.path());}}
} else {images.push_back(p);
}for (auto &imgPath : images) {cv::Mat src = cv::imread(imgPath.string());if (src.empty()) {std::cerr << "Failed to open " << imgPath << "\n";continue;}cv::Mat proc = preprocess(src);// 调试可视化:// cv::imshow("proc", proc); cv::waitKey(0);std::string text = recognizeWithTesseract(proc, api);std::cout << imgPath.filename().string() << " -> " << text << std::endl;
}api.End();
return 0;
}
说明:上面把 Pix 创建做了示意(简化),在实际工程中建议使用 pixCreateHeader + pixSetData 或将 Mat 保存为临时 PNG 再 pixRead。若要避免内存复制,参考 Leptonica 文档使用 pixCreateHeader 并设置数据指针,注意行对齐(wpl)和内存生命周期。
4 CMakeLists.txt(构建脚本)
创建项目根目录 CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(captcha_ocr)
set(CMAKE_CXX_STANDARD 17)
find_package(PkgConfig REQUIRED)
pkg_check_modules(OPENCV4 REQUIRED opencv4)
find_package(Tesseract REQUIRED)
include_directories(${OPENCV4_INCLUDE_DIRS})
link_directories(${OPENCV4_LIBRARY_DIRS})
add_definitions(${OPENCV4_CFLAGS_OTHER})
add_executable(captcha_ocr src/main.cpp)
target_link_libraries(captcha_ocr ${OPENCV4_LIBRARIES} tesseract lept)
5 构建与运行
mkdir build && cd build
cmake ..
make -j4
单张测试
./captcha_ocr ../samples/captcha.png
批量测试
./captcha_ocr ../samples/