opencv在图片上添加中文汉字(c++以及python)

opencv在图片上添加中文汉字(c++以及python)_c++ opencv绘制中文 知乎-CSDN博客

环境:

  • ubuntu18.04 desktop
  • opencv 3.4.15

opencv是不支持中文的。

这里C++代码是采用替换原图的像素点来实现的,实现之前我们先了解一下汉字点阵字库。

python代码是采用将opencv格式转为PIL格式实现的。

不想了解汉字点阵字库的,可以直接看代码

一. 汉字点阵字库实现原理

1. 汉字编码

1.1 区位码

在国标 GD2312—80 中规定,所有的国标汉字及符号分配在一个 94 行、94 列的方阵中,方阵的每一行称为一个“区”,编号为 01 区到 94 区,每一列称为一个“位”,编号为 01 位到 94 位,方阵中的每一个汉字和符号所在的区号和位号组合在一起形成的四个阿拉伯数字就是它们的“区位码”。

区位码的前两位是它的区号,后两位是它的位号。

用区位码就可以唯一地确定一个汉字或符号,反过来说,任何一个汉字或符号也都对应着一个唯一的 区位码。

汉字“母”字的区位码是 3624,表明它在方阵的 36 区 24 位,问号“?”的区位码为 0331,则它在 03 区 3l 位。

1.2 机内码

汉字的机内码是指在计算机中表示一个汉字的编码。

机内码与区位码稍有区别

如上所述,汉字区位码的区码和位码的取值均在 1-94 之间,如直接用区位码作为机内码,就会与基本 ASCII 码混淆。

为了避免机内码与基本 ASCII 码的冲突,需要避开基本 ASCII 码中的控制码(00H~1FH),还需与基本 ASCII 码中的字符相区别。

为了实现这两点,可以先在区码和位码分别加上 20H,在此基础上再加 80H(此处“H”表示前两位数字为十六进制数)。经过这些处理,用机内码表示一个汉字需要占两个字节,分别称为高位字节和低位字节,这两位字节的机内码按如下规则表示:

高位字节 = 区码 + 20H + 80H(或区码 + A0H)

低位字节 = 位码 + 20H + 80H(或位码 + AOH)

由于汉字的区码与位码的取值范围的十六进制数均为 01H~5EH(即十进制的 01~94),所以汉字的高位字节与低位字节的取值范围则为 A1H~FEH(即十进制的 161~254)。 例如,汉字“啊”的区位码为 1601,区码和位码分别用十六进制表示即为 1001H,它 的机内码的高位字节为 B0H,低位字节为 A1H,机内码就是 B0A1H。

2. 点阵字库结构

2.1 点阵字库存储

在汉字的点阵字库中,每个字节的每个位都代表一个汉字的一个点,每个汉字都是由一个矩形的点阵组成,0 代表没有,1 代表有点,将 0 和 1 分别用不同 颜色画出,就形成了一个汉字,常用的点阵矩阵有 12 * 12, 14 * 14, 16 * 16 三种字库。

字库根据字节所表示点的不同有分为横向矩阵和纵向矩阵。

目前多数的字库都是横向矩阵的存储方式(用得最多的应该是早期 UCDOS 字库),纵向矩阵一般是因为有某些液晶是采用纵向扫描显示法,为了提高显示速度,于是便把字库矩阵做成纵向,省得在显示时还要做矩阵转换。我们接下去所描述的都是指横向矩阵字库。

2.2 16*16点阵字库

对于 16*16 的矩阵来说,它所需要的位数共是 16*16=256 个位,每个字节为 8 位,因此,每个汉字都需要用 256/8=32 个字节来表示。 即每两个字节代表一行的 16 个点,共需要 16 行,显示汉字时,只需一次性读取 32 个字节,并将每两个字节为一行打印出来,即可形成一个汉字。 点阵结构如下图所示:

3. 汉字点阵获取

3.1 利用区位码获取汉字

汉字点阵字库是根据区位码的顺序进行存储的,因此,我们可以根据区位来获取一个字库的点阵,它的计算公式如下:

*点阵起始位置 = ((区码- 1)94 + (位码 – 1)) * 汉字点阵字节数

获取点阵起始位置后,我们就可以从这个位置开始,读取出一个汉字的点阵。

3.2 利用汉字机内码获取汉字

前面我们己经讲过,汉字的区位码和机内码的关系如下:

