各種圖像處理類庫的比較及選擇(The Comparison of Image Processing Libraries)

29 篇文章 0 订阅
13 篇文章 0 订阅

http://www.cnblogs.com/djlzxzy/archive/2010/06/15/1758564.html

前言

近期需要做一些圖像處理方面的學習和研究,首要任務就是選擇一套合適的圖像處理類庫。目前較知名且功能完善的圖像處理類庫有OpenCvEmguCvAForge.net等等。本文將從許可協議、下載、安裝、文檔資料、易用性、性能等方面對這些類庫進行比較,然後給出選擇建議,當然也包括我自己的選擇。

 

許可協議

類庫 許可協議 許可協議網址 大致介紹
OpenCv BSD www.opensource.org/licenses/bsd-license.html 在保留原來BSD協議聲明的前提下,隨便怎麼用都行
EmguCv GPL v3 http://www.gnu.org/licenses/gpl-3.0.txt 你的產品必須也使用GPL協議,開源且免費
商業授權 http://www.emgu.com/wiki/files/CommercialLicense.txt 給錢之後可以用於閉源的商業產品
AForge.net LGPL v3 http://www.gnu.org/licenses/lgpl.html 如果不修改類庫源代碼,引用該類庫的產品可以閉源和(或)收費

以上三種類庫都可以用於開發商業產品,但是EmguCv需要付費;因為我只是用來學習和研究,所以這些許可協議對我無所謂。不過鑑於我們身在中國,如果臉皮厚點,去他丫的許可協議。

 

下載

可以很方便的下載到這些類庫,下載地址分別為:

類庫

下載地址

OpenCv

http://sourceforge.net/projects/opencvlibrary/files/

EmguCv

http://www.emgu.com/wiki/index.php/Download_And_Installation

AForge.net

http://www.aforgenet.com/framework/downloads.html

 

安裝

這些類庫的安裝都比較簡單,直接運行安裝程序,並點「下一步」即可完成。但是OpenCv在安裝完之後還需要一些額外的處理才能在VS2008裡面使用,在http://www.opencv.org.cn有一篇名為《VC2008 Express下安裝OpenCv 2.0》的文章專門介紹了如何安裝OpenCv

類庫

安裝難易度

備注

OpenCv

比較容易

VC下使用需要重新編譯

EmguCv

容易

 

AForge.net

容易

 

相信看這篇文章的人都不會被安裝困擾。

 

文檔資料 

類庫

總體評價

書籍

網站

文檔

示例

社區

備注

OpenCv

中等

中英文

中英文

中英文

較多

中文論壇

有中文資料但不完整

EmguCv

英文

英文

英文論壇

論壇人氣很差

AForge.net

英文

英文

英文論壇

論壇人氣很差

OpenCv有一些中文資料,另外兩種的資料全是英文的;不過EmguCv建立在OpenCv的基礎上,大部分OpenCv的資料可以用於EmguCv;而AForge.net是原生的.net類庫,對GDI+有很多擴展,一些MSDN的資料可以借鑑。如果在查詞典的基礎上還看不懂英文文檔,基本上可以放棄使用這些類庫了。

 

易用性

易用性這玩意,主觀意志和個人能力對它影響很大,下面是我的看法:

類庫

易用性

備注

OpenCv

比較差

OpenCv大多數功能都以C風格函數形式提供,少部分功能以C++類提供。注意:2.0版將更多的功能封裝成類了。

EmguCv

比較好

OpenCv的絕大部分功能都包裝成了.net類、結構或者枚舉。不過文檔不全,還是得對照OpenCv的文檔去看才行。

AForge.net

.net類庫,用起來很方便。

最近幾年一直用的是C# ,把CC++忘記得差不多了,況且本來C/C++我就不太熟,所以對OpenCv的看法恐怕有偏見。

 

性能

這些類庫能做的事情很多,我選了最基礎的部分來進行性能測試,那就是將一幅彩色圖像轉換成灰度圖,然後再將灰度圖轉換成二值圖像。因為圖像處理大部分時間都用於內存讀寫及運算(特別是矩陣運算),所以這兩種操作有一定的代表性。

