在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. };

 

 

 

 

 

原文地址:https://www.cnblogs.com/lvfeilong/p/khgskjdfhg.html