机内码高位字节 = 区码 + 20H + 80H(或区码 + A0H)

机内码低位字节 = 位码 + 20H + 80H(或位码 + AOH)

反过来说,我们也可以根据机内码来获得区位码:

区码 = 机内码高位字节 - A0H

位码 = 机内码低位字节 - AOH

将这个公式与获取汉字点阵的公式进行合并计就可以得到汉字的点阵位置。

二. 代码实现

1. C++

创建文件夹 DrawChinese 并进入此文件夹

准备中文点阵字库,以及文本文件、图片。

中文点阵字库及显示工具程序百度网盘

链接:https://pan.baidu.com/s/1gCtiVbHrr2y6teo-bG8rEw
提取码:2413

windows上文本文件默认编码是ANSI。
ANSI即扩展的ASCII编码,简体中文操作系统中ANSI编码代表GB2312编码。linux系统默认编码UTF-8,两种编码对中文的处理不一致,因此在linux中显示中文乱码。

因为代码是采用ascii字库以及hz中文点阵字库,中文字库编码基本采用GB2312(扩展的ASCll编码),所以不能将ANSI改为UTF-8。

在当前文件夹里创建文件 drawchinese.cpp,编写代码

c++的代码不是我编的,因为c++的opencv过于复杂。
参考我们老师给的资料。

 vim drawchinese.cpp

#include<iostream>
#include<opencv/cv.h>
#include"opencv2/opencv.hpp"
#include<opencv/cxcore.h>
#include<opencv/highgui.h>
#include<math.h>
using namespace cv;
using namespace std;

//绘制汉字,采用中文字库
void paint_chinese(Mat& image,int x_offset,int y_offset,unsigned long offset);
//绘制字符,采用ASCll字库
void paint_ascii(Mat& image,int x_offset,int y_offset,unsigned long offset);
//在图片上添加文本
void put_text_to_image(int x_offset,int y_offset,String image_path,char* logo_path);

int main()
{	
    string imagePath="liqin.jpg";
    char* txtPath=(char*)"logo.txt";
	//参数分别为:文本添加的位置(x,y)、图片路径、所添加的文本路径
	put_text_to_image(100,250,imagePath,txtPath);		
    return 0;
}

