如何在C++/OpenCV中正确绘制波斯语(Farsi)文本?
在OpenCV中绘制波斯语文本的解决方案
问题根源
cv::putText仅支持简单拉丁字符,不处理从右到左(RTL)的复杂脚本(如波斯语),也不支持字形连写,因此显示乱码。cv::addText属于OpenCV Contrib模块,VC包管理器默认安装的OpenCV不含该模块,因此抛出未实现异常。- 单纯用FreeType实现时,未处理波斯语的RTL排版规则和字形上下文变体(独立/起始/中间/结尾形态),导致字符拆分、顺序反转。
核心解决方案:HarfBuzz + FreeType + OpenCV
HarfBuzz是专业的文本整形库,可处理RTL脚本的排版逻辑、字形连写和上下文变体,配合FreeType渲染字形,最终将结果绘制到OpenCV图像中。
步骤1:安装依赖库
通过vcpkg安装所需库:
vcpkg install freetype harfbuzz opencv4
步骤2:实现代码示例
#include <opencv2/opencv.hpp> #include <ft2build.h> #include FT_FREETYPE_H #include <hb.h> #include <hb-ft.h> // 绘制RTL文本到OpenCV图像 void drawFarsiText(cv::Mat& img, const std::string& text, const std::string& fontPath, cv::Point bottomRight, cv::Scalar color, int fontSize) { // 初始化FreeType FT_Library ftLib; FT_Face ftFace; if (FT_Init_FreeType(&ftLib) != 0 || FT_New_Face(ftLib, fontPath.c_str(), 0, &ftFace) != 0) { return; } FT_Set_Pixel_Sizes(ftFace, 0, fontSize); // 初始化HarfBuzz hb_font_t* hbFont = hb_ft_font_create(ftFace, nullptr); hb_buffer_t* hbBuffer = hb_buffer_create(); hb_buffer_add_utf8(hbBuffer, text.c_str(), -1, 0, -1); // 设置文本方向:RTL,波斯语脚本 hb_buffer_set_direction(hbBuffer, HB_DIRECTION_RTL); hb_buffer_set_script(hbBuffer, HB_SCRIPT_ARABIC); hb_buffer_set_language(hbBuffer, hb_language_from_string("fa", -1)); // 文本整形:生成排版后的字形信息 hb_shape(hbFont, hbBuffer, nullptr, 0); unsigned int glyphCount; hb_glyph_info_t* glyphInfos = hb_buffer_get_glyph_infos(hbBuffer, &glyphCount); hb_glyph_position_t* glyphPositions = hb_buffer_get_glyph_positions(hbBuffer, &glyphCount); // 计算文本总宽度(用于确定起始X坐标) int totalWidth = 0; for (unsigned int i = 0; i < glyphCount; ++i) { totalWidth += glyphPositions[i].x_advance >> 6; // HarfBuzz的单位是1/64像素 } int x = bottomRight.x - totalWidth; int y = bottomRight.y; // 逐个渲染字形到图像 for (unsigned int i = 0; i < glyphCount; ++i) { // 加载字形 if (FT_Load_Glyph(ftFace, glyphInfos[i].codepoint, FT_LOAD_DEFAULT) != 0) { continue; } FT_Render_Glyph(ftFace->glyph, FT_RENDER_MODE_NORMAL); FT_Bitmap& bitmap = ftFace->glyph->bitmap; int glyphX = x + (glyphPositions[i].x_offset >> 6); int glyphY = y - (glyphPositions[i].y_offset >> 6) - bitmap.rows; // 确保字形在图像范围内 if (glyphX < 0 || glyphY < 0 || glyphX + bitmap.width > img.cols || glyphY + bitmap.rows > img.rows) { x += glyphPositions[i].x_advance >> 6; continue; } // 将灰度字形转换为彩色并绘制到图像 for (int row = 0; row < bitmap.rows; ++row) { for (int col = 0; col < bitmap.width; ++col) { uchar alpha = bitmap.buffer[row * bitmap.pitch + col]; if (alpha == 0) continue; float alphaNorm = alpha / 255.0f; cv::Vec3b& pixel = img.at<cv::Vec3b>(glyphY + row, glyphX + col); pixel[0] = cv::saturate_cast<uchar>(pixel[0] * (1 - alphaNorm) + color[0] * alphaNorm); pixel[1] = cv::saturate_cast<uchar>(pixel[1] * (1 - alphaNorm) + color[1] * alphaNorm); pixel[2] = cv::saturate_cast<uchar>(pixel[2] * (1 - alphaNorm) + color[2] * alphaNorm); } } // 更新X坐标(RTL方向,向左移动) x += glyphPositions[i].x_advance >> 6; } // 释放资源 hb_buffer_destroy(hbBuffer); hb_font_destroy(hbFont); FT_Done_Face(ftFace); FT_Done_FreeType(ftLib); } int main() { cv::Mat img = cv::Mat(500, 800, CV_8UC3, cv::Scalar(255, 255, 255)); std::string farsiText = "سلام دنیا"; // 波斯语"你好世界" std::string fontPath = "path/to/your/farsi/font.ttf"; // 替换为波斯语字体路径,比如Vazirmatn.ttf drawFarsiText(img, farsiText, fontPath, cv::Point(700, 250), cv::Scalar(0, 0, 0), 48); cv::imshow("Farsi Text", img); cv::waitKey(0); return 0; }
代码关键说明
- HarfBuzz文本整形:设置方向为RTL、脚本为阿拉伯语(波斯语基于阿拉伯脚本),生成正确的字形序列和位置偏移,解决字符顺序和连写问题。
- 坐标计算:先计算文本总宽度,从指定的右下角向左偏移起始X坐标,符合RTL排版逻辑。
- 字形渲染:FreeType渲染灰度字形后,通过alpha混合将彩色文本绘制到OpenCV图像中。
替代方案
- Pango + Cairo:Pango处理文本排版,Cairo渲染到图像后转换为
cv::Mat,适合需要更复杂排版(如多行、对齐)的场景。 - Qt绘图:使用
QPainter在QPixmap上绘制波斯语文本,再将QPixmap转换为cv::Mat,适合Qt项目集成。
内容的提问来源于stack exchange,提问作者Babak.Abad
相关产品推荐
相关产品推荐

