在matplotlib中高效绘制多张含多个补丁的图像的方法?

5 投票
1 回答
2789 浏览
提问于 2025-04-17 16:48

我正在写一段代码,用来显示图片之间的特征匹配。目前这段代码运行得比较慢。我有一些想法可以加快速度,但对matplotlib的使用和它背后的工作原理还不是特别熟悉。

这段代码的基本结构是:(为了让内容更易读,我省略了一些细节)

from matplotlib.patches import Rectangle, Circle, Ellipse
import matplotlib.gridspec as gridspec
from matplotlib.transforms import Affine2D
from scipy.linalg import inv, sqrtm
import matplotlib.pyplot as plt
import numpy as np
  • 添加一组图片。每张图片都有自己的坐标轴:ax,并且记住ax.transData

    gs = gridspec.GridSpec( nr, nc )
    for i in range(num_images):
         dm.ax_list[i] = plt.subplot(gs[i])
         dm.ax_list[i].imshow( img_list[i])
         transData_list[i] = dm.ax_list[i].transData
    
  • 将特征表示可视化为椭圆

    for i in range(num_chips):
         axi =  chips[i].axi 
         ax  =  dm.ax_list[axi]
         transData = dm.transData_list[axi]
         chip_feats = chips[i].features
         for feat in chip_feats:
             (x,y,a,c,d) = feat
             A = numpy.array( [ ( a, 0, 0 ) ,
                                      ( c, d, 0 ) ,
                                      ( 0, 0, 1 ) ] , dtype=np.float64)
             EllShape = Affine2D( numpy.array(sqrtm( inv(A) ), dtype=np.float64) )
             transEll  = EllShape.translate(x,y)
             unitCirc = Circle((0,0),1,transform=transEll+transData)
             ax.add_patch(unitCirc)
    

我使用RunSnakeRun来分析代码,结果显示绘制所有内容的时间很长。我在学习matplotlib的变换时,基本想法是将每张图片绘制在自己的坐标系中,然后保持多个变换,以便后续可以做一些有趣的操作,但我怀疑这样做可能不太适合大规模数据。

实际绘制的结果如下:

当我调整窗口大小时,图形重绘大约需要4秒,而我还想要平移和缩放。

我为每个特征添加了两个补丁,每张图片大约有300个特征,所以我可以看到轮廓和一些透明度。显然,这样会增加开销。但即使没有椭圆,绘制速度也相对较慢。

我还需要写一些代码来在匹配特征之间画线,但现在我不太确定使用多个坐标轴是否是个好主意,尤其是当数据集相对较小的时候。

所以,我有一些更具体的问题:

  • 绘制椭圆和变换后的圆形哪个更高效?使用matplotlib变换的开销有多大?
  • 有没有办法将一组补丁组合在一起,使它们一起变换或更高效?
  • 把所有内容放在一个坐标轴里会更高效吗?如果这样做,变换的概念还能用吗?还是说变换是主要的问题所在?
  • 有没有快速的方法在一组矩阵A上计算sqrtm( inv( A ) )?还是说用for循环也没问题?
  • 我应该换成像pyqtgraph这样的工具吗?我不打算做任何动画,除了平移和缩放。(也许将来我会想把这些嵌入到一个交互式图表中)

编辑:

我通过手动计算平方根逆矩阵的形式,提高了绘制效率。这确实加快了速度。

在上面的代码中:

 A = numpy.array( [ ( a, 0, 0 ) ,
                          ( c, d, 0 ) ,
                          ( 0, 0, 1 ) ] , dtype=np.float64)
 EllShape = Affine2D( numpy.array(sqrtm( inv(A) ), dtype=np.float64) )

被替换为

 EllShape = Affine2D([\
 ( 1/sqrt(a),         0, 0),\
 ((c/sqrt(a) - c/sqrt(d))/(a - d), 1/sqrt(d), 0),\
 (         0,         0, 1)])

我还发现了一些有趣的时间结果:

  num_to_run = 100000
  all_setup  = ''' import numpy as np ; from scipy.linalg import sqrtm ; from numpy.linalg import inv ; from numpy import sqrt
  a=.1 ; c=43.2 ; d=32.343'''

  timeit( \
  'sqrtm(inv(np.array([ ( a, 0, 0 ) , ( c, d, 0 ) , ( 0, 0, 1 ) ])))',\
  setup=all_setup, number=num_to_run)
   >> 22.2588094075 #(Matlab reports 8 seconds for this run) 

  timeit(\
  '[ (1/sqrt(a), 0, 0), ((c/sqrt(a) - c/sqrt(d))/(a - d), 1/sqrt(d), 0), (0, 0, 1) ]',\
  setup=all_setup,  number=num_to_run)
  >> 1.10265190941 #(Matlab reports .1 seconds for this run) 