我分別用以下方式實現了圖像的灰度化及二值化:(1C語言調用OpenCv庫;(2C#調用AForge.net庫;(3C#調用EmguCv庫;(4C#中用P/INVOKE的形式調用OpenCv函數;(5C#調用自己寫的灰度和二值化方法。

C语言调用OpenCv
#include "stdafx.h"
#include "cv.h"
#include "cxcore.h"
#include "highgui.h"
#include "winbase.h"

int _tmain(int argc, _TCHAR* argv[])
{
    //初始化图像
    IplImage * pIplSource=cvLoadImage("E:\\xrwang\\ImageProcessLearn\\Debug\\wky_tms_2272x1704.jpg");
    IplImage * pIplGrayscale=cvCreateImage(cvSize(pIplSource->width,pIplSource->height),IPL_DEPTH_8U,1);
    IplImage * pIplThreshold=cvCreateImage(cvSize(pIplSource->width,pIplSource->height),IPL_DEPTH_8U,1);
    //执行灰度化和二值化,并输出所用时间
    LARGE_INTEGER frequency,count1,count2,count3;
    double time1,time2;
    QueryPerformanceFrequency(&frequency);
    for(int i=0;i<10;i++)
    {
        QueryPerformanceCounter(&count1);
        cvCvtColor(pIplSource,pIplGrayscale,CV_BGR2GRAY);
        QueryPerformanceCounter(&count2);
        cvThreshold(pIplGrayscale,pIplThreshold,128,255,CV_THRESH_BINARY);
        QueryPerformanceCounter(&count3);
        time1=(double)1000.0*(count2.QuadPart-count1.QuadPart)/frequency.QuadPart;
        time2=(double)1000.0*(count3.QuadPart-count2.QuadPart)/frequency.QuadPart;
        printf("灰度:%g毫秒,二值化:%g毫秒\r\n",time1,time2);
    }
    //显示图像
    cvNamedWindow("grayscale",0);
    cvNamedWindow("threshold",0);
    cvResizeWindow("grayscale",600,480);
    cvResizeWindow("threshold",600,480);
    cvShowImage("grayscale",pIplGrayscale);
    cvShowImage("threshold",pIplThreshold);
    cvWaitKey(0);
    //销毁对象
    cvDestroyAllWindows();
    cvReleaseImage(&pIplThreshold);
    cvReleaseImage(&pIplGrayscale);
    cvReleaseImage(&pIplSource);
    return 0;
}

C#调用各种类库处理图像

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
using AForge.Imaging.Filters;
using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.CV.CvEnum;

namespace ImageProcessLearn
{
    public partial class FormMain : Form
    {
        public FormMain()
        {
            InitializeComponent();
        }

        //窗体加载时
        private void FormMain_Load(object sender, EventArgs e)
        {
            //显示原始图像
            pbSource.Image = Image.FromFile("wky_tms_2272x1704.jpg");
        }

        //使用选定的类库处理图像
        private void btnProcess_Click(object sender, EventArgs e)
        {
            if (rbAForge.Checked)
            {
                ProcessImageWithAforge();
            }
            else if (rbEmgucv.Checked)
            {
                ProcessImageWithEmgucv();
            }
            else if (rbOpencv.Checked)
            {
                ProcessImageWithOpencv();
            }
            else if (rbOwnMethod.Checked)
                ProcessImageWithOwnMethod();
        }

        /// <summary>
        /// 使用AForge.net处理图像
        /// </summary>
        private void ProcessImageWithAforge()
        {
            Stopwatch sw = new Stopwatch(); //计时器
            //灰度
            sw.Start();
            Grayscale grayscaleFilter = new Grayscale(0.299, 0.587, 0.114);
            Bitmap bitmapGrayscale = grayscaleFilter.Apply((Bitmap)pbSource.Image);
            sw.Stop();
            double timeGrayscale = sw.Elapsed.TotalMilliseconds;
            if (pbGrayscale.Image != null)
            {
                pbGrayscale.Image.Dispose();
                pbGrayscale.Image = null;
            }
            pbGrayscale.Image = bitmapGrayscale;
            //二值化
            sw.Reset();
            sw.Start();
            Threshold thresholdFilter = new Threshold(128);
            Bitmap bitmapThreshold = thresholdFilter.Apply(bitmapGrayscale);
            sw.Stop();
            double timeThreshold = sw.Elapsed.TotalMilliseconds;
            if (pbThreshold.Image != null)
            {
                pbThreshold.Image.Dispose();
                pbThreshold.Image = null;
            }
            pbThreshold.Image = bitmapThreshold;
            //输出所用时间
            txtResult.Text += string.Format("类库:AForge.net,灰度:{0:F05}毫秒,二值化:{1:F05}毫秒\r\n", timeGrayscale, timeThreshold);
        }

        /// <summary>
        /// 使用EmguCv处理图像
        /// </summary>
        private void ProcessImageWithEmgucv()
        {
            Stopwatch sw = new Stopwatch(); //计时器
            //灰度
            Image<Bgr, Byte> imageSource = new Image<Bgr, byte>((Bitmap)pbSource.Image);
            sw.Start();
            Image<Gray, Byte> imageGrayscale = imageSource.Convert<Gray, Byte>();
            sw.Stop();
            double timeGrayscale = sw.Elapsed.TotalMilliseconds;
            if (pbGrayscale.Image != null)
            {
                pbGrayscale.Image.Dispose();
                pbGrayscale.Image = null;
            }
            pbGrayscale.Image = imageGrayscale.ToBitmap();
            //二值化
            sw.Reset();
            sw.Start();
            Image<Gray, Byte> imageThreshold = imageGrayscale.ThresholdBinary(new Gray(128), new Gray(255));
            sw.Stop();
            double timeThreshold = sw.Elapsed.TotalMilliseconds;
            if (pbThreshold.Image != null)
            {
                pbThreshold.Image.Dispose();
                pbThreshold.Image = null;
            }
            pbThreshold.Image = imageThreshold.ToBitmap();
            //输出所用时间
            txtResult.Text += string.Format("类库:EmguCv,灰度:{0:F05}毫秒,二值化:{1:F05}毫秒\r\n", timeGrayscale, timeThreshold);
        }

        /// <summary>
        /// 使用Open Cv P/Invoke处理图像
        /// </summary>
        unsafe private void ProcessImageWithOpencv()
        {
            Stopwatch sw = new Stopwatch(); //计时器
            //灰度
            Image<Bgr, Byte> imageSource = new Image<Bgr, byte>((Bitmap)pbSource.Image);
            IntPtr ptrSource = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MIplImage)));
            Marshal.StructureToPtr(imageSource.MIplImage, ptrSource, true);
            sw.Start();
            IntPtr ptrGrayscale = CvInvoke.cvCreateImage(imageSource.Size, IPL_DEPTH.IPL_DEPTH_8U, 1);
            CvInvoke.cvCvtColor(ptrSource, ptrGrayscale, COLOR_CONVERSION.CV_BGR2GRAY);
            sw.Stop();
            double timeGrayscale = sw.Elapsed.TotalMilliseconds;
            if (pbGrayscale.Image != null)
            {
                pbGrayscale.Image.Dispose();
                pbGrayscale.Image = null;
            }
            pbGrayscale.Image = ImageConverter.IplImagePointerToBitmap(ptrGrayscale);
            //二值化
            sw.Reset();
            sw.Start();
            IntPtr ptrThreshold = CvInvoke.cvCreateImage(imageSource.Size, IPL_DEPTH.IPL_DEPTH_8U, 1);
            CvInvoke.cvThreshold(ptrGrayscale, ptrThreshold, 128d, 255d, THRESH.CV_THRESH_BINARY);
            sw.Stop();
            double timeThreshold = sw.Elapsed.TotalMilliseconds;
            if (pbThreshold.Image != null)
            {
                pbThreshold.Image.Dispose();
                pbThreshold.Image = null;
            }
            pbThreshold.Image = ImageConverter.IplImagePointerToBitmap(ptrThreshold);
            //释放资源
            //CvInvoke.cvReleaseImage(ref ptrThreshold);
            //CvInvoke.cvReleaseImage(ref ptrGrayscale);
            Marshal.FreeHGlobal(ptrSource);
            //输出所用时间
            txtResult.Text += string.Format("类库:OpenCv P/Invoke,灰度:{0:F05}毫秒,二值化:{1:F05}毫秒\r\n", timeGrayscale, timeThreshold);
        }

        /// <summary>
        /// 使用自定义的方法处理图像
        /// </summary>
        private void ProcessImageWithOwnMethod()
        {
            Stopwatch sw = new Stopwatch(); //计时器
            //灰度
            sw.Start();
            Bitmap bitmapGrayscale = Grayscale((Bitmap)pbSource.Image);
            sw.Stop();
            double timeGrayscale = sw.Elapsed.TotalMilliseconds;
            if (pbGrayscale.Image != null)
            {
                pbGrayscale.Image.Dispose();
                pbGrayscale.Image = null;
            }
            pbGrayscale.Image = bitmapGrayscale;
            //二值化
            sw.Reset();
            sw.Start();
            Bitmap bitmapThreshold = Threshold(bitmapGrayscale, 128);
            sw.Stop();
            double timeThreshold = sw.Elapsed.TotalMilliseconds;
            if (pbThreshold.Image != null)
            {
                pbThreshold.Image.Dispose();
                pbThreshold.Image = null;
            }
            pbThreshold.Image = bitmapThreshold;
            //输出所用时间
            txtResult.Text += string.Format("类库:自定义方法,灰度:{0:F05}毫秒,二值化:{1:F05}毫秒\r\n", timeGrayscale, timeThreshold);
        }

        /// <summary>
        /// 将指定图像转换成灰度图
        /// </summary>
        /// <param name="bitmapSource">源图像支持3通道或者4通道图像,支持Format24bppRgb、Format32bppRgb和Format32bppArgb这3种像素格式</param>
        /// <returns>返回灰度图,如果转化失败,返回null。</returns>
        private Bitmap Grayscale(Bitmap bitmapSource)
        {
            Bitmap bitmapGrayscale = null;
            if (bitmapSource != null && (bitmapSource.PixelFormat == PixelFormat.Format24bppRgb || bitmapSource.PixelFormat == PixelFormat.Format32bppArgb || bitmapSource.PixelFormat == PixelFormat.Format32bppRgb))
            {
                int width = bitmapSource.Width;
                int height = bitmapSource.Height;
                Rectangle rect = new Rectangle(0, 0, width, height);
                bitmapGrayscale = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
                //设置调色板
                ColorPalette palette = bitmapGrayscale.Palette;
                for (int i = 0; i < palette.Entries.Length; i++)
                    palette.Entries[i] = Color.FromArgb(255, i, i, i);
                bitmapGrayscale.Palette = palette;
                BitmapData dataSource = bitmapSource.LockBits(rect, ImageLockMode.ReadOnly, bitmapSource.PixelFormat);
                BitmapData dataGrayscale = bitmapGrayscale.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
                byte b, g, r;
                int strideSource = dataSource.Stride;
                int strideGrayscale = dataGrayscale.Stride;
                unsafe
                {
                    byte* ptrSource = (byte*)dataSource.Scan0.ToPointer();
                    byte* ptr1;
                    byte* ptrGrayscale = (byte*)dataGrayscale.Scan0.ToPointer();
                    byte* ptr2;
                    if (bitmapSource.PixelFormat == PixelFormat.Format24bppRgb)
                    {
                        for (int row = 0; row < height; row++)
                        {
                            ptr1 = ptrSource + strideSource * row;
                            ptr2 = ptrGrayscale + strideGrayscale * row;
                            for (int col = 0; col < width; col++)
                            {
                                b = *ptr1;
                                ptr1++;
                                g = *ptr1;
                                ptr1++;
                                r = *ptr1;
                                ptr1++;
                                *ptr2 = (byte)(0.114 * b + 0.587 * g + 0.299 * r);
                                ptr2++;
                            }
                        }
                    }
                    else    //bitmapSource.PixelFormat == PixelFormat.Format32bppArgb || bitmapSource.PixelFormat == PixelFormat.Format32bppRgb
                    {
                        for (int row = 0; row < height; row++)
                        {
                            ptr1 = ptrSource + strideGrayscale * row;
                            ptr2 = ptrGrayscale + strideGrayscale * row;
                            for (int col = 0; col < width; col++)
                            {
                                b = *ptr1;
                                ptr1++;
                                g = *ptr1;
                                ptr1++;
                                r = *ptr1;
                                ptr1 += 2;
                                *ptr2 = (byte)(0.114 * b + 0.587 * g + 0.299 * r);
                                ptr2++;
                            }
                        }
                    }
                }
                bitmapGrayscale.UnlockBits(dataGrayscale);
                bitmapSource.UnlockBits(dataSource);
            }
            return bitmapGrayscale;
        }

        /// <summary>
        /// 将指定的灰度图像转换成二值图像。如果某个像素的值大于等于阀值,该像素置为白色;否则置为黑色。
        /// 目前支持8bpp和16bpp两种灰度图像的转换,对于8bpp,阀值介于0~255之间;对于16bpp,阀值介于0~65535之间。
        /// </summary>
        /// <param name="bitmapGrayscale">灰度图像</param>
        /// <param name="thresholdValue">阀值</param>
        /// <returns>返回转换之后的二值图像;如果转换失败,返回null。</returns>
        private Bitmap Threshold(Bitmap bitmapGrayscale,int thresholdValue)
        {
            Bitmap bitmapThreshold = null;
            if (bitmapGrayscale != null)
            {
                int width = bitmapGrayscale.Width;
                int height = bitmapGrayscale.Height;
                Rectangle rect = new Rectangle(0, 0, width, height);
                PixelFormat pixelFormat = bitmapGrayscale.PixelFormat;
                if (pixelFormat == PixelFormat.Format8bppIndexed)
                {
                    if (thresholdValue >= 0 && thresholdValue <= 255)
                    {
                        bitmapThreshold = (Bitmap)bitmapGrayscale.Clone();
                        byte white = 255;
                        byte black = 0;
                        BitmapData data = bitmapThreshold.LockBits(rect, ImageLockMode.ReadWrite, pixelFormat);
                        unsafe
                        {
                            byte* ptrStart = (byte*)data.Scan0.ToPointer();
                            byte* ptr1;
                            for (int row = 0; row < height; row++)
                            {
                                ptr1 = ptrStart + data.Stride * row;
                                for (int col = 0; col < width; col++)
                                {
                                    *ptr1 = (*ptr1 < thresholdValue) ? black : white;
                                    ptr1++;
                                }
                            }
                        }
                        bitmapThreshold.UnlockBits(data);
                    }
                }
                else if (pixelFormat == PixelFormat.Format16bppGrayScale)
                {
                    bitmapThreshold = (Bitmap)bitmapGrayscale.Clone();
                    UInt16 white = 65535;
                    UInt16 black = 0;
                    BitmapData data = bitmapThreshold.LockBits(rect, ImageLockMode.ReadWrite, pixelFormat);
                    unsafe
                    {
                        byte* ptrStart = (byte*)data.Scan0.ToPointer();
                        UInt16* ptr1;
                        for (int row = 0; row < height; row++)
                        {
                            ptr1 = (UInt16*)(ptrStart + data.Stride * row);
                            for (int col = 0; col < width; col++)
                            {
                                *ptr1 = (*ptr1 < thresholdValue) ? black : white;
                                ptr1++;
                            }
                        }
                    }
                    bitmapThreshold.UnlockBits(data);
                }
            }
            return bitmapThreshold;
        }
    }
}

