首先,网上有好几个显示中文标签的教程了,我为什么还要写呢??哼,很显然,是觉得他们实现的不够完美嘛~
YOLOv5在标签显示上,是花了点心思的,标签字体的大小,会根据图片尺寸进行调整,背景颜色条是为了避免图片中的内容干扰标签的展示,网上有的教程直接把字体大小定死了,背景颜色条也去除了,根本就不合理嘛!完全算不上实现。
因为OpenCV不支持中文,所以原代码中用来计算字体尺寸的方法cv2.getTextSize()
对中文字符是不能正确计算宽度的。因此按网上的另一教程,虽然可以显示中文,但标签的背景颜色条宽度是不对的,会长出一截,如图:
我改进的思路是:保留代码原有的自适应尺寸操作,虽然cv2.getTextSize()
对中文字符的宽度计算是错误的,但高度是对的,使用这个高度作为字体的size,然后用支持中文字符的PIL里的方法来计算字符宽度,这样就能得到宽度合适的背景颜色条啦~
代码比较简单,就不装biu啦,以YOLOv5-3.1版本为例,步骤如下:
1、找个中文字体如MSYH.TTC放在YOLOv5文件夹下。
2、train.py文件
with open(opt.data) as f:
改为
with open(opt.data, encoding='UTF-8') as f:
3、test.py文件
with open(data) as f:
改为
with open(data, encoding='UTF-8') as f:
4、utils/general.py文件,导包
from PIL import Image, ImageDraw, ImageFont
修改plot_one_box
函数,if label之后的代码改为
if label:
tf = max(tl - 1, 1) # font thickness
t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
font_size = t_size[1]
font = ImageFont.truetype('MSYH.TTC', font_size)
t_size = font.getsize(label)
c2 = c1[0] + t_size[0], c1[1] - t_size[1]
cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA) # filled
img_PIL = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img_PIL)
draw.text((c1[0], c2[1] - 2), label, fill=(255, 255, 255), font=font)
return cv2.cvtColor(np.array(img_PIL), cv2.COLOR_RGB2BGR)
其中font路径改为自己的字体路径。
5、utils/general.py文件,plot_images
函数中
plot_one_box(box, mosaic, label=label, color=color, line_thickness=tl)
改为
mosaic = plot_one_box(box, mosaic, label=label, color=color, line_thickness=tl)
6、detect.py文件
plot_one_box(xyxy, im0, label=label, color=colors[int(cls)], line_thickness=3)
改为
im0 = plot_one_box(xyxy, im0, label=label, color=colors[int(cls)], line_thickness=3)
搞定, 这样无论是训练、验证还是推理过程,都可以是中文显示了。
这才是完美实现嘛,哼~
5.0版本更新:
针对增加的混淆矩阵等评估图中的中文显示异常问题的解决方案
utils/metrics.py文件
开头添加代码
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
下列代码
sn.set(font_scale=1.0 if self.nc < 50 else 0.8) # for label size
改为
sn.set(font='SimHei', font_scale=1.0 if self.nc < 50 else 0.8) # for label size
6.0版本更新:
6.0版本项目改动较多,官方添加了画中文标签的方法,给我们省了点事儿,但在评估图中的中文还是需要自己改。
1、utils/general.py文件
with open(data, errors='ignore') as f:
改为
with open(data, encoding='UTF-8', errors='ignore') as f:
2、utils/metrics.py文件
sn.set(font_scale=1.0 if self.nc < 50 else 0.8) # for label size
改为
sn.set(font='SimHei', font_scale=1.0 if self.nc < 50 else 0.8) # for label size
3、utils/plots.py文件
开头添加代码
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
最后,6.0版本实现的画中文标签的方法是准备两种字体(默认是Arial.ttf和Arial.Unicode.ttf),根据用户需要选择使用英文字体还是中文字体,毕竟画中文的成本比较高,如果是英文标签就没必要浪费时间了。
我们只需要下载这2种字体到指定位置(默认 C:\Users\用户\AppData\Roaming\Ultralytics),然后在Annotator类的__init__()方法中,把参数example改成任意中文就好了。
当然这里的两种字体可以自己选,只要修改下列两行中的参数font(分别对应英文字体和中文字体):
def __init__(self, im, line_width=None, font_size=None, font='Arial.ttf', pil=False, example='abc'):
self.font = check_font(font='Arial.Unicode.ttf' if is_chinese(example) else font,=font_size or max(round(sum(self.im.size) / 2 * 0.035), 12))
7.0版本更新
这个版本有点麻烦,评估图画图的代码改了不少,导致按照正常步骤还是会出现中文不显示的情况。
先进行以下步骤:
1、utils/general.py文件,yaml_load方法中:
with open(file, errors='ignore') as f:
改为
with open(file, encoding='UTF-8', errors='ignore') as f:
2、utils/plots.py文件
开头添加代码
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
完成这两步后,精确率召回率等图还会出现随机中文不显示的问题。我测试了半天,发现是因为画图被改成了多线程,具体多线程为什么会导致这个问题不清楚。
解决方案:
utils/metrics.py文件,删除plot_pr_curve方法和plot_mc_curve方法上方的多线程装饰器:
@threaded
最后,如果按6.0版本的步骤2,混淆矩阵confusion_matrix的图还是显示不了中文,这和seaborn画图有关,不知道官方为什么要改动这里的代码,新代码画的图跟之前基本没有区别,就是多了个标题…我没找到简单的办法解决这个问题,如果非要混淆矩阵显示中文,建议把ConfusionMatrix类中的plot方法完全替换成6.0版本的代码,再进行6.0版本的步骤2即可。
最后补充Ultralytics YOLOv8,已自动支持中文标签框的绘制,只有评估图会乱码,在调用前添加代码即可:
from matplotlib import pyplot as plt
import seaborn as sn
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
sn.set(font='SimHei')