通过改进算法来优化程序性能的真实案例(Ransac)

本文通过分析RANSAC算法在图像分析领域的应用,指出其性能优化的重要性。通过简化随机抽样过程,作者实现了算法性能的显著提升,将运行时间从原始版本的24.5秒降低至20毫秒,提高了千倍之多。文中详细阐述了优化前后的代码对比,并提供了完整的RANSAC算法实现,旨在帮助程序员理解和实践性能优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

from: http://www.cnblogs.com/xiaotie/archive/2009/11/19/1605769.html


通过改进算法来优化程序性能的真实案例(Ransac)

对于运行不了几次,一次运行不了多久的方法,我们不需要考虑性能优化,对于那些需要经常运行几百次几千次的方法,我们头脑里还是要有性能这根弦。C#太优雅方便了,以至于很多人写程序时根本就把性能抛到脑后了,不愿意耗费心思去进行代码优化和算法优化,结果写出来的程序奇慢无比。不明真相的群众把这怪罪给C#语言。这不是C#的杯具,是程序员的无能。

2个月前,我研究sift(一种重要的图像分析算法)。最先找到了一个C#实现的library——libsift,这个library处理一张正常大小的图像,要耗时2-3分钟。后来,又找到一个C实现的library,处理同样的图像,耗时在1秒以内——秒杀。

昨天,我写Ransac(随机抽样一致性)算法代码时参考了libsift里的Ransac实现。不看不知道,一看吓一跳。那代码性能低下得无以复加。我随手优化了一下算法,就将随机抽样那部分的性能提高了上千倍。

下面详细道出。

一、Ransac

Ransac是用途很广泛的算法,详细介绍请看http://en.wikipedia.org/wiki/RANSAC。下面简单介绍一下(没兴趣的可以略过不看)。

我们分析世界,需要对世界建模,把世界中的现象抽象成模型。每个模型,又存在一些参数,通过调节参数,可以得到不同的实例,进行推演。我们观察现象,得到一堆数据。如何为这堆数据找一个合适的模型,再确定合适的模型参数,这是很重要的问题,是人类理性的基础。
数据分两种:有效数据(inliers)和无效数据(outliers)。那些偏差不大的数据是有效数据,偏差大的数据是无效数据。
如果有效数据占大多数,无效数据只是很少量时,我们可以通过最小二乘法或类似的方法来确定模型的参数和误差。如果无效数据很多(比如,超过了50%的数据是无效数据),最小二乘法就失效了,我们需要新的算法。

 

上图左图是观察的数据。直觉可以看出,外面的散点是outliers,中间近似分布为一直线的是inliers。怎么设计一个算法,算出这条直线,使它对inliers的拟合度较高(如上图右图所示)?

再举一个更直观的例子:

 

上图左侧是一个验证码,我们将它看作“数据”。右侧是一个字符,我们将它看作“模型”,如何通过算法去除“数据”中的outlier,剩下inliner来和“模型”进行匹配
Ransac 是解决这类问题的代表性算法。它是一种随机算法,步骤如下:

输入:k,n,t,d,model,data
BestModel = null;
迭代k次——
(1) 从data中随机取出n个点,用这n个点去拟合model和模型的model,将得到的带参数的model记为MaybeBestModel。
(2) 依次取出剩下的点,计算该点对应MaybeBestModel模型的误差,如果这个误差小于阈值t,则认为这个点是有效的,把这个点也放进MaybeBestModel中。
(3) 所有点取完了。这时,MaybeBestModel中有效点的数量是否大于或等于d,如果是,则对于MaybeBestModel,重新计算一下它的模型参数。
(4) 评估一下MaybeBestModel和BestModel哪一个好?如果MaybeBestModel更好,则将MaybeBestModel 记做新的 BestModel。

二、libsift中Ransac算法的实现

