• Open Source Computer Vision Library

CvImage中的陷阱和BUG

Wikipedia,自由的百科全书

目录

CvImage类的定义

  1. class CV_EXPORTS CvImage
  2. {
  3. public:
  4.  
  5. CvImage() : image(0), refcount(0) {}
  6.  
  7. CvImage( CvSize size, int depth, int channels )
  8. {
  9. image = cvCreateImage( size, depth, channels );
  10. refcount = image ? new int(1) : 0;
  11. }
  12.  
  13. CvImage( IplImage* img ) : image(img)
  14. {
  15. refcount = image ? new int(1) : 0;
  16. }
  17.  
  18. ~CvImage()
  19. {
  20. if( refcount && !(--*refcount) )
  21. {
  22. cvReleaseImage( &image );
  23. delete refcount;
  24. }
  25. }
  26.  
  27. void attach( IplImage* img, bool use_refcount=true )
  28. {
  29. if( refcount )
  30. {
  31. if( --*refcount == 0 )
  32. cvReleaseImage( &image );
  33. delete refcount;
  34. }
  35. image = img;
  36. refcount = use_refcount && image ? new int(1) : 0;
  37. }
  38.  
  39. void detach()
  40. {
  41. if( refcount )
  42. {
  43. if( --*refcount == 0 )
  44. cvReleaseImage( &image );
  45. delete refcount;
  46. refcount = 0;
  47. }
  48. image = 0;
  49. }
  50.  
  51.  
  52. CvImage& operator = (const CvImage& img)
  53. {
  54. if( img.refcount )
  55. ++*img.refcount;
  56. if( refcount && !(--*refcount) )
  57. cvReleaseImage( &image );
  58. image=img.image;
  59. refcount=img.refcount;
  60. return *this;
  61. }
  62.  
  63. protected:
  64. IplImage* image; // 实际影象
  65. int* refcount; // 引用计数
  66. };

CvImage类的相关代码在以下位置:

OpenCV\cxcore\include\cxcore.hpp
OpenCV\cxcore\src\cximage.cpp

这里给出的只是部分函数。

为了提高效率,CvImage采用的是引用计数。不过目前的CvImage实现中,引用计数机制存在bug。

关于引用计数

引用计数应该也可以叫写时复制技术。就是在复制一个数据时,先只是简单地复制数据的 指针(地址),只有在数据被修改的时候才真的进行数据的复制操作。写时复制技术对用户 是透明的,也就是说用户可以当作数据是真的复制了。

一般数据(或者是文件,类等)都会对应创建/销毁操作。因此,采用写时复制技术的数据 一般还对应一个计数,记录该数据被别人引用的次数。数据在第一次被创建的时候被设置为1, 以后每次被重复创建则增加1,如果是被销毁则减少1。再销毁数据减少引用计数的时候,如果 记录变为0则真的执行删除数据操作,否则的话只执行逻辑删除。

这里需要注意的一点是,每个引用计数和它对应的数据是绑定的。因此,任何一个引用计数都 不应该独立于数据存在。

CvImage中的引用计数机制

  1. class CV_EXPORTS CvImage
  2. {
  3. IplImage* image;
  4. int* refcount;
  5. };

image指向影像数据的地址,refcount指向影像数据对应的引用计数的地址。需要强调的一点是, refcount指向的引用计数并不属于哪个类,而是属于image指向影像数据!任何将影像数据 和其对应的引用计数分离的操作都是错误的。

CvImage(IplImage* img)陷阱

假设有下面一个段代码:

  1. IplImage *pIplImg = cvLoadImage("load.tiff");
  2. {
  3. CvImage cvImg(pIplImg);
  4. }
  5. cvSaveImage("save.tiff", pIplImg);

虽然逻辑上好像没有错误,但再执行到cvSaveImage语句的时候却会产生异常!跟踪调试后发现, 原来pIplImg对应的数据在cvImg析构的时候被释放了!

