在C#中使用WIA獲取掃描儀數據


在C#中使用WIA獲取掃描儀數據(一)

WIA(Windows Image Acquire,最新版本2.0)是Windows中一組從設備中捕獲圖像的標准API集合,它可以從設備(例如掃描儀、數碼相機)中獲取靜態圖像,以及管理這些設備。它既是API,又是DDI(Device Driver Interface)。因此,只要是滿足這個規范的設備,都能夠利用WIA直接和應用程序交互,而不是通過驅動。WIA甚至提供了統一的對話框來獲取圖片。

WIA是基於Com的,有兩種使用方式:

  1. c++:使用WIA自定義接口
  2. 其他:使用WIAAL(WIA Automation Layer)。


注:在Windows XP sp1以前的版本,WIAAL還不存在,因此第二種方式用的是WIA Scripting Model。

在.Net中使用WIA,我們用的是第二種方法。接下來做一個簡單的圖像掃描程序:

界面

新建一個WinForm應用程序,在上面添加一個按鈕和一個圖片框,點擊按鈕時啟動掃描進程,然后在圖片框中顯示圖像,應用程序界面如下:


image-thumb39.png(42.89 K)

5/24/2009 6:59:47 AM




使用WIA

Visual Studio 2008有一個好處,可以自動裝配Com組件,在工程中添加一個WIA的COM引用:

image-thumb40.png(48.10 K)

5/24/2009 6:59:47 AM




點擊確定后,會在工程引用中添加一個WIA.Interop.dll的文件,可以在對象瀏覽器中查看它:


image-thumb41.png(9.83 K)

5/24/2009 6:59:47 AM




打開掃描對話框

接下來可以利用WIA來進行掃描了,步驟很簡單,首先引用命名空間:

using WIA;接下來,在button的Click事件中,添加如下代碼:

  1. ImageFile imageFile = null;
  2. CommonDialogClass cdc = new WIA.CommonDialogClass();
  3.  
  4. try
  5. {
  6.     imageFile = cdc.ShowAcquireImage(WIA.WiaDeviceType.ScannerDeviceType,
  7.                                     WIA.WiaImageIntent.TextIntent,
  8.                                     WIA.WiaImageBias.MaximizeQuality,
  9.                                     "{00000000-0000-0000-0000-000000000000}",
  10. 10.                                     true,
  11. 11.                                     true,
  12. 12.                                     false);

13. }

14. catch (System.Runtime.InteropServices.COMException)

15. {

  1. 16.     imageFile = null;

17. }

復制代碼

WIA會自動彈出標准掃描對話框,進行掃描操作:

image-thumb42.png(31.45 K)

5/24/2009 6:59:47 AM





獲取圖像

調用ShowAcquireImage后,掃描后的數據就保存在ImageFile對象里了。用以下方法讀取ImageFile中的數據(該方法很傻很傻……很傻)

  1. if (imageFile != null)
  2. {
  3.  
  4.     imageFile.SaveFile(@"c:\1.bmp");
  5.     using (FileStream stream = new FileStream(@"c:\1.bmp", FileMode.Open,
  6.         FileAccess.Read, FileShare.Read))
  7.     {
  8.         pictureBox1.Image = Image.FromStream(stream);
  9.     }    File.Delete(@"c:\1.bmp");

10. }

復制代碼

結果如下:

 

在C#中使用WIA獲取掃描儀數據(二、WIA Automation Layer

前文說過,在WIA 2.0 里,有一個叫Automation Layer的東西,來負責WIA和應用程序交互。既然被命名為Automation了,那么意味着比直接試用WIA接口,WIAAL更容易、更方便。實際上的確如此。

關於WIA Automation Layer

