1. 为什么OpenCV自己搞不定中文?
很多朋友刚接触OpenCV,想给图片加个中文水印或者做个带中文的标注,结果一上手就懵了。你满怀信心地写下 cv2.putText(img, ‘你好世界’, (50, 50), font, 1, (255,0,0), 2),运行后看到的却是一堆乱码或者空白。这感觉就像你买了一台顶级咖啡机,结果发现它不支持你最喜欢的咖啡豆,瞬间就泄了气。
OpenCV本身是一个功能强大的计算机视觉库,但它在设计之初,文本渲染这块主要依赖的是操作系统底层的基础字体引擎,比如FreeType。问题就出在这里:OpenCV默认编译时,通常没有启用对FreeType库的完整支持,尤其是对像中文这样需要复杂字符集(Unicode)的渲染。你可以把OpenCV想象成一个“国际大厨”,它精通做各种西餐(处理英文、数字符号),但后厨里没有准备中餐的专用厨具和食材(比如中文点阵字库或完整的Unicode字体支持),所以当你点一道“宫保鸡丁”时,它就无能为力了。
更具体地说,cv::putText 或 cv2.putText 函数内部使用的是非常简单的位图字体,通常只包含基本的ASCII字符集(大概128个字符)。它处理英文“Hello”没问题,但一旦遇到中文“你好”,它根本不知道去哪里找这些字的“样子”(字形)。这就引出了我们解决这个问题的核心思路:要么我们自己告诉OpenCV每个中文字长什么样(C++点阵字库方案),要么我们借用一个已经知道中文长什么样的“外援”来帮忙画(Python PIL方案)。
我刚开始做项目时也在这里卡了很久,试过各种野路子,最后发现这两种方法是最靠谱、最通用的。下面我就把这两种方法的里里外外、优缺点以及实际踩过的坑,都给你讲明白。
2. 方案一:C++的硬核之路——手动操作点阵字库
如果你主要用C++做开发,或者你的项目对性能有极致要求,不希望引入额外的庞大库,那么直接操作点阵字库是最“原生”、最可控的方案。这有点像自己动手做模具,虽然前期麻烦点,但做出来的东西严丝合缝,效率极高。
2.1 先搞懂汉字在计算机里是怎么“画”出来的
要自己画中文,首先得知道汉字的“蓝图”存在哪里。这里就涉及到两个老概念:区位码和机内码。别怕,我用大白话解释一下。
区位码可以理解为一个巨大的汉字表格(国标GB2312)。这个表格有94行(区)和94列(位)。每个汉字在这个表格里都有一个唯一的“座位号”,比如“啊”字在16区01位,它的区位码就是1601。这个表格涵盖了大部分常用汉字。
机内码则是汉字在计算机内部实际存储和使用的编码。因为区位码的数字范围(1-94)和基本的ASCII码(0-127)有重叠,为了区分开,计算机会在区位码上加上一个固定的偏移量(+0xA0)。具体公式是:
- 高位字节(区码) = 区码 + 0xA0
- 低位字节(位码) = 位码 + 0xA0 这样,“啊”字的机内码就变成了
0xB0A1(十六进制)。我们读写文件时,遇到的就是这种两个字节连在一起的机内码。
有了机内码,我们就能在点阵字库文件里找到这个字。字库文件就像一本“像素画册”,里面按顺序存放了每个汉字的“点阵图”。一个16x16的汉字,需要256个点来表示,相当于32个字节(因为1字节=8位)。字库里就按顺序存着这32个字节的数据,每个bit是1就表示这个点要涂黑,是0就表示是空白。
2.2 手把手实现C++中文渲染
理论懂了,我们来看代码怎么实现。核心思路是:读取一个包含中文的文本文件(以GB2312编码保存),逐个字符判断。如果是英文字符(ASCII码),就从ASCII点阵字库里取数据;如果是中文字符(两个字节的机内码),就换算回区位码,然后去中文点阵字库里找到对应的那32个字节数据,最后在图片的对应位置,用cv::circle或直接设置像素点的方式把这些“点”画出来。
这里我贴一个关键函数 paint_chinese 的简化版逻辑,并加上详细注释:
void paint_chinese(cv::Mat &image, int x_offset, int y_offset, unsigned long offset) {
FILE* font_file = fopen("HZKf2424.hz", "rb"); // 打开24x24点阵字库文件
char buffer[72]; // 24x24

987

被折叠的 条评论
为什么被折叠?