分别用上述5种形式处理10次,记录下运行时间,去掉每种的最大和最小数据,然后计算平均值。结果如下所示(单位是毫秒):

语言

类库

灰度化

二值化

性能排名

C

OpenCv

16.89721

7.807766

1

C#

Aforge.net

48.9403

25.32473

5

C#

EmguCv

18.86898

13.74628

3

C#

OpenCv(P/Invoke)

18.68938

10.0149

2

C#

自定义处理方法

48.33593

21.46168

4

测试环境如下:CPU-奔腾4 2.4G,内存-512M,操作系统-Windows XP SP2,显卡-nVidia GForce4 64M,进程数-49,线程数-611,句柄数-13004,可用内存101M。

毫无疑问,用C语言调用OpenCv的性能最好,两种纯.net的方式性能最差。

 C语言调用OpenCv的处理效果如下所示:

 

C#的处理效果如下:

 

结论

将上面的内容汇总结果如下表所示:

类库

OpenCv

EmguCv

AForge.net

许可协议

BSD

GPL v3或商业授权

LGPL v3

下载

方便

方便

方便

安装

比较容易

容易

容易

文档资料

中等

易用性

比较差

比较好

性能

很好

比较好

不好

综上所述,我的选择是使用EmguCv作为我的图像处理类库,在必要的时候用P/Invoke的形式调用没有被封装的OpenCv函数。你呢? 



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值