文檔上說,WIA Automation Layer是一個高級的,全能的圖像操作組件,能為應用程序(例如ASP,C#)提供首尾相連的處理能力。利用WIAAL,在程序中可以很容易地從諸如數碼相機、掃描儀等圖像設備中捕獲圖像,以及進行簡單處理(縮放、旋轉)。

對象分級結構

WIAAL的對象不多,總的來說分成來兩塊,第一塊是可以被創建的類(例如在c#里我們用關鍵字new來創建),另一部分是不能被創建的類(在c#里,這些類雖然也有構造函數,不過即使創建了,也沒有任何東西),它們必須由第一種類創建。如下圖:

image-thumb44.png(51.69 K)

5/24/2009 7:03:03 AM




可見,上面有我們熟悉的CommonDialog(在Interop后,這些類后面都加上了Class表示實現,例如CommonDialogClass就是CommonDialog的實現)。 

改進的例子

在前面的文章中,我用了一個很“囧”的方法來保存圖片,實際上大可不必如此,從上面的關系圖可以看到,ImageFile對象有一個Vector的對象,該對象保存了圖片的像素值。修改代碼如下:

  1. if (imageFile != null)
  2. {
  3.     var buffer =imageFile.FileData.get_BinaryData() as byte[];
  4.     using (MemoryStream ms = new MemoryStream())
  5.     {
  6.         ms.Write(buffer, 0, buffer.Length);
  7.         pictureBox1.Image = Image.FromStream(ms);
  8.     }
  9. }

 

在C#中使用WIA獲取掃描儀數據(三、利用Filter處理圖片)

 

WIA Automation Layer不僅能從設備中捕獲照片,還能進行簡單的處理。當WIA Automation Layer從設備中捕獲照片,保存為一個ImageFile對象,我們可以通過訪問該ImageFile對象來訪問照片的屬性。然而,為了保護原來的照片,不能直接通過修改該ImageFile對象的方法修改圖片。代替的方法是,使用ImageProcess和一個或多個Filter對象創建一個副本,修改圖片。

代碼

以下代碼把掃描得到的圖片順時針旋轉90度:

  1. if (imageFile != null)
  2. {
  3.  
  4.     ImageProcess ip = new ImageProcessClass();
  5.  
  6.     object filterName="RotateFlip";
  7.     Object propertyName = "RotationAngle";
  8.     Object propertyValue = 90;
  9.  
  10. 10.     ip.Filters.Add(ip.FilterInfos.get_Item(ref filterName).FilterID, 0);
  11. 11.     ip.Filters[1].Properties.get_Item(ref propertyName).set_Value(ref propertyValue);
  12. 12.  
  13. 13.     var buffer =ip.Apply(imageFile).FileData.get_BinaryData() as byte[];
  14. 14.     using (MemoryStream ms = new MemoryStream())
  15. 15.     {
  16. 16.         ms.Write(buffer, 0, buffer.Length);
  17. 17.         pictureBox1.Image = Image.FromStream(ms);
  18. 18.     }
  19. 19.  

20. }

復制代碼

 

image-thumb45.png(256.10 K)

5/24/2009 7:08:54 AM




FilterID

以下是可用的FilterID

RotateFlip

以 90 度增量旋轉,以及水平或垂直翻轉。
RotationAngle  - 如果希望旋轉,可將 RotationAngle 屬性設置為 90、180 或 270,
                    否則設置為 0 [默認值]
FlipHorizontal - 如果希望水平翻轉圖像,可將 FlipHorizontal 屬性設置為 True,
                    否則設置為 False [默認值]
FlipVertical  - 如果希望垂直翻轉圖像,可將 FlipVertical 屬性設置為 True,
                    否則設置為 False [默認值]
FrameIndex    - 如果希望修改除 ActiveFrame 之外的幀,
                    可將 FrameIndex 屬性設置為幀的索引,
                    否則設置為 0 [默認值]


Crop

以指定的左、右、上、下邊距裁剪圖像。
Left      - 如果希望沿左側裁剪,可將 Left 屬性設置為左邊距(單位為像素),
                否則設置為 0 [默認值]
Top        - 如果希望沿頂部裁剪,可將 Top 屬性設置為上邊距(單位為像素),
                否則設置為 0 [默認值]
Right      - 如果希望沿右側裁剪,可將 Right 屬性設置為右邊距(單位為像素),
                否則設置為 0 [默認值]
Bottom    - 如果希望沿底部裁剪,可將 Bottom 屬性設置為下邊距(單位為像素),
                否則設置為 0 [默認值]
FrameIndex - 如果希望修改除 ActiveFrame 之外的幀,
                可將 FrameIndex 屬性設置為幀的索引,否則設置為 0 [默認值]


Scale

將圖像縮放到指定的最大寬度和最大高度,如有必要,保留縱橫比。
MaximumWidth        - 將 MaximumWidth 屬性設置為希望將圖像縮放到的寬度(單位為像素)。
MaximumHeight      - 將 MaximumHeight 屬性設置為希望將圖像縮放到的高度(單位為像素)。
PreserveAspectRatio - 如果希望保持圖像當前的縱橫比,可將 PreserveAspectRatio 屬性設置為 True [默認值],
                          否則設置為 False,圖像將被拉伸到MaximumWidth 和 MaximumHeight
FrameIndex          - 如果希望修改除 ActiveFrame 之外的幀,可將 FrameIndex 屬性設置為幀的索引,
                      否則設置為 0 [默認值]


Stamp

在指定的 Left 和 Top 坐標處標記指定的 ImageFile。
ImageFile  - 將 ImageFile 屬性設置為希望標記的 ImageFile 對象
Left      - 將 Left 屬性設置為希望將 ImageFile 標記到的從左側開始的偏移(單位為像素)[默認值為 0]
Top        - 將 Top 屬性設置為希望將 ImageFile 標記到的從頂部開始的偏移(單位為像素)[默認值為 0]
FrameIndex - 如果希望修改除 ActiveFrame 之外的幀,可將 FrameIndex 屬性設置為幀的索引,否則設置為0[默認值]


Exif

添加/刪除指定的 Exif 屬性。
Remove    - 如果希望刪除指定的 Exif 屬性,可將 Remove 屬性設置為 True,否則設置為 False [默認值]以添加
              指定的 exif 屬性
ID        - 將 ID 屬性設置為希望添加或刪除的 PropertyID
Type      - 設置 Type 屬性以指示希望添加的 Exif 屬性的 WiaImagePropertyType(對於刪除則忽略)
Value      - 將 Value 屬性設置為希望添加的 Exif 屬性的值(對於刪除則忽略)
FrameIndex - 如果希望修改除 ActiveFrame 之外的幀,可將 FrameIndex 屬性設置為幀的索引,否則設置為0[默認值]
Frame
Remove    - 如果希望刪除指定的 FrameIndex,可將 Remove 屬性設置為 True,
              否則設置為 False [默認值]以在指定的 FrameIndex 之前插入 ImageFile
ImageFile  - 將 ImageFile 屬性設置為希望添加其 ActiveFrame 的 ImageFile 對象(對於刪除則忽略)
FrameIndex - 對於刪除,將 FrameIndex 屬性設置為希望刪除的幀的索引,
                  對於添加,將 FrameIndex 設置為要在其之前插入ImageFile 的幀的索引,否則設置為 0 [默認值]
              以從指定的 ImageFile 追加幀


ARGB

ARGBData -  將 ARGBData 屬性設置為表示指定 FrameIndex 的ARGB 數據的 Longs 的矢量(寬度和高度必須匹配)
FrameIndex - 將 FrameIndex 屬性設置為希望修改其 ARGB 數據的幀的索引,否則設置為0[默認值]以修改ActiveFrame


Convert

將得到的 ImageFile 轉換為指定的類型。
FormatID    - 將 FormatID 屬性設置為所需支持的光柵圖像格式,當前可選擇的格式有 wiaFormatBMP、
                wiaFormatPNG、wiaFormatGIF、wiaFormatJPEG 或 wiaFormatTIFF
Quality    - 對於 JPEG 文件,可將 Quality 屬性設置為從 1 到100 [默認值]之間的任何值,以指定 JPEG 壓縮的質量
Compression - 對於 TIFF 文件,可將 Compression 屬性設置為 CCITT3、CCITT4、RLE 或 Uncompressed 以指定壓縮方案,
                否則可設置為 LZW [默認值]


小節

總的來說,在c#中利用Automation Layer中的Filter非常麻煩(要寫一堆Object),這些簡單的圖像處理操作還不如用GDI+來實現。

 

在C#中使用WIA獲取掃描儀數據(四、通過編程方式掃描圖像)

 

在前面幾節,我通過調用CommonDialog對象的ShowAcquireImage方法來掃描圖像,這是一個彈出選擇設備對話框,讓用戶自己掃描的過程。有時候,我們不想把過程弄得那么復雜,只想用戶點擊按鈕后,自動開始掃描。本節我將嘗試這個需求。

WIAAL模型

在開始代碼前,再回顧以下WIAAL模型,這里選取其中的一小部分:

image-thumb49.png(12.37 K)

5/24/2009 7:13:49 AM




和  

image-thumb50.png(15.66 K)

5/24/2009 7:13:49 AM





從上圖不難想象,一台掃描儀,實際上就是一個Device對象,因此,我們可以通過DeviceManager來“獲取”這台設備的“引用”,然后通過得到的Device對象,執行相應的掃描工作。從而跳過了使用ShowAcquireImage方法帶來的一系列“多余的鼠標操作問題”。

獲取Device對象

按照上面思路,首先需要建立一個DeviceManager對象:

DeviceManager manager = new DeviceManagerClass();然后獲取Device對象,在這里,我假設我的電腦上只有一台掃描儀,因此不做諸如“判斷使用哪台掃描儀進行掃描”之類的操作。

  1. Device device = null;
  2.  
  3. foreach (DeviceInfo info in manager.DeviceInfos)
  4. {
  5.     if (info.Type != WiaDeviceType.ScannerDeviceType) continue;
  6.     device = info.Connect();
  7.     break;
  8. }

復制代碼

掃描圖像

WIA把Device設備的圖像數據看做一個個Item對象,可以通過方法GetItem(ItemID)來實現。不過,對於掃描儀做種東西,和數碼相機不同,一般只有一個Item對象,因此可以簡單的使用數組的方法(注意:index是從1開始的,而不是從0):

Item item = device.Items[1];

最后,調用CommonDialog的ShowTransfer方法,用一個進度條,來顯示掃描過程:

  1. CommonDialogClass cdc = new WIA.CommonDialogClass();
  2. ImageFile imageFile = cdc.ShowTransfer(item,
  3.     "{B96B3CAB-0728-11D3-9D7B-0000F81EF32E}",
  4.     true) as ImageFile;
  5.  
  6. if (imageFile != null)
  7. {
  8.     var buffer = imageFile.FileData.get_BinaryData() as byte[];
  9.     using (MemoryStream ms = new MemoryStream())
  10. 10.     {
  11. 11.         ms.Write(buffer, 0, buffer.Length);
  12. 12.         pictureBox1.Image = Image.FromStream(ms);
  13. 13.     }

14. }

復制代碼

關於ShowTransfer方法

CommonDialog的ShowTransfer方法,實際上就是ShowAcquireImage方法的最后一個步驟,顯示一個獲取圖片的進度條:

image-thumb51.png(18.47 K)

5/24/2009 7:13:49 AM




聲明如下:

public virtual object ShowTransfer(Item Item, string FormatID, bool CancelError);對於第二個參數,FormatID,可以使用以下值:

  • wiaFormatBMP ({B96B3CAB-0728-11D3-9D7B-0000F81EF32E})
  • wiaFormatPNG ({B96B3CAF-0728-11D3-9D7B-0000F81EF32E})
  • wiaFormatGIF ({B96B3CB0-0728-11D3-9D7B-0000F81EF32E})
  • wiaFormatJPEG ({B96B3CAE-0728-11D3-9D7B-0000F81EF32E})
  • wiaFormatTIFF ({B96B3CB1-0728-11D3-9D7B-0000F81EF32E})

 

============================================================================

有人提問

 

真是好文. wia的sample很多都是vb各c++的. c#的真的很小. 先謝謝樓主.

但小弟有一個問題. 請先看小弟的PROGRAM.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using WIA;
using System.IO;

namespace ScanDoc
{
    public partial class Form1 : Form
    {
        String wiaFormatJPEG = "{B96B3CAE-0728-11D3-9D7B-0000F81EF32E}";
        String wiaFormatTIFF = "{B96B3CB1-0728-11D3-9D7B-0000F81EF32E}";

        DeviceManager manager;
        Device device;
        CommonDialogClass cdc;

        public Form1()
        {
            InitializeComponent();
            manager = new DeviceManager();
            cdc = new WIA.CommonDialogClass();
            device = cdc.ShowSelectDevice(WiaDeviceType.ScannerDeviceType, true, true);
            manager.RegisterEvent("{6AA1C701-F2FD-11D2-99F9-006008CF3572}", device.DeviceID);
            manager.OnEvent += new _IDeviceManagerEvents_OnEventEventHandler(OnScanImageButtonPress);
        }

        private void scanTest()
        {
            ImageFile imageFile = cdc.ShowTransfer(device.Items[1], wiaFormatTIFF, true) as ImageFile;
            displayScanedImage(imageFile);
        }

        private void displayScanedImage(ImageFile imageFile)
        {
            if (imageFile != null)
            {
                byte[] buffer;
                buffer = imageFile.FileData.get_BinaryData() as byte[];
                using (MemoryStream ms = new MemoryStream())
                {
                    ms.Write(buffer, 0, buffer.Length);
                    pictureBox1.Image = Image.FromStream(ms);
                }
            }
        }

        private void OnScanImageButtonPress(string eventId, string deviceId, string itemId)
        {
            scanTest();
        }

    }
}

這程序一開始會請使用者選擇 掃瞄器.
以后每按一下 scan 的鍵就會運行 ScanTest 這個 procedure.
這支程序在一般掃瞄器無什麼問題. 問題是如果我用有送紙器的掃瞄器,問題就出玩. 問題如下:

1.) 掃瞄格式如果是 wiaFormatJPEG. 每按一下掃瞄器上的掃瞄按鈕,就只掃瞄一張.
2.) 掃瞄格式如果是 wiaFormatTIFF. 每按一下掃瞄器上的掃瞄按鈕,可以把送紙器的文件都全掃瞄到一個 多頁的TIFF檔. 但再把文件放回送紙器,再按掃瞄按鈕. 這情況就只會出現 進導條 而掃瞄器則沒有動作.

