一、任务概述

在这里插入图片描述
任务要求:需要将上述生僻字png图片批量自动转成ttf字体文件,该字体对应的unicode码为图中下半部分对应的16进制值。

整个任务分成几个部分实现:

  • OCR识别:识别出图片下半部分对应的16进制数值;
  • 图形文字精确提取:提取出图片上半部分精确的文字图形区域;
  • png图片转svg: 通过图像处理算法提取上半部分图片的字形轮廓,将其转成svg文件;
  • svg转ttf:通过FontForge提供的Python工具实现批量转换;

二、实现

2.1 ocr识别

综合考虑识别精度和CPU推理速度要求,本文使用PaddleOCR实现。

2.1.1 安装环境

python -m pip install paddlepaddle==2.3.0 -i https://mirror.baidu.com/pypi/simple
pip install paddlehub -i https://pypi.tuna.tsinghua.edu.cn/simple 
pip install shapely -i https://pypi.tuna.tsinghua.edu.cn/simple

2.1.2 实现脚本

# 导入系统库
import cv2
import os

# 导入字体识别库
import paddlehub as hub

# 导入自定义识别库
from tools import sim_code 

def main(): 
    '''
    主函数
    '''
    # 定义参数
    img_list = list()
    img_dir = './screenshot/25A2C_2625F'  
    target_width, target_height = 363, 269

    # 获取文件列表
    for file in os.listdir(img_dir):
            if os.path.splitext(file)[1].lower() in '.png|.jpg':
                img_list.append(file)
    print('当前总图片数量: %d' % len(img_list))
    
    # 创建识别器
    ocr = hub.Module(name="chinese_ocr_db_crnn_server")
    
    # 循环处理图片
    index = 0
    error_index = 0
    for img_path in img_list:
        img = cv2.imread(os.path.join(img_dir,img_path),cv2.IMREAD_COLOR)
        # 图像标准化
        h, w, _=img.shape
        if h != target_height or w != target_width:
            img = cv2.resize(img, dsize=(target_width, target_height))
            
        # 提取ocr区域
        ocrimg = img[170:259,40:310,:]    
        h,w,_= ocrimg.shape
        ocrimg = cv2.copyMakeBorder(ocrimg,2*h,2*h,2*w,2*w, cv2.BORDER_CONSTANT,value=[255,255,255])    
        result = ocr.recognize_text([ocrimg])
        
        code = result[0]['data']
        if len(code)==0:
            error_index+=1
            cv2.imwrite('error/%d.png' % error_index, img)
            continue
        
        code = code[0]["text"].strip()
        code = sim_code(code)
        if len(code)!=5:
            error_index+=1
            cv2.imwrite('error/%d.png' % error_index, img)
            continue
        try:
            a = int(code,16)
        except Exception as e:
            error_index+=1
            cv2.imwrite('error/%d.png' % error_index, img)
            continue
        
        index += 1 
        print(img_path+'   识读结果:'+code)
        
        # 检查是否有同名文件
        save_path = 'ocr/%s.png' % code
        if os.path.exists(save_path):
            error_index+=1
            cv2.imwrite('error/%d_repeat.png' % error_index, img)
            continue
            
        textimg = img   
        cv2.imwrite(save_path, textimg)


if __name__ == "__main__":
    '''
    程序入口
    '''
    main()

其中sim_code函数定义如下:

def sim_code(code):
    code = code.strip()
    code = code.upper()
    # 剔除常见错误项
    code = code.replace("G", "")
    code = code.replace("H", "")
    code = code.replace("I", "1")
    code = code.replace("J", "")
    code = code.replace("K", "")
    code = code.replace("L", "")
    code = code.replace("M", "")
    code = code.replace("N", "")
    code = code.replace("O", "0")
    code = code.replace("P", "")
    code = code.replace("Q", "")
    code = code.replace("R", "")
    code = code.replace("S", "")
    code = code.replace("T", "")
    code = code.replace("U", "")
    code = code.replace("V", "")
    code = code.replace("W", "")
    code = code.replace("X", "")
    code = code.replace("Y", "")
    code = code.replace("Z", "")
    code = code.replace("0", "0")   
    return code