编辑2

我已经让椭圆的计算和绘制速度非常快(大约一秒,我没有进行详细分析),使用了PatchCollection和一些手动计算。唯一的缺点是我似乎无法将椭圆的填充设置为false。

 from matplotlib.collections import PatchCollection
 ell_list = []
 for i in range(num_chips):
     axi =  chips[i].axi 
     ax  =  dm.ax_list[axi]
     transData = dm.transData_list[axi]
     chip_feats = chips[i].features
     for feat in chip_feats:
         (x,y,a,c,d) = feat
         EllShape = Affine2D([\
            ( 1/sqrt(a),         0, x),\
            ((c/sqrt(a) - c/sqrt(d))/(a - d), 1/sqrt(d), y),\
            (         0,         0, 1)])
         unitCirc = Circle((0,0),1,transform=EllShape)
         ell_list = [unitCirc] + ell_list
    ellipses = PatchCollection(ell_list)
    ellipses.set_color([1,1,1])
    ellipses.face_color('none') #'none' gives no fill, while None will default to [0,0,1]
    ellipses.set_alpha(.05)
    ellipses.set_transformation(transData)
    ax.add_collection(ellipses)

1 个回答

0

我通过手动计算平方根倒数矩阵的形式,成功提高了绘图的效率。这真的让速度快了很多。

在上面的代码中:

 A = numpy.array( [ ( a, 0, 0 ) ,
                          ( c, d, 0 ) ,
                          ( 0, 0, 1 ) ] , dtype=np.float64)
 EllShape = Affine2D( numpy.array(sqrtm( inv(A) ), dtype=np.float64) )

被替换成了

 EllShape = Affine2D([\
 ( 1/sqrt(a),         0, 0),\
 ((c/sqrt(a) - c/sqrt(d))/(a - d), 1/sqrt(d), 0),\
 (         0,         0, 1)])

我还发现了一些有趣的时间测试结果:

  num_to_run = 100000
  all_setup  = ''' import numpy as np ; from scipy.linalg import sqrtm ; from numpy.linalg import inv ; from numpy import sqrt
  a=.1 ; c=43.2 ; d=32.343'''

  timeit( \
  'sqrtm(inv(np.array([ ( a, 0, 0 ) , ( c, d, 0 ) , ( 0, 0, 1 ) ])))',\
  setup=all_setup, number=num_to_run)
   >> 22.2588094075 #(Matlab reports 8 seconds for this run) 

  timeit(\
  '[ (1/sqrt(a), 0, 0), ((c/sqrt(a) - c/sqrt(d))/(a - d), 1/sqrt(d), 0), (0, 0, 1) ]',\
  setup=all_setup,  number=num_to_run)
  >> 1.10265190941 #(Matlab reports .1 seconds for this run) 

编辑 2

我已经让椭圆的计算和绘制速度非常快(大约一秒钟,我没有进行详细的性能分析),这是通过使用PatchCollection和一些手动计算实现的。唯一的问题是我似乎无法把椭圆的填充设置为不填充。

 from matplotlib.collections import PatchCollection
 ell_list = []
 for i in range(num_chips):
     axi =  chips[i].axi 
     ax  =  dm.ax_list[axi]
     transData = dm.transData_list[axi]
     chip_feats = chips[i].features
     for feat in chip_feats:
         (x,y,a,c,d) = feat
         EllShape = Affine2D([\
            ( 1/sqrt(a),         0, x),\
            ((c/sqrt(a) - c/sqrt(d))/(a - d), 1/sqrt(d), y),\
            (         0,         0, 1)])
         unitCirc = Circle((0,0),1,transform=EllShape)
         ell_list = [unitCirc] + ell_list
    ellipses = PatchCollection(ell_list)
    ellipses.set_color([1,1,1])
    ellipses.face_color('none') #'none' gives no fill, while None will default to [0,0,1]
    ellipses.set_alpha(.05)
    ellipses.set_transformation(transData)
    ax.add_collection(ellipses)

撰写回答