如果我修改 scanTest() 則可以正常掃瞄. 但會再要求重就選擇掃瞄器.
        private void scanTest()
        {
            device = cdc.ShowSelectDevice(WiaDeviceType.ScannerDeviceType, true, true);
            ImageFile imageFile = cdc.ShowTransfer(device.Items[1], wiaFormatTIFF, true) as ImageFile;
            displayScanedImage(imageFile);
        }

不知是什麼問題.請樓主指教.

============================================================================

在C#中使用WIA獲取掃描儀數據(五、注冊事件)

 

好了,現在我們能在c#里通過編程掃描圖像了。還不滿足?對,在前面的例子里,需要掃描的時候總是要按下一個掃描按鈕,既傻又費事。現在的掃描儀,上面往往會多幾個額外的按鈕用來和用戶交互,例如我是用的HP G2410上就有兩個按鈕:掃描及復制。那么,能不能用這兩個按鈕來代替程序里的那個難看的按鈕呢?

image-thumb52.png(29.40 K)

5/24/2009 7:17:45 AM




注意左上角那個難看的按鈕了嗎?

在WIAAL里,我們可以同過注冊設備事件,監聽事件等方式和設備上的按鈕交互。

注冊事件

還記得我們在上節提到的DeviceManager對象嗎?MSDN官方文檔描述:

