在 Python 中使用给定字体正确渲染文本并准确检测其边界 [英] Properly render text with a given font in Python and accurately detect its boundaries

查看:24
本文介绍了在 Python 中使用给定字体正确渲染文本并准确检测其边界的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这可能是一件非常简单的事情,我也认为是这样,但显然不是.我一定花了一个星期的时间来努力完成这项工作,但看在我的爱上,我无法做到.

我需要什么

我需要在 Python 中使用任何给定的字体(类似手写的)呈现任何给定的字符串(仅包含标准字符).字体必须从 TTF 文件加载.我还需要能够准确地检测它的边界(获得文本的确切开始和结束位置,垂直和水平),最好在绘制之前.最后,如果输出是一个我可以继续处理的数组,而不是写入光盘的图像文件,那真的会让我的生活更轻松.

我的尝试

Imagemagick 绑定(即 Wand):在设置图像大小并在其上渲染文本之前,无法弄清楚如何获取文本度量.

通过 Pycairo 绑定的 Pango:几乎不存在的文档,无法弄清楚如何从文件加载 TrueType 字体.

PIL(枕头):最有前途的选择.我已经设法准确地计算出任何文本的高度(令人惊讶的是,这不是 getsize 返回的高度),但是某些字体的宽度似乎有问题.不仅如此,那些宽度有问题的字体也会被错误地渲染.即使将图像制作得足够大,它们也会被切断.

以下是一些示例,文本为Puzzling":

字体:

字体:

这是我用来生成图像的代码:

from PIL import Image, ImageDraw, ImageFont导入 cv2将 numpy 导入为 np导入全局导入操作系统字体大小 = 75font_paths = sorted(glob.glob('./fonts/*.ttf'))text = "令人费解"背景颜色 = 180文本颜色 = 50颜色_方差 = 60cv2.namedWindow('显示', 0)对于 font_paths 中的 font_path:font = ImageFont.truetype(font_path, font_size)text_width, text_height = font.getsize(text)上升,下降 = font.getmetrics()(宽度, 基线), (offset_x, offset_y) = font.font.getsize(text)# +100 添加以查看文本被截断PIL_image = Image.new('RGB', (text_width-offset_x+100, text_height-offset_y), color=0x888888)draw = ImageDraw.Draw(PIL_image)draw.text((-offset_x, -offset_y), text, font=font, fill=0)cv2.imshow('display', np.array(PIL_image))k = cv2.waitKey()如果 chr(k & 255) == 'q':休息

一些问题

字体有问题吗?一些同事告诉我可能是这样,但我不这么认为,因为 Imagemagick 通过命令行正确渲染了它们.

是我的代码有问题吗?我做错了什么导致文本被切断了吗?

最后,这是 PIL 中的错误吗?在这种情况下,您建议我使用哪个库来解决我的问题?我应该再试一次 Pango 和 Wand 吗?

解决方案

pyvips 文档对选项进行了快速介绍:

https://libvips.github.io/pyvips/vimage.html#pyvips.Image.text

或者 C 库文档有更多细节:

http://libvips.github.io/libvips/API/current/libvips-create.html#vips-text

它制作了抗锯齿文本的单波段 8 位图像,您可以将其用于进一步处理、传递给 NumPy 或 PIL 等.介绍中有一节介绍了如何将 libvips 图像转换为数组:

https://libvips.github.io/pyvips/intro.html#numpy-and-pil

This might strike as something very simple, and I too thought it'd be, but it apparently isn't. I must've spent a week trying to make this work, but I for the love of me can't manage to do so.

What I need

I need to render any given string (only containing standard characters) with any given font (handwritten-like) in Python. The font must be loaded from a TTF file. I also need to be able to accurately detect its borders (get the exact start and end position of the text, vertically and horizontally), preferably before drawing it. Lastly, it'd really make my life easier if the output is an array which I can then keep processing, and not an image file written to disc.

What I've tried

Imagemagick bindings (namely Wand): Couldn't figure out how to get the text metrics before setting the image size and rendering the text on it.

Pango via Pycairo bindings: nearly inexistent documentation, couldn't figure out how to load a TrueType font from a file.

PIL (Pillow): The most promising option. I've managed to accurately calculate the height for any text (which surprisingly is not the height getsize returns), but the width seems buggy for some fonts. Not only that, but those fonts with buggy width also get rendered incorrectly. Even when making the image large enough, they get cut off.

Here are some examples, with the text "Puzzling":

Font: Lovers Quarrel

Result:

Font: Miss Fajardose

Result:

This is the code I'm using to generate the images:

from PIL import Image, ImageDraw, ImageFont
import cv2
import numpy as np
import glob
import os

font_size = 75
font_paths = sorted(glob.glob('./fonts/*.ttf'))
text = "Puzzling"
background_color = 180
text_color = 50
color_variance = 60
cv2.namedWindow('display', 0)

for font_path in font_paths:

    font = ImageFont.truetype(font_path, font_size)
    text_width, text_height = font.getsize(text)

    ascent, descent = font.getmetrics()
    (width, baseline), (offset_x, offset_y) = font.font.getsize(text)

    # +100 added to see that text gets cut off
    PIL_image = Image.new('RGB', (text_width-offset_x+100, text_height-offset_y), color=0x888888)
    draw = ImageDraw.Draw(PIL_image)
    draw.text((-offset_x, -offset_y), text, font=font, fill=0)

    cv2.imshow('display', np.array(PIL_image))
    k = cv2.waitKey()
    if chr(k & 255) == 'q':
        break

Some questions

Are the fonts the problem? I've been told by some colleagues that might be it, but I don't think so, since they get rendered correctly by the Imagemagick via command line.

Is my code the problem? Am I doing something wrong which is causing the text to get cut off?

Lastly, is it a bug in PIL? In that case, which library do you recommend I use to solve my problem? Should I give Pango and Wand another try?

解决方案

pyvips seems to do this correctly. I tried this:

$ python3
Python 3.7.3 (default, Apr  3 2019, 05:39:12) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyvips
>>> x = pyvips.Image.text("Puzzling", dpi=300, font="Miss Fajardose", fontfile="/home/john/pics/MissFajardose-Regular.ttf")
>>> x.write_to_file("x.png")

To make:

The pyvips docs have a quick intro to the options:

https://libvips.github.io/pyvips/vimage.html#pyvips.Image.text

Or the C library docs have a lot more detail:

http://libvips.github.io/libvips/API/current/libvips-create.html#vips-text

It makes a one-band 8-bit image of the antialiased text which you can use for further processing, pass to NumPy or PIL, etc etc. There's a section in the intro on how to convert libvips images into arrays:

https://libvips.github.io/pyvips/intro.html#numpy-and-pil

这篇关于在 Python 中使用给定字体正确渲染文本并准确检测其边界的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