//绘制字符
void paint_ascii(Mat& image,int x_offset,int y_offset,unsigned long offset){
	Point p;
	p.x = x_offset;
	p.y = y_offset;
	//存放ascii字膜
	char buff[16];           
	//打开ascii字库文件
	FILE *ASCII;
	if ((ASCII = fopen("Asci0816.zf", "rb")) == NULL){
        printf("Can't open ascii.zf,Please check the path!");
		//getch();
		exit(0);
	}
	fseek(ASCII, offset, SEEK_SET);
	fread(buff, 16, 1, ASCII);
	int i, j;
	Point p1 = p;
	for (i = 0; i<16; i++)                  //十六个char
	{
		p.x = x_offset;
		for (j = 0; j < 8; j++)              //一个char八个bit
		{
			p1 = p;
			if (buff[i] & (0x80 >> j))    /*测试当前位是否为1*/
			{
				/*
					由于原本ascii字膜是8*16的,不够大,
					所以原本的一个像素点用4个像素点替换,
					替换后就有16*32个像素点
					ps:感觉这样写代码多余了,但目前暂时只想到了这种方法
				*/
				circle(image, p1, 0, Scalar(0, 0, 255), -1);
				p1.x++;
				circle(image, p1, 0, Scalar(0, 0, 255), -1);
				p1.y++;
				circle(image, p1, 0, Scalar(0, 0, 255), -1);
				p1.x--;
				circle(image, p1, 0, Scalar(0, 0, 255), -1);
			}						
			p.x+=2;            //原来的一个像素点变为四个像素点,所以x和y都应该+2
		}
		p.y+=2;
	}
}
//绘制汉字
void paint_chinese(Mat& image,int x_offset,int y_offset,unsigned long offset){
    Point p;
    p.x=x_offset;
    p.y=y_offset;
    FILE *HZK;
    char buff[72];//72个字节,用来存放汉字的
   if((HZK=fopen("HZKf2424.hz","rb"))==NULL){
        printf("Can't open HZKf2424.hz,Please check the path!");
        exit(0);//退出
    }
    fseek(HZK, offset, SEEK_SET);/*将文件指针移动到偏移量的位置*/
    fread(buff, 72, 1, HZK);/*从偏移量的位置读取72个字节,每个汉字占72个字节*/
    bool mat[24][24];//定义一个新的矩阵存放转置后的文字字膜
    int i,j,k;
    for (i = 0; i<24; i++)                 /*24x24点阵汉字,一共有24行*/
	{
		for (j = 0; j<3; j++)                /*横向有3个字节,循环判断每个字节的*/
			for (k = 0; k<8; k++)              /*每个字节有8位,循环判断每位是否为1*/
				if (buff[i * 3 + j] & (0x80 >> k))    /*测试当前位是否为1*/
				{
					mat[j * 8 + k][i] = true;          /*为1的存入新的字膜中*/
				}
				else {
					mat[j * 8 + k][i] = false;
				}
	}
    for (i = 0; i < 24; i++)
	{
		p.x = x_offset;
		for (j = 0; j < 24; j++)
		{		
			if (mat[i][j])
				circle(image, p, 1, Scalar(255, 0, 0), -1);		  //写(替换)像素点
			p.x++;                                                //右移一个像素点
		}
		p.y++;                                                    //下移一个像素点
	}
}
//在图片上添加文本
void put_text_to_image(int x_offset,int y_offset,String image_path,char* logo_path)
{
    //x和y就是第一个字在图片上的起始坐标
    //通过图片路径获取图片
    Mat image=imread(image_path);
    int text_length=17;         //文本的字节长度
    unsigned char qh,wh;      //定义区号,位号
    unsigned long offset;           //偏移量
    unsigned char hexcode[30];      //用于存放记事本读取的十六进制,记得要用无符号
    FILE* file_logo;
    if ((file_logo = fopen(logo_path, "rb")) == NULL){
		printf("Can't open txtfile,Please check the path!");
		//getch();
		exit(0);
	}
    fseek(file_logo, 0, SEEK_SET);
    fread(hexcode, length, 1, file_logo);
    int x =x_offset,y = y_offset;//x,y:在图片上绘制文字的起始坐标
    for(int m=0;m<text_length;){
        if(hexcode[m]==0x23){
            break;//读到#号时结束
        }
        else if(hexcode[m]>0xaf){
            qh=hexcode[m]-0xaf;//使用的字库里是以汉字啊开头,而不是以汉字符号开头
            wh=hexcode[m+1] - 0xa0;//计算位码
            offset=(94*(qh-1)+(wh-1))*72L;
            paint_chinese(image,x,y,offset);
            /*
            计算在汉字库中的偏移量
            对于每个汉字,使用24*24的点阵来表示的
            一行有三个字节,一共24行,所以需要72个字节来表示
            */
            m=m+2;//一个汉字的机内码占两个字节,
            x+=24;//一个汉字为24*24个像素点,由于是水平放置,所以是向右移动24个像素点
        }
        else{//当读取的字符为ASCII码时
        wh=hexcode[m];
        offset=wh*16l;//计算英文字符的偏移量
        paint_ascii(image,x,y,offset);
        m++;//英文字符在文件里表示只占一个字节,所以往后移一位就行了
        x+=16;
        }
    }
    imwrite("image_text.jpg",image);    //保存为新图片 image_text.jpg
	imshow("image",image);          //展示图片
    waitKey(0); 
}

注意函数 void put_text_to_image() 中的
int text_length=17; //文本的字节长度,
改成自己的文本长度

字节长度计算:汉字两个字节,空格以及字符一个字节
如果按照这样计算,最后显示的文本还有多余,则你可能需要考虑文本内容是全角还是半角的问题。全角就是每个字符占两个字节。

编译代码

g++ drawchinese.cpp -o test `pkg-config --cflags --libs opencv`

执行程序,输出结果

./test

2. python

这里还是选择在 DrawChinese 文件夹中

准备字体,我采用simhei字体

百度网盘链接:百度网盘 请输入提取码
提取码:7qzy

创建文件,编写代码

vim drawchinese.py

from PIL import Image, ImageDraw, ImageFont
import cv2
import numpy as np
 
# cv2读取图片
img = cv2.imread('liqin.jpg') # 名称不能有汉字
cv2img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # cv2和PIL中颜色的hex码的储存顺序不同
#将cv2格式转化为PIL格式
pilimg = Image.fromarray(cv2img)
 