The Microsoft Windows Image Acquisition (WIA) Device Manager is an extension of the Still Image (STI) Event Monitor. The WIA Device Manager provides objects, methods, and interfaces for the following:

  • Installing devices
  • Enumerating devices
  • Querying properties of installed devices
  • Creating device objects
  • Monitoring device events
  • Acquiring images
  • Registering destination applications.

和傳統.Net編程不同,WIA的事件,需要先通過DeviceManager的RegisterEvent的方法注冊,才能使用。RegisterEvent定義如下:

void RegisterEvent(string EventID, string DeviceID);其中,EventID是事件的GUID,DeviceID是掃描儀的GUID。在類EventID里,WIA定義了幾種基本的事件類型,從定義上不難理解這些ID的所代表的具體事件:

  1. public const string wiaEventDeviceConnected = "{A28BBADE-64B6-11D2-A231-00C04FA31809}";
  2. public const string wiaEventDeviceDisconnected = "{143E4E83-6497-11D2-A231-00C04FA31809}";
  3. public const string wiaEventItemCreated = "{4C8F4EF5-E14F-11D2-B326-00C04F68CE61}";
  4. public const string wiaEventItemDeleted = "{1D22A559-E14F-11D2-B326-00C04F68CE61}";
  5. public const string wiaEventScanEmailImage = "{C686DCEE-54F2-419E-9A27-2FC7F2E98F9E}";
  6. public const string wiaEventScanFaxImage = "{C00EB793-8C6E-11D2-977A-0000F87A926F}";
  7. public const string wiaEventScanFilmImage = "{9B2B662C-6185-438C-B68B-E39EE25E71CB}";
  8. public const string wiaEventScanImage = "{A6C5A715-8C6E-11D2-977A-0000F87A926F}";
  9. public const string wiaEventScanImage2 = "{FC4767C1-C8B3-48A2-9CFA-2E90CB3D3590}";