Ransac算法中,model,model的拟合,不同参数model之间的比较都是因问题不同而不同,因此,可以将model抽象成接口。将model 抽象之后,Ransac 算法的骨干就只剩下一个随机采样的过程:

迭代k次——
(1) 从data中随机抽取n个点,然后do something
(2) 依次取出剩下的点,然后do something

下面是libsift中Ransac算法的实现代码:

Code

不考虑Model部分,只考虑单次迭代过程中的随机抽样,可抽象出这样一个过程:

(1)假设数据集是points,它的类型是List<T>;
(2)从points中随机选取n个对象,放入容器samples中;
(3)依次处理剩下的对象,根据处理结果决定放入samples或不放入samples

我把libsift的Ransac代码中上述逻辑部分单独提取出来了,并作了以下简化:

(1) 直接令points是List<int>类型
(2) 处理剩下的对象时,全部决定放入samples中

代码如下:

Code

准备测试数据,进行性能测试:

Code

这个测试中假设共有10000个数据,一共进行50次迭代,每次迭代的n值为4000。用老赵的CodeTimer测量运行时间,结果为:

CaseLibSift
        Time Elapsed:   24,492ms
        CPU Cycles:     44,426,562,664
        Gen 0:          6
        Gen 1:          0
        Gen 2:          0

 24.5秒!雷人的慢!

为什么会这样呢?主要问题出在这两句中:

                    if (samples.Contains(sampleToAdd))

                     if (samples.Contains(point))

 您有更好的方案吗?

 下面是娱乐时间。娱乐之后,放上我的改进方案。

 三、娱乐

 四、我的方案

 再回顾一下问题:

(1)假设数据集是points,它的类型是List<T>;
(2)从points中随机选取n个对象,放入容器samples中;
(3)依次处理剩下的对象,根据处理结果决定放入samples或不放入samples

我采用的洗牌算法的变种。所谓洗牌问题,就是给定一个数组,编写程序将这个数组打乱。下面是一个经典的洗牌算法:

对于N个元素的数组
(1) 从N个元素中随机取出一个元素,与数组最后一个元素调换
(2) 从前N-1个元素中随机取出一个元素,与倒数第二个元素调换
(3) ……

 将上述洗牌算法稍微改变一下,就得到本文问题的答案:

对于N个元素的数组
(1) 从N个元素中随机取出一个元素,与数组第一个元素调换
(2) 从后N-1个元素中随机取出一个元素,与第二个元素调换

……
(n) 从后N-(n-1)个元素中随机取出一个元素,与第n个元素调换

这样,前n个元素就是随机取出的元素了。再考虑这样一个问题,就是n>N/2的情况,这时,n>N-n。我们不需要随机取出n个元素,只需要取出N-n个元素即可,剩下n个元素便是我们想要的随机采样结果。

 把整个算法写成了扩展方法,代码如下:

Code

 同CaseLibSift对比性能:

Code

结果为:

(1)datalenth=10000;n=1000;loops=100时的测试结果:

CaseLibSift
        Time Elapsed:   43,750ms
        CPU Cycles:     78,647,268,469
        Gen 0:          12
        Gen 1:          1
        Gen 2:          0

MyCase
        Time Elapsed:   20ms
        CPU Cycles:     29,902,543
        Gen 0:          0
        Gen 1:          0
        Gen 2:          0

 

(2)datalenth=10000;n=4000;loops=50时的测试结果:

CaseLibSift
        Time Elapsed:   24,626ms
        CPU Cycles:     44,217,626,002
        Gen 0:          6
        Gen 1:          1
        Gen 2:          0