# PIL图片上打印汉字
draw = ImageDraw.Draw(pilimg) # 图片上打印
# 参数1:字体文件路径,参数2:字体大小 ,参数3:编码格式
font = ImageFont.truetype("simhei.ttf", 60, encoding="utf-8") 
# 参数1:打印坐标,参数2:文本,参数3:字体颜色,参数4:字体
draw.text((150, 250), "Hi,我是缔宇", (255, 0, 0), font=font) 
 
# PIL图片转cv2 图片
# cv2.cvtColor颜色空间转换函数,第一个参数是需要转换的图片,第二个参数是转换成何种格式,这里是将RGB换成BGR
cv2charimg = cv2.cvtColor(np.array(pilimg), cv2.COLOR_RGB2BGR)

cv2.imwrite("image_python.jpg",cv2charimg)   #保存图片
cv2.imshow("photo", cv2charimg)   #展示图片
cv2.waitKey (0) 
cv2.destroyAllWindows()

运行程序

python3 drawchinese.py

三. 总结

运用opencv添加中文,在C++中虽然也有多种方法添加中文,但都很复杂,而python的方法就很简单了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/957086.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

线程同步与Mutex

梦想是逃离世界… 文章目录 一、什么是线程同步&#xff1f;二、线程同步机制三、互斥锁&#xff08;Mutex&#xff09;四、loock 和 unlock五、Mutex的四种类型 一、什么是线程同步&#xff1f; 线程同步(Thread Synchronization)是多线程编程中的一个重要概念&#xff0c;它…

基于SpringBoot和PostGIS的全球首都信息管理设计与实现

目录 前言 一、首都空间表的设计 1、三张空间表的结构 二、SpringBoot后台管理的设计与实现 1、模型层的实现 2、业务层及控制层实现 三、前端的实现与成果可视化 1、新增数据的保存 2、首都的实际管理成果 3、全球首都信息 四、总结 前言 首都&#xff0c;一个国家的…

计算机网络 (50)两类密码体制

前言 计算机网络中的两类密码体制主要包括对称密钥密码体制&#xff08;也称为私钥密码体制、对称密码体制&#xff09;和公钥密码体制&#xff08;也称为非对称密码体制、公开密钥加密技术&#xff09;。 一、对称密钥密码体制 定义&#xff1a; 对称密钥密码体制是一种传…

【数据结构篇】顺序表 超详细

目录 一.顺序表的定义 1.顺序表的概念及结构 1.1线性表 2.顺序表的分类 2.1静态顺序表 2.2动态顺序表 二.动态顺序表的实现 1.准备工作和注意事项 2.顺序表的基本接口&#xff1a; 2.0 创建一个顺序表 2.1 顺序表的初始化 2.2 顺序表的销毁 2.3 顺序表的打印 3.顺序…

C 语言雏启:擘画代码乾坤,谛观编程奥宇之初瞰

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。* 这一课主要是让大家初步了解C语言&#xff0c;了解我们的开发环境&#xff0c;main函数&#xff0c;库…

根据 Web 服务器端的架构相关知识,将PHP改JAVA重构企业网站系统

目录 案例 【题目】 【问题 1】(7 分) 【问题 2】(8 分) 【问题 3】(10 分) 答案 【问题 1】解析 【问题 2】解析 【问题 3】解析 相关推荐 案例 阅读以下关于应用服务器的叙述&#xff0c;在答题纸上回答问题 1 至问题 3。 【题目】 某电子产品制造公司&#xff0c…

多选multiple下拉框el-select回显问题(只显示后端返回id)

首先保证v-model的值对应options数据源里面的id <el-form-item prop"subclass" label"分类" ><el-select v-model"formData.subclass" multiple placeholder"请选择" clearable :disabled"!!formData.id"><e…

java快速导出word文档

点关注不迷路&#xff0c;欢迎再访&#xff01; 精简博客内容&#xff0c;尽量已行业术语来分享。 努力做到对每一位认可自己的读者负责。 帮助别人的同时更是丰富自己的良机。 文章目录 前言一.添加 Apache POI 依赖二.填充文档内容三.导出文档效果测试 前言 在 Java 应用程序…

《MambaIR:一种基于状态空间模型的简单图像修复基线方法》学习笔记