10. public const string wiaEventScanImage3 = "{154E27BE-B617-4653-ACC5-0FD7BD4C65CE}";

11. public const string wiaEventScanImage4 = "{A65B704A-7F3C-4447-A75D-8A26DFCA1FDF}";

12. public const string wiaEventScanOCRImage = "{9D095B89-37D6-4877-AFED-62A297DC6DBE}";

13. public const string wiaEventScanPrintImage = "{B441F425-8C6E-11D2-977A-0000F87A926F}";

復制代碼

例如,我們可以使用以下來嗎來注冊一個事件,並監聽它:

  1. manager.RegisterEvent(EventID.wiaEventScanImage, device.DeviceID);
  2.  
  3. manager.OnEvent += (eventID, deviceID, itemID) =>
  4. {
  5.     //…………
  6. }

復制代碼

枚舉設備事件

如果你向我這般,興沖沖地在OnEvent里加入掃描處理邏輯,然后按下HP G2410上的掃描按鈕,你一定會像我一樣,在漫長的等待中漸漸失望:掃描儀根本沒有按我所想的那樣掃描圖片。也就是說,wiaEventScanImage這個事件根本不起作用。

幸好能夠通過Device類來枚舉設備支持的事件,我寫了以下一段代碼:

  1. Console.WriteLine("Events:");
  2. foreach (DeviceEvent eve in device.Events)
  3. {
  4.     Console.WriteLine("{0}:{1}:{2}", eve.EventID, eve.Name, eve.Description);
  5. }