识读结果如下图所示:
在这里插入图片描述

2.2 图形文字精确提取

完整代码如下:

# 导入系统库
import cv2
import os
import numpy as np


def main(): 
    '''
    主函数
    '''
    # 定义参数
    img_list = list()
    img_dir = './ocr' 
    target_width, target_height = 363, 269

    # 获取文件列表
    for file in os.listdir(img_dir):
            if os.path.splitext(file)[1].lower() in '.png|.jpg':
                img_list.append(file)
    print('当前总图片数量: %d' % len(img_list))
    
    # 循环处理图片
    index = 0
    error_index = 0
    for img_path in img_list:
        img = cv2.imread(os.path.join(img_dir,img_path),cv2.IMREAD_COLOR)
        # 图像标准化
        h, w, _=img.shape
        if h != target_height or w != target_width:
            img = cv2.resize(img, dsize=(target_width, target_height))
        
        # 去掉上下左右边界线
        image_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        edges = cv2.Canny(image_gray, 170, 220, apertureSize=3)
        lines = cv2.HoughLines(edges, 1, np.pi / 180, 250)
        for line in lines:
            # 获取rho和theta
            rho, theta = line[0]
            a = np.cos(theta)
            b = np.sin(theta)
            x0 = a * rho
            y0 = b * rho
            x1 = int(x0 + 1000 * (-b))
            y1 = int(y0 + 1000 * (a))
            x2 = int(x0 - 1000 * (-b))
            y2 = int(y0 - 1000 * (a))
            cv2.line(img, (x1, y1), (x2, y2), (255, 255, 255), thickness=20)
        
        # 提取图形区域
        img = img[5:155,100:270,:]
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        
        # 精确裁剪
        ret, thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY_INV)
        coords = np.column_stack(np.where(thresh > 0))
        coords = np.array(coords, dtype=np.float32)
        #min_rect = cv2.minAreaRect(coords)
        
        rect = cv2.boundingRect(coords)
        [y, x, h, w] = rect
        img = img[y:y+h,x:x+w,:]
        
        # 调整为正方形
        h,w,_ = img.shape
        if h>w:
            pad = int((h-w)/2.0)
            img = cv2.copyMakeBorder(img,0,0,pad,pad, cv2.BORDER_CONSTANT,value=[255,255,255])
        elif w>h:
            pad = int((w-h)/2.0)
            img = cv2.copyMakeBorder(img,pad,pad,0,0, cv2.BORDER_CONSTANT,value=[255,255,255])
                    
        # 统一缩放
        img = cv2.resize(img, dsize=(128, 128))

        # 边缘补白
        img = cv2.copyMakeBorder(img,10,10,10,10, cv2.BORDER_CONSTANT,value=[255,255,255])   
        
        # 保存
        code = os.path.splitext(img_path)[0]
        save_path = 'crop/%s.png' % code  
        cv2.imwrite(save_path, img)
        index += 1
        print(img_path)   
        


if __name__ == "__main__":
    '''
    程序入口
    '''
    main()

效果如下图所示:
在这里插入图片描述

2.3 png转svg

这里主要通过opencv的形态学操作提取图像轮廓实现转换。

# 导入系统库
import cv2
import os

