zoukankan      html  css  js  c++  java
  • 在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. };

     

     

     

     

     

  • 相关阅读:
    ASP.NET Web Form(八)
    ASP.NET Web Form(八)
    软考知识点总结
    软考知识点总结
    c# 引用外部dll
    c# 引用外部dll
    ORA600 [Kcbz_check_objd_typ_1] Running a Job (Doc ID 785899.1)
    oracle创建em
    1918: 等值数目
    1917: 支配值数目
  • 原文地址:https://www.cnblogs.com/lvfeilong/p/khgskjdfhg.html
Copyright © 2011-2022 走看看