復制代碼

運行后,發現該掃描儀僅僅支持wiaEventDeviceConnected 和 wiaEventDeviceDisconnected ,以及兩個HP自定義的事件:按下掃描按鈕、按下拷貝按鈕。OOXX!

image-thumb53.png(36.57 K)

5/24/2009 7:17:45 AM





按下按鈕掃描圖像

修改manager.RegisterEvent方法,使用HP提供的EventID:

manager.RegisterEvent("{0C5E2143-FD9B-490B-9AD5-7637A403566B}", device.DeviceID);

最終我們可以通過按下掃描儀上的掃描按鈕來掃描數據了!:)

  1. manager.OnEvent += (eventID, deviceID, itemID) =>
  2. {
  3.     Item item = device.Items[1];
  4.  
  5.     CommonDialogClass cdc = new WIA.CommonDialogClass();
  6.     ImageFile imageFile = cdc.ShowTransfer(item,
  7.     "{B96B3CAB-0728-11D3-9D7B-0000F81EF32E}",
  8.     true) as ImageFile;
  9.  
  10. 10.     if (imageFile != null)
  11. 11.     {
  12. 12.         var buffer = imageFile.FileData.get_BinaryData() as byte[];
  13. 13.         using (MemoryStream ms = new MemoryStream())
  14. 14.         {
  15. 15.             ms.Write(buffer, 0, buffer.Length);
  16. 16.             pictureBox1.Image = Image.FromStream(ms);
  17. 17.         }
  18. 18.     }

19. };


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM