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

Link:http://www.cnblogs.com/xrwang/archive/2010/01/26/TheComparisonOfImageProcessingLibraries.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函數。你呢?

 

感謝您耐心看完本文,希望對您有所幫助。

 

博客園的文本編輯器太操蛋了,辛苦打了一個多小時的字,突然彈出一個錯誤提示無法繼續了。提醒大家注意:如果博客內容較長,一定要用別的工具(例如WORD)編寫好,然後再複製到博客園的編輯器。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章