MyCase
        Time Elapsed:   30ms
        CPU Cycles:     48,109,204
        Gen 0:          0
        Gen 1:          0
        Gen 2:          0

 对比可见,性能提高了千倍。

 下面是我的Ransac完整实现代码:

 


  1     public interface IRansacModel : ICollection<Vector>,  ICloneable
  2     {
  3         double Error { get; }
  4         void Update();
  5         bool FitPoint(Vector point);
  6         /// <summary>
  7         /// 比较IRansacModel的优劣。
  8         /// </summary>
  9         /// <param name="other"></param>
 10         /// <returns></returns>
 11         bool BestThan(IRansacModel other);
 12     }
 13 
 14     public abstract class RansacModelBase : List<Vector>, IRansacModel
 15     {
 16         public double Error { getprivate set; }
 17 
 18         public RansacModelBase():base()
 19         { }
 20 
 21         public RansacModelBase(int capacity):base(capacity)
 22         { }
 23 
 24         public abstract void Update();
 25 
 26         public abstract bool FitPoint(Vector point);
 27 
 28         protected void CloneBaseFrom(RansacModelBase other)
 29         {
 30             this.Error = other.Error;
 31             this.Clear();
 32             this.AddRange(other);
 33         }
 34 
 35         /// <summary>
 36         /// 比较IRansacModel的优劣。
 37         /// 默认情况下比较两者的 Error,Error 小则认为较优。
 38         /// </summary>
 39         /// <param name="other"></param>
 40         /// <returns></returns>
 41         public virtual bool BestThan(IRansacModel other)
 42         {
 43             return this.Error < other.Error;
 44         }
 45 
 46         #region ICloneable Members
 47 
 48         public abstract object Clone();
 49 
 50         #endregion
 51 
 52     }
 53 
 54     public class Ransac<TModel> where TModel : IRansacModel
 55     {
 56         private int m_minNumberFitted;
 57         private TModel m_model;
 58         private Random m_rand = new Random();
 59         private int m_iteration;
 60 
 61         private Ransac()
 62         {
 63         }
 64 
 65         public Ransac(TModel model, int minNumberFitted, int iteration)
 66         {
 67             this.m_minNumberFitted = minNumberFitted;
 68             this.m_iteration = iteration;
 69             m_model = model;
 70         }
 71 
 72         public TModel Match(IList<Vector> points, int d)
 73         {
 74             if (points.Count < m_minNumberFitted) return default(TModel);
 75 
 76             TModel bestModel = default(TModel);
 77 
 78             for (int ki = 0; ki < m_iteration; ++ki)
 79             {
 80                 TModel tmpModel = (TModel)this.m_model.Clone();
 81 
 82                 // 随机采样
 83                 ListSegment<Vector> v = points.RandomSampleSplitOnSite(m_minNumberFitted);
 84 
 85                 for (int i = v.Start; i < v.End; i++)
 86                 {
 87                     tmpModel.Add(points[i]);
 88                 }
 89 
 90                 tmpModel.Update();
 91 
 92                 IList<Vector> good = new List<Vector>();
 93 
 94                 // Check all non-sample points for fit.
 95                 for (int i = v.End; i < points.Count; i++)
 96                 {
 97                     Vector point = points[i];
 98                     if (tmpModel.FitPoint(point) == true) tmpModel.Add(point);
 99                 }
100 
101                 if (tmpModel.Count >= d)
102                 {
103                     tmpModel.Update();
104                     if (bestModel == null) bestModel = tmpModel;
105                     else if (tmpModel.BestThan(bestModel)) bestModel = tmpModel;
106                 }
107             }
108             return (bestModel);
109         }
110     }

 

若标题中有“转载”字样,则本文版权归原作者所有。若无转载字样,本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
4
0
(请您对文章做出评价)
« 上一篇: 图像处理/识别/检索的几个开放性问题
» 下一篇: 汗一下,.Net的单维数组自动实现IList<T>接口
posted @ 2009-11-19 05:38 xiaotie 阅读( 5393) 评论( 28) 编辑 收藏
  
#1楼 2009-11-19 07:38 徐少侠  
霍霍

其实最基本的N个数里取M个,也就是福利彩票那种36选7的