def main():
    '''主函数'''
    # 定义参数
    img_list = list()
    img_dir = './crop' 

    # 获取文件列表
    for file in os.listdir(img_dir):
            if os.path.splitext(file)[1].lower() in '.png|.jpg':
                img_list.append(file)
    print('当前总图片数量: %d' % len(img_list))
    
    # 循环处理图片
    index = 0
    for img_path in img_list:
        textimg = cv2.imread(os.path.join(img_dir,img_path),cv2.IMREAD_COLOR)
        
        # 提取图形区域
        textimg = cv2.resize(textimg, dsize=(640, 640))
        blur = cv2.GaussianBlur(textimg, (3, 3), 0)
        gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
        ret, thresh = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)               
        contours,hierarchy = cv2.findContours(thresh, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_SIMPLE)
        
        epsilon = 10
        h, w, _ = textimg.shape
        code = os.path.splitext(img_path)[0]
        svg_path = 'svg/'+code+'.svg'
        with open(svg_path, "w+") as f:
            f.write(f'<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="{w}.000000pt" height="{h}.000000pt" viewBox="0 0 680.000000 680.000000" preserveAspectRatio="xMidYMid meet">')      
            f.write(f'<g transform="scale(1.00000,1.00000)">')
            for c in contours:
                f.write('<path d="M')
                approx = cv2.approxPolyDP(c,epsilon,False)
                for i in range(len(approx)):
                    x, y = approx[i][0]
                    if i == len(approx)-1:
                        f.write(f"{x} {y}")
                    else:
                        f.write(f"{x} {y} ")
                f.write('"/>')                
            f.write(f'</g>')
            f.write("</svg>")
        index +=1
        print('当前处理完 %d 张图片' % index)
    print('全部处理结束')

if __name__ == "__main__":
    '''程序入口'''
    main()

部分样例转换结果如下图所示:
在这里插入图片描述

2.4 svg转ttf

ttf是专门的字体库文件,目前能够支持ttf编辑的软件并不多。这里推荐使用FontForge,该软件提供了python处理接口,可以使用python脚本批量转换svg到ttf。

首先从官网下载windows版的FontForge并安装,本文将其安装到D盘的toolplace的文件夹中。要使用FontForge提供的python接口,我们必须要使用FontForge的python执行器,这个执行器位于FontForge安装目录的bin文件夹中,完整路径如下图所示:

D:\toolplace\FontForgeBuilds\bin

在这个目录下面有个名为ffpython.exe的可执行文件,这个就是FontForge提供的python执行器。为了能够正常使用这个执行器,我们需要将我们的可执行目录切换到bin文件夹下面,然后创建一个转换脚本main_ttf.py,内容如下:

# 导入系统库
from time import sleep
import fontforge, os,psMat


def main():
    '''
    主函数
    '''
    # 定义参数
    img_list = list()
    img_dir = './svg' 

    # 获取文件列表
    for file in os.listdir(img_dir):
            if os.path.splitext(file)[1].lower() in '.svg':
                img_list.append(file)
    print('当前总图片数量: %d' % len(img_list))
    
    # 循环处理图片
    index = 0
    
    for img_path in img_list:  
        print('当前处理 '+img_path)
        
        # 获取unicode
        codestr = os.path.splitext(img_path)[0]
        code = int(codestr,16)
        
         # 创建字体
        font = fontforge.font()
        font.encoding = 'UnicodeFull'
        font.version = '1.0'
        font.weight = 'Regular'
        font.fontname = 'uni'+codestr
        font.familyname = 'uni'+codestr
        font.fullname = 'uni'+codestr       
        
        # 创建字符
        glyph = font.createChar(code, "uni"+codestr)
        glyph.importOutlines(os.path.join(img_dir,img_path))
        
        # 位移调整
        base_matrix = psMat.translate(0,0)
        glyph.transform(base_matrix)
        
        # 写入ttf
        font.generate('./ttf/'+codestr+'.ttf')      
        index +=1
        # if index>1:
        #     break
        print('当前处理完 %d 张图片' % index)
        
        # 删除文件
        os.remove(os.path.join(img_dir,img_path))
        
    print('全部处理结束')


if __name__ == "__main__":
    '''
    程序入口
    '''
    main()

然后使用下面的命令执行该脚本:

./ffpython.exe main_ttf.py

最后在当前目录下会生成一个个的ttf文件。

我们可以使用FontForge的客户端查看我们这个生成的字体文件,打开后在菜单栏上选择Encoding-Compact,如下图所示:
在这里插入图片描述
可以看到我们已经成功的将png图片批量转换成了ttf文件。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