paper&#xff1a;2402.15648 目录 摘要 一、引言 1、模型性能的提升依赖于网络感受野的扩大&#xff1a; 2、全局感受野和高效计算之间存在固有矛盾&#xff1a; 3、改进版 Mamba的巨大潜力 4、Mamba 在图像修复任务中仍面临以下挑战&#xff1a; 5、方法 6、主要贡献…

ngnix上传小文件成功,大文件报错

ngnix错误日志 "/var/tmp/nginx/client//0000001299" failed (2: No such file or directory), client: 10.188.141.160, server: 127.0.0.1, request: "POST /fts/sys/common/biUpload HTTP/1.1", host: "10.20.166.179", referrer: "http…

Word表格批量提取数据到Excel,批量提取,我爱excel

Word表格批量提取数据到Excel&#xff0c;Word导出到Excel - 我爱Excel助你高效办公 在日常办公中&#xff0c;Word表格常常用于记录和整理数据&#xff0c;但将这些数据从Word提取到Excel&#xff0c;特别是当涉及多个文件时&#xff0c;常常让人头疼。如果你经常需要将多个W…

【Zookeeper】Windows下安装Zookeeper(图文记录详细步骤,手把手包安装成功)

【Zookeeper】Windows下安装Zookeeper Zookeeper简介一、下载Zookeeper安装包1.1、官网下载Zookeeper1.2、网盘下载Zookeeper 二、解压Zookeeper安装包到指定目录三、Zookeeper安装目录下创建文件夹四、进入config目录五、复制zoo_sample.cfg文件&#xff0c;改名为zoo.cfg六、…

JDK长期支持版本(LTS)

https://blogs.oracle.com/java/post/the-arrival-of-java-23 jdk长期支持版本&#xff08;LTS&#xff09;&#xff1a;JDK 8、11、17、21&#xff1a;

深度学习J3周:RNN-心脏病预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 要求&#xff1a; 1.本地读取并加载数据 2.了解循环神经网络&#xff08;RNN&#xff09;的构建过程 3.测试集accuracy到达87% 拔高&#xff1a; 测试机a…

Linux C\C++方式下的文件I/O编程

【图书推荐】《Linux C与C一线开发实践&#xff08;第2版&#xff09;》_linux c与c一线开发实践pdf-CSDN博客 《Linux C与C一线开发实践&#xff08;第2版&#xff09;&#xff08;Linux技术丛书&#xff09;》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 Lin…

FPGA:Quartus软件与操作系统版本对照表

文章目录 1.软件概述2.软件版本3.设计流程4.支持的设备5.新特性6.版本对照 1.软件概述 Quartus软件是由英特尔&#xff08;Intel&#xff09;公司开发的一款功能强大的FPGA&#xff08;现场可编程逻辑门阵列&#xff09;设计工具&#xff0c;广泛应用于数字电路设计、仿真、综…

ui设计公司分享:浅色 UI 设计

在数字化产品琳琅满目的今天&#xff0c;用户对于界面的要求早已不止于功能的实现&#xff0c;更追求一种舒适、无压的交互体验。而浅色UI设计&#xff0c;凭借其独特的魅力&#xff0c;正逐渐成为众多设计师营造优质体验的首选。 一、浅色UI设计的视觉优势 &#xff08;一&a…

Unity中实现伤害跳字效果(简单好抄)

第一步骤安装并导入Dotween插件&#xff08;也可以不用导入之后直接下载我的安装包&#xff09; 官网DOTween - 下载 第二步&#xff1a; 制作跳字预制体 建议把最佳适应打开&#xff0c;这样就不怕数字太大显示不全了。 第三步&#xff1a;创建一个空对象并编写脚本JumpNumbe…

为什么相关性不是因果关系?人工智能中的因果推理探秘

目录 一、背景 &#xff08;一&#xff09;聚焦当下人工智能 &#xff08;二&#xff09;基于关联框架的人工智能 &#xff08;三&#xff09;基于因果框架的人工智能 二、因果推理的基本理论 &#xff08;一&#xff09;因果推理基本范式&#xff1a;因果模型&#xff0…

VMamba 安装教程(无需更改base环境中的cuda版本)

导航 安装教程导航 Mamba 及 Vim 安装问题参看本人博客&#xff1a;Mamba 环境安装踩坑问题汇总及解决方法&#xff08;初版&#xff09;Linux 下Mamba 及 Vim 安装问题参看本人博客&#xff1a;Mamba 环境安装踩坑问题汇总及解决方法&#xff08;重置版&#xff09;Windows …