最原始的写法效率就是很低,整体循环次数不定的.
要如同你上面加以分析后就能优化成固定循环次数的算法

算法的优化,一个主要的说法就是解题步骤中是否存在过多的不确定分支,裁减之
  
#2楼 2009-11-19 09:44 CodeYu  
好文。


  
#3楼 2009-11-19 09:50 YJJ  
图比较邪恶...
  
#4楼 2009-11-19 10:01 Red_angelX  
好文,收藏一个
  
#5楼 2009-11-19 10:10 polar  
引用 YJJ:图比较邪恶...

  
#6楼 2009-11-19 10:12 Done  
我是来看图的
  
#7楼 2009-11-19 10:25 runex[未注册用户]
@YJJ
汗 我也有同感...
  
#8楼 2009-11-19 10:26 MSFT:waywa 韦恩卑鄙  
离不开糨糊~
  
#9楼 2009-11-19 11:25 Vincent Zhou  
引用 polar:
引用YJJ:图比较邪恶...


  
#10楼 2009-11-19 12:58 wispzone  
灰常邪恶。。。。
  
#11楼 2009-11-19 14:23 Yunanw  
汗一下图
  
#12楼 2009-11-19 15:52 Daniel Cai  
引用 Vincent Zhou:
引用polar:
引用YJJ:图比较邪恶...



  
#13楼 2009-11-19 16:16 远哥  
这么好的文章,一定要支持一下
  
#14楼 2009-11-19 16:28 Beggar  
图让我不敢在公司看。。。
  
#15楼 2009-11-19 16:39 changyonglan  
LZ说点简单点的吧,这些都太深奥了哦!
  
#16楼 [ 楼主] 2009-11-19 18:16 xiaotie  
  
#17楼 2009-11-19 18:43 ode  
园子是应该多一些讲算法的文章了。好文章。
  
#18楼 2010-06-25 09:35 西林  
楼主能分享一下你改进的sift算法吗,或者那个秒杀的library
  
#19楼 [ 楼主] 2010-06-27 21:40 xiaotie  
@西林
抱歉,最近不喜欢分享代码了。
  
#20楼 2010-10-09 21:23 borrows  
楼主真是分析的好啊
  
#21楼 2010-11-14 19:37 编著人  
疑问
我看了一下你的文章,感觉大意是:从N中取n = 从N取(N-n)?
得到简化算法,不知道我理解的是否有偏差?

如果是这样的话,取n与取N-n得到的最优RANSAC模型未必相等啊?
  
#22楼 [ 楼主] 2010-11-15 11:01 xiaotie  
@编者
从N中取n 和 从N取(N-n) 是同一个问题,都是把一个集合随机分成 n 和 N - n 两部分。
  
#23楼 2010-11-15 16:08 编著人  
@xiaotie
博主:还有第2问没回答呢?

那我理解的就没错了。
从N中取n 和 从N取(N-n) 是在排列组合上同一个问题!

可是如果取到的n与(N-n)不相等,那么得到的最优RANSAC模型未必相同啊???
  
#24楼 [ 楼主] 2010-11-15 20:06 xiaotie  
@编著人
RandomSampleSplitOnSite 方法?
两个分支都是从N中取n。
  
#25楼 2010-11-15 22:48 编著人  
我感觉你没有明白我什么意思!
这样讨论很费劲,刚刚看了一下RANSAC~
我现在对这个问题也不是很感兴趣了

有机会再讨论吧!
  
#26楼 2010-11-17 18:28 编著人  

你的+code,能展开的代码做的很酷;
还输入密码才能看的文章!

我是菜鸟,能不能教教我啊?
说得详细点啊~我没基础

希望楼主不吝赐教!
  
#27楼 [ 楼主] 2010-11-18 00:07 xiaotie  
@编著人
?
输入密码才能看的不看也罢,后面的文章都覆盖了
  
#28楼 2012-08-23 09:58 钟卡尔  
我也来看图的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值