获取文本边界框,与后端无关

13 投票
3 回答
6001 浏览
提问于 2025-04-18 00:00

我想在一个matplotlib图形中获取一些文本周围的边界框(尺寸)。在这篇帖子这里,我了解到可以使用方法text.get_window_extent(renderer)来获取边界框,但我需要提供正确的渲染器。有些后端没有方法figure.canvas.get_renderer(),所以我尝试了matplotlib.backend_bases.RendererBase()来获取渲染器,但结果不太理想。下面是一个简单的例子:

import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle

fig = plt.figure()
ax = plt.subplot()
txt = fig.text(0.15,0.5,'afdjsklhvvhwd', fontsize = 36)
renderer1 = fig.canvas.get_renderer()
renderer2 = mpl.backend_bases.RendererBase()
bbox1 = txt.get_window_extent(renderer1)
bbox2 = txt.get_window_extent(renderer2)
rect1 = Rectangle([bbox1.x0, bbox1.y0], bbox1.width, bbox1.height, \
    color = [0,0,0], fill = False)
rect2 = Rectangle([bbox2.x0, bbox2.y0], bbox2.width, bbox2.height, \
    color = [1,0,0], fill = False)
fig.patches.append(rect1)
fig.patches.append(rect2)
plt.draw()

这个例子生成了以下图表:

image

很明显,红色的框太小了。我觉得Paul的回答这里也发现了同样的问题。黑色的框看起来不错,但我无法使用MacOSX后端,或者其他没有方法figure.canvas.get_renderer()的后端。

如果这有帮助,我的系统是Mac OS X 10.8.5,Matplotlib 1.3.0,Python 2.7.5。

3 个回答

0

Figure对象中的_get_renderer()方法给我的结果很满意:

from matplotlib.figure import Figure
import matplotlib.pyplot as plt

fig1, ax1 = plt.subplots()

plotted_text = ax1.text(0.5, 0.5, "afdjsklhvvhwd")

renderer1 = fig1.canvas.get_renderer()
bb1 = plotted_text.get_window_extent(renderer=renderer1).transformed(ax1.transData.inverted())

text_width1 = bb1.width


fig2 = Figure()
ax2 = fig2.subplots()

plotted_text2 = ax2.text(0.5, 0.5, "afdjsklhvvhwd")

renderer2 = fig2._get_renderer()
bb2 = plotted_text2.get_window_extent(renderer=renderer2).transformed(ax2.transData.inverted())

text_width2 = bb2.width
1

如果你想要获取一个旋转文本区域的紧凑边界框,这里有一个可能的解决方案。

# generate text layer
def text_on_canvas(text, myf, ro, margin = 1):
    axis_lim = 1

    fig = plt.figure(figsize = (5,5), dpi=100)
    plt.axis([0, axis_lim, 0, axis_lim])

    # place the left bottom corner at (axis_lim/20,axis_lim/20) to avoid clip during rotation
    aa = plt.text(axis_lim/20.,axis_lim/20., text, ha='left', va = 'top', fontproperties = myf, rotation = ro, wrap=True)
    plt.axis('off')
    text_layer = fig2img(fig) # convert to image
    plt.close()

    we = aa.get_window_extent()
    min_x, min_y, max_x, max_y = we.xmin, 500 - we.ymax, we.xmax, 500 - we.ymin
    box = (min_x-margin, min_y-margin, max_x+margin, max_y+margin)

    # return coordinates to further calculate the bbox of rotated text
    return text_layer, min_x, min_y, max_x, max_y 


def geneText(text, font_family, font_size, style):
    myf = font_manager.FontProperties(fname=font_family, size=font_size)
    ro = 0

    if style < 8: # rotated text
        # no rotation, just to get the minimum bbox
        htext_layer, min_x, min_y, max_x, max_y = text_on_canvas(text, myf, 0)

        # actual rotated text
        ro = random.randint(0, 90)
        M = cv2.getRotationMatrix2D((min_x,min_y),ro,1)
        # pts is 4x3 matrix
        pts = np.array([[min_x, min_y, 1],[max_x, min_y, 1],[max_x, max_y, 1],[min_x, max_y,1]]) # clockwise
        affine_pts = np.dot(M, pts.T).T
        #print affine_pts
        text_layer, _, _, _, _ = text_on_canvas(text, myf, ro)

        visualize_points(htext_layer, pts)
        visualize_points(text_layer, affine_pts)

        return text_layer  

    else:
        raise NotImplementedError


fonts = glob.glob(fonts_path + '/*.ttf')
ret = geneText('aaaaaa', fonts[0], 80, 1)

结果看起来是这样的:第一个是没有旋转的,第二个是旋转后的文本区域。完整的代码片段可以在这里找到。

在这里输入图片描述 在这里输入图片描述

10

这是我的解决方案/小技巧。@tcaswell建议我看看matplotlib是如何处理保存图形的,特别是紧凑的边界框。我在Github上找到了backend_bases.py的代码,它是通过将图形保存到一个临时文件对象来获取缓存中的渲染器。我把这个小技巧变成了一个小函数,如果后端支持,就使用内置的方法get_renderer(),否则就使用保存方法。

def find_renderer(fig):

    if hasattr(fig.canvas, "get_renderer"):
        #Some backends, such as TkAgg, have the get_renderer method, which 
        #makes this easy.
        renderer = fig.canvas.get_renderer()
    else:
        #Other backends do not have the get_renderer method, so we have a work 
        #around to find the renderer.  Print the figure to a temporary file 
        #object, and then grab the renderer that was used.
        #(I stole this trick from the matplotlib backend_bases.py 
        #print_figure() method.)
        import io
        fig.canvas.print_pdf(io.BytesIO())
        renderer = fig._cachedRenderer
    return(renderer)

这是使用find_renderer()和我原始示例中稍微修改过的代码得到的结果。使用支持get_renderer()方法的TkAgg后端,我得到了:

TkAgg

而使用不支持get_renderer()方法的MacOSX后端,我得到了:

MacOSX

显然,使用MacOSX后端的边界框并不是完美的,但比我最初问题中的红框要好得多。

撰写回答