仔细分析后会发现,CvImage将pIplImg对应的数据和它本身的refcount绑定到一起了。pIplImg 对应的数据虽然不属于CvImage,但是它却依据refcount对其进行管理,直到(*refcount)变为0 的时候私自释放了pIplImg影像。

对于这个问题,我不建议使用引用计数,因此可以将代码修改为:

  1. CvImage( IplImage* img, bool use_refcount=false) : image(img)
  2. {
  3. refcount = use_refcount && image ? new int(1) : 0;
  4. }

在默认的时候不使用引用计数机制,用户自己维护img内存空间。

attach问题

  1. void attach( IplImage* img, bool use_refcount=true )
  2. {
  3. if( refcount )
  4. {
  5. if( --*refcount == 0 )
  6. cvReleaseImage( &image );
  7. delete refcount;
  8. }
  9. image = img;
  10. refcount = use_refcount && image ? new int(1) : 0;
  11. }

attach是将一个IplImage影像绑定到CvImage。其中的一个陷阱和前面的CvImage类似:

  1. IplImage *pIplImg = cvLoadImage("load.tiff");
  2. {
  3. CvImage cvImg;
  4. cvImg.attach(pIplImg);
  5. }
  6. cvSaveImage("save.tiff", pIplImg); // 异常

处理是方法是把参数use_refcount的默认值改为false。

除了和CvImage类型的陷阱外,attach本身还有一个bug!前面我们分析过,CvImage类中 refcount指向的空间和image指向的空间是绑在一起的。因此,if( --*refcount == 0 ) 语句中将cvReleaseImage( &image )和delete refcount分离的操作肯定是错误的!!

假设有以下代码:

  1. IplImage *pIplImg = cvLoadImage("load.tiff");
  2. {
  3. CvImage cvImg;
  4. cvImg.create(cvSize(600,400), 8, 1); // 创建一个600*400的单字节单通道影像
  5.  
  6. CvImage cvImgX(cvImg); // 由cvImg拷贝构造cvImgX
  7. cvImgX.attach(pIplImg);
  8. }
  9. cvSaveImage("save.tiff", pIplImg);

代码将在执行完cvImgX.attach(pIplImg)语句后发生异常!

分析代码可以发现,cvImg.create先创建了一个影像,同时影像还对应一个引用计数。由于cvImgX 是有cvImg拷贝构造得到,因此cvImgX也保存了和cvImg一样的image和refcount。在接着执行的 attach中,cvImgX将refcount指向的空间释放(delete refcount)。注意,cvImgX和cvImg的refcount 对应同一个空间!!那么在,cvImg退出花括弧执行析构函数的时候,delete refcount语句就非法了!

修改bug后的attach代码:

  1. void attach( IplImage* img, bool use_refcount=false ) // use_refcount默认值没有修改
  2. {
  3. if( refcount )
  4. {
  5. if( --*refcount == 0 )
  6. {
  7. // 同时释放
  8.  
  9. cvReleaseImage( &image );
  10. delete refcount;
  11. }
  12. }
  13. image = img;
  14. refcount = use_refcount && image ? new int(1) : 0;
  15. }

由于CvImage中的许多函数都基于attach实现,因此没有修改use_refcount的默认值。detach中的问题 和attach相似,代码修改如下:

  1. void detach()
  2. {
  3. if( refcount )
  4. {
  5. if( --*refcount == 0 )
  6. {
  7. // 同时释放
  8.  
  9. cvReleaseImage( &image );
  10. delete refcount;
  11. }
  12. refcount = 0;
  13. }
  14. image = 0;
  15. }

重载操作符“=”时的内存泄漏

  1. CvImage& operator = (const CvImage& img)
  2. {
  3. if( img.refcount )
  4. ++*img.refcount;
  5. if( refcount && !(--*refcount) )
  6. cvReleaseImage( &image );
  7. image=img.image;
  8. refcount=img.refcount;
  9. return *this;
  10. }

