OpenCV 功能强大,但在初次使用时有若干注意事项,现记下备忘。
M1. 在没有 MFC 的Win32程序中,highGUI可以很好的工作。只是注意“编译选项/代码生成/运行时库”必须选择为多线程或单线程的DLL。否则 DEBUG版可能会报heap Error。因为通过 CvImage.load 调用生成的对象是由highGUI的动态库调用动态运行时库分配的内存,而CvImage在析构的时候用的是静态运行时库。所以Debug会报试图释放不同堆分配的内存。若编译选项使用DLL的运行时库,则不再有问题。
M2. 当编译使用MFC的OpenCV应用程序,必须使用静态链接的MFC。若使用了动态连接的MFC,则Debug版在程序退出时可能会报告大量的内存泄漏。测试表明,只要调用了 CvImage.load,内存泄漏必然出现。而静态连接MFC导致运行时库也必须选用静态的,这又和M1的结论相冲突。
M1和M2的焦点都集中到了highGUI 库上。然而,创建一个使用MFC的OpenCV更有现实意义,所以应该讨论一下解决方案和原因。
我并不想花时间研究OpenCV的源码,以解决动态链接MFC时可能导致内存泄漏的原因,而是选择了一种偷懒的方式,那就是逃避。因此,方案的第一步就是使用静态连接的MFC。由于使用了静态连接的MFC,就不得不使用静态运行时库,这就不得不解决M1提到的问题。我方案的第二步是避免调用 CvImage.load 函数。下面解释一下为什么不使用CvImage.load就可以解决问题。
从源码可知,CvImage.load是将各种格式的图像文件装载到CvImage的内存对象的函数。load的实现是通过函数指针运行的。而函数指针的值由运行时动态指定,例如 load 一个 *.bmp 图像,其实际的代码由highGUI动态库提供,因而内存也由highGUI动态库的代码在运行时分配。而CvImage在析构的时候却没有区分它内部包裹的 IplImage 的内存是由哪个模块分配的。所以,只要使用了 CvImage.load,那么对应的内部 IplImage 在析构的时候就可能出问题。可见只要避免调用CvImage.load, 使用静态运行时库和静态MFC就没有问题。
既然不使用CvImage.load了,如何读取不同格式的图像文件到CvImage呢? 我方案的第三步就是使用 FreeImage 库。FreeImage 可以识别大部分常用图像格式,并将数据以 BITMAP 的数据结构保存,结合 OpenCV中文官网上提供的"BMP与IplImage相互转换"的代码构建CvImage。这样就解决了 M1。
此外,由于CvImage.save 方法对于图像格式的支持也很有限,因此将 FreeImage 引入进来还可以丰富save的图像格式。
总结一下:
1. 从 CvImage 继承一个新类 CvImg
2. 重载 load,并用 FreeImage 实现数据的读取。
内部使用OpenCV中文官网上提供的"BMP与IplImage相互转换"的代码中的 CopyData 函数即可。
3. 重载 save, 用 FreeImage 实现
4. 使用静态运行时库和静态MFC
注意,这样继承只是为了节省代码,在以后的使用中必须保持用 CvImg 完全替代 CvImage。
因为 CvImage 中的 load和save都不是虚函数。