假设有以下类似代码:

CvImage cvImg1, cvImg2;

cvImg1.create(cvSize(600,400), 8, 1);
cvImg2.create(cvSize(800,500), 8, 1);

cvImg1 = cvImg2;

虽然看着很清晰,但是该代码却存在内存泄漏!分析如下:

cvImg1先创建一个(600,400)大小的影像,默认还对应一个引用计数(refcount指向的空间)。cvImg2 也采用同样的方式创建一个类似的影像。注意:cvImg1和cvImg1中refcount指向的空间是不同的!!

下面执行“=”操作时,cvImg1的image空间被释放(cvReleaseImage( &image )),但是cvImg1的 refcount指向的空间却没有释放!然后,cvImg1的refcount指向了cvImg2的refcount。这样,cvImg1 的refcount指向内存就丢失了!

修改后的代码:

  1. CvImage& operator = (const CvImage& img)
  2. {
  3. if( img.refcount )
  4. ++*img.refcount;
  5. if( refcount && !(--*refcount) )
  6. {
  7. cvReleaseImage( &image );
  8.  
  9. // 释放refcount
  10.  
  11. delete refcount;
  12. }
  13. image=img.image;
  14. refcount=img.refcount;
  15. return *this;
  16. }

小节

虽然讲了这么多关于CvImage的陷阱和bug,单主要目的还是为了更好地使用CvImage。这里给出一个 建议:

在将IplImage数据和CvImage进行绑定,或者是基于IplImage数据构造CvImage对象的时候要清楚是否需要使用CvImage的引用计数技术(有哪些好处/坏处)

特别是attach默认是采用引用计数的(没改的理由前面已经说明)

附:修复的CvImage

  1. class CV_EXPORTS CvImage
  2. {
  3. public:
  4.  
  5. CvImage() : image(0), refcount(0) {}
  6.  
  7. CvImage( CvSize size, int depth, int channels )
  8. {
  9. image = cvCreateImage( size, depth, channels );
  10. refcount = image ? new int(1) : 0;
  11. }
  12.  
  13. // 修改
  14.  
  15. CvImage( IplImage* img, bool use_refcount=false) : image(img)
  16. {
  17. refcount = use_refcount && image ? new int(1) : 0;
  18. }
  19.  
  20. ~CvImage()
  21. {
  22. if( refcount && !(--*refcount) )
  23. {
  24. cvReleaseImage( &image );
  25. delete refcount;
  26. }
  27. }
  28.  
  29. // 修改
  30.  
  31. void attach( IplImage* img, bool use_refcount=false ) // use_refcount默认值没有修改
  32. {
  33. if( refcount )
  34. {
  35. if( --*refcount == 0 )
  36. {
  37. // 同时释放
  38.  
  39. cvReleaseImage( &image );
  40. delete refcount;
  41. }
  42. }
  43. image = img;
  44. refcount = use_refcount && image ? new int(1) : 0;
  45. }
  46.  
  47. // 修改
  48.  
  49. void detach()
  50. {
  51. if( refcount )
  52. {
  53. if( --*refcount == 0 )
  54. {
  55. // 同时释放
  56.  
  57. cvReleaseImage( &image );
  58. delete refcount;
  59. }
  60. refcount = 0;
  61. }
  62. image = 0;
  63. }
  64.  
  65. // 修改
  66.  
  67. CvImage& operator = (const CvImage& img)
  68. {
  69. if( img.refcount )
  70. ++*img.refcount;
  71.  
  72. if( refcount && !(--*refcount) )
  73. {
  74. cvReleaseImage( &image );
  75.  
  76. // 释放refcount
  77.  
  78. delete refcount;
  79. }
  80.  
  81. image=img.image;
  82. refcount=img.refcount;
  83. return *this;
  84. }
  85.  
  86. protected:
  87. IplImage* image; // 实际影象
  88. int* refcount; // 引用计数
  89. };

相关页面

编写者

Views
Personal tools