Conner's profile☆ Conner Wang ☆PhotosBlogListsMore Tools Help

Blog


    January 14

    使用C#进行图像处理的几种方法

    本文讨论了C#图像处理中Bitmap类、BitmapData类和unsafe代码的使用以及字节对齐问题。

    Bitmap类

    命名空间:System.Drawing

    封装 GDI+ 位图,此位图由图形图像及其属性的像素数据组成。Bitmap 是用于处理由像素数据定义的图像的对象。 

        利用C#类进行图像处理,最方便的是使用Bitmap类,使用该类的GetPixel()与SetPixel()来访问图像的每个像素点。下面是MSDN中的示例代码:

    public void GetPixel_Example(PaintEventArgs e)
    {
        // Create a Bitmap object from an image file.
        Bitmap myBitmap = new Bitmap("Grapes.jpg");
        // Get the color of a pixel within myBitmap.
        Color pixelColor = myBitmap.GetPixel(50, 50);
        // Fill a rectangle with pixelColor.
        SolidBrush pixelBrush = new SolidBrush(pixelColor);
        e.Graphics.FillRectangle(pixelBrush, 0, 0, 100, 100);
    }

        可见,Bitmap类使用一种优雅的方式来操作图像,但是带来的性能的降低却是不可忽略的。比如对一个800*600的彩色图像灰度化,其耗费的时间都要以秒为单位来计算。在实际项目中进行图像处理,这种速度是决对不可忍受的。

     

    BitmapData类

    命名空间:System.Drawing.Imaging

    指定位图图像的属性。BitmapData 类由 Bitmap 类的 LockBits 和 UnlockBits 方法使用。不可继承。

        好在我们还有BitmapData类,通过BitmapData BitmapData LockBits ( )可将 Bitmap 锁定到系统内存中。该类的公共属性有:

    • Width           获取或设置 Bitmap 对象的像素宽度。这也可以看作是一个扫描行中的像素数。
    • Height          获取或设置 Bitmap 对象的像素高度。有时也称作扫描行数。
    • PixelFormat  获取或设置返回此 BitmapData 对象的 Bitmap 对象中像素信息的格式。
    • Scan0            获取或设置位图中第一个像素数据的地址。它也可以看成是位图中的第一个扫描行。
    • Stride            获取或设置 Bitmap 对象的跨距宽度(也称为扫描宽度)。

        下面的MSDN中的示例代码演示了如何使用 PixelFormat、Height、Width 和 Scan0 属性;LockBits 和 UnlockBits 方法;以及 ImageLockMode 枚举。

    private void LockUnlockBitsExample(PaintEventArgs e)
    {

        // Create a new bitmap.
        Bitmap bmp = new Bitmap("c:\\fakePhoto.jpg");

        // Lock the bitmap's bits. 
        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        System.Drawing.Imaging.BitmapData bmpData =
            bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
            bmp.PixelFormat);
        // Get the address of the first line.
       IntPtr ptr = bmpData.Scan0;

        // Declare an array to hold the bytes of the bitmap.
        int bytes  = bmpData.Stride * bmp.Height;
        byte[] rgbValues = new byte[bytes];

        // Copy the RGB values into the array.
        System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

        // Set every red value to 255. 
        for (int counter = 0; counter < rgbValues.Length; counter+=3)
            rgbValues[counter] = 255;
        // Copy the RGB values back to the bitmap
        System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);

        // Unlock the bits.
        bmp.UnlockBits(bmpData);

        // Draw the modified image.
        e.Graphics.DrawImage(bmp, 0, 150);

    }

        上面的代码演示了如何用数组的方式来访问一幅图像,而不在使用低效的GetPixel()和SetPixel()。

     

    unsafe代码

        而在实际中上面的做法仍然不能满足我们的要求,图像处理是一种运算量比较大的操作,不同于我们写的一般的应用程序。我们需要的是一种性能可以同C++程序相媲美的图像处理程序。C++是怎么提高效率的呢,答曰:指针。幸运的是.Net也允许我们使用指针,只能在非安全代码块中使用指针。何谓非安全代码?

        为了保持类型安全,默认情况下,C# 不支持指针运算。不过,通过使用 unsafe 关键字,可以定义可使用指针的不安全上下文。在公共语言运行库 (CLR) 中,不安全代码是指无法验证的代码。C# 中的不安全代码不一定是危险的,只是其安全性无法由 CLR 进行验证的代码。因此,CLR 只对在完全受信任的程序集中的不安全代码执行操作。如果使用不安全代码,由您负责确保您的代码不会引起安全风险或指针错误。不安全代码具有下列属性:

    • 方法、类型和可被定义为不安全的代码块。
    • 在某些情况下,通过移除数组界限检查,不安全代码可提高应用程序的性能。
    • 当调用需要指针的本机函数时,需要使用不安全代码。
    • 使用不安全代码将引起安全风险和稳定性风险。
    • 在 C# 中,为了编译不安全代码,必须用 /unsafe 编译应用程序。

        正如《C#语言规范》中所说无论从开发人员还是从用户角度来看,不安全代码事实上都是一种“安全”功能。不安全代码必须用修饰符 unsafe 明确地标记,这样开发人员就不会误用不安全功能,而执行引擎将确保不会在不受信任的环境中执行不安全代码。

        以下代码演示如何借助BitmapData类采用指针的方式来遍历一幅图像,这里的unsafe代码块中的代码就是非安全代码。

    //创建图像
    Bitmap image =  new Bitmap( "c:\\images\\image.gif" );
    //获取图像的BitmapData对像
    BitmapData data = image.LockBits( new Rectangle( 0 , 0 , image.Width , image.Height ) , ImageLockMode.ReadWrite  , PixelFormat.Format24bppRgb  ); 
    //循环处理
    unsafe

           byte* ptr = ( byte* )( data.Scan0 ); 
           for( int i = 0 ; i < data.Height ; i ++ )
           {
              for( int j = 0 ;  j < data.Width ;  j ++ )
               {
                 // write the logic implementation here
                 ptr += 3;  
               }
             ptr += data.Stride - data.Width * 3;
           }
    }

        毫无疑问,采用这种方式是最快的,所以在实际工程中都是采用指针的方式来访问图像像素的。

     

    字节对齐问题
        上例中ptr += data.Stride - data.Width * 3,表示跨过无用的区域,其原因是图像数据在内存中存储时是按4字节对齐的,具体解释如下:

        假设有一张图片宽度为6,假设是Format24bppRgb格式的(每像素3字节,在以下的讨论中,除非特别说明,否则Bitmap都被认为是24位RGB)。显然,每一行需要6*3=18个字节存储。对于Bitmap就是如此。但对于BitmapData,虽然data.Width还是等于image.Width,但大概是出于显示性能的考虑,每行的实际的字节数将变成大于等于它的那个离它最近的4的整倍数,此时的实际字节数就是Stride。就此例而言,18不是4的整倍数,而比18大的离18最近的4的倍数是20,所以这个data.Stride = 20。显然,当宽度本身就是4的倍数时,data.Stride = image.Width * 3。

        画个图可能更好理解。R、G、B 分别代表3个原色分量字节,BGR就表示一个像素。为了看起来方便我在们每个像素之间插了个空格,实际上是没有的。X表示补足4的倍数而自动插入的字节。为了符合人类的阅读习惯我分行了,其实在计算机内存中应该看成连续的一大段。

    |-------Stride-----------|
    |-------Width---------| |
    Scan0:
    BGR BGR BGR BGR BGR BGR XX
    BGR BGR BGR BGR BGR BGR XX
    BGR BGR BGR BGR BGR BGR XX
    .
    .
    .

        首先用data.Scan0找到第0个像素的第0个分量的地址,这个地址指向的是个byte类型,所以当时定义为byte* ptr。行扫描时,在当前指针位置(不妨看成当前像素的第0个颜色分量)连续取出三个值(3个原色分量。注意,0 1 2代表的次序是B G R。在取指针指向的值时,貌似p[n]和p += n再取p[0]是等价的),然后下移3个位置(ptr += 3,看成指到下一个像素的第0个颜色分量)。做过Bitmap.Width次操作后,就到达了Bitmap.Width * 3的位置,应该要跳过图中标记为X的字节了(共有Stride - Width * 3个字节),代码中就是 ptr += dataIn.Stride - dataIn.Width * 3。

        通过阅读本文,相信你已经对使用C#进行图像处理可能用到的几种方法有了一个了解。至于采用哪种方式,取决于你的性能要求。其中第一种方式最优雅;第三种方式最快,但不是安全代码;第二种方式取了个折中,保证是安全代码的同时又提高了效率。熟悉C/C++编程的人可能会比较偏向于第三种方式,我个人也比较喜欢第三种方式。

    参考:

    1. MSDN2005

    2. C#语言规范

    3. Basic Image Processing support in C#

    4. 使用C#的BitmapData

    作者:http://conner-wang.spaces.live.com转载请注明出处!

    January 03

    OpenCV/paintlib/CImg/FreeImage/CxImage/SILLY/Corona[转载]

     

    引用

    OpenCV/paintlib/CImg/FreeImage/CxImage/SILLY/Corona
    paintlib is a portable C++ class library for image loading, saving and manipulation. Images can be loaded from BMP, GIF, IFF, JPEG, PCX, PGM, PICT, PNG, PSD, SGI, TGA, TIFF and WMF files and saved in BMP, JPEG, PNG and TIFF formats. Image manipulation can be done either through filters implemented in filter classes or by directly accessing the bitmap bits. Full C++ source is provided.
     
    Really Cool!
     
    FreeImage is not full free!
     
    CxImage      http://www.xdp.it/
    完全免费,支持的图像文件格式比较多。但是图像的像素数据(pDib)和Alpha数据(pAlpha)是分开存储的,造成游戏中使用不便。如对于GDI来说,如果想使用AlphaBlend就不合适了。
     
    仅支持jpg, png, tga的读,适合游戏开发。
     
    Corona is an image input/output library that can read, write, and manipulate image files in just a few lines of code. It can write PNG and TGA files, and read PNG, JPEG, PCX, BMP, TGA, and GIF. Corona was designed to be easy to use, and exports a straightforward C++ API. With just a few lines of C++, you can add image loading to your application. Corona is open source and licensed under the zlib license.
    January 02

    sin函数做图像拉伸

        今天用到的一个算法上用到了数值的拉伸变换,给定一系列0~1之间的浮点数样本,使处于中间部分的样本得到拉伸,而值较低的部分和较高的部分得到压缩。我首先想到了图像处理中的灰度拉伸算法,于是赶紧查找相关书籍。

        大部分图像处理书上都介绍了三种灰度变换,一种是基于分段线性函数的,一种是基于log变换的,一种是基于幂函数的。显然第一种方法能完成我的功能,但由于是分段函数,所能操作较麻烦点,而且处理结果不平滑;第二种和第三种方法只能同时拉伸(或压缩)较暗的样本(或较亮)的样本。

        于是我就请教阿登,给他画了一个曲线,问他有没有这样的数学函数。他看了一会说这像正弦和余弦函数。我晃然大悟,感紧用Matlab仿真了一下。

        通过变换,我最终确定使用归一化了的这个正弦函数 f(x) = 0.5*(sin(pi*x-pi/2)+1) 0<x<=1,其函数曲线如下:

    f(x) = 0.5*(sin(pi*x-pi/2)+1) 0<x<=1

        可以看出这个函数是一个比较完美的拉伸函数,能过归一化你还能把任意范围的样本进行拉伸变换;缺点是拉伸程度不能调整。

        所以,实际中并不一定要用到很多高深的东西,关键在于思路的开阔和活学活用。

    August 14

    VC++和VC++.NET中与图像处理有关的几个概念、结构和类

      最近一直在看VC++有关图像处理方面的书,终于把以前一直混淆的几个概念、结构和类弄清楚了,特整理如下。如有错误,请大家批评指正,不胜感激。下一步想好好学习学习OpenCV,希望也能总结点东西。

    一、DDB与DIB位图

      一个Windows的位图实际上是一些和显示像素相对应的位阵列,它有两种类型:一种称之为GDI(Graphic Device Interface)位图,另一种是DIB位图(Device-Independent Bitmap)

      GDI位图包含了一种和Windows的GDI模块有关的Windows数据结构,该数据结构是与设备有关的,故此位图又称为DDB位图(Device-Dependent Bitmap)。当用户的程序取得位图数据信息时,其位图显示方式视显示卡而定。由于GDI位图的这种设备依赖性,当位图通过网络传送到另一台PC,很可能就会出现问题。

      DIB比GDI位图有很多编程优势,例如它自带颜色信息,从而使调色板管理更加容易。且任何运行Windows的机器都可以处理DIB,并通常以后缀为.BMP的文件形式被保存在磁盘中或作为资源存在于程序的EXE或DLL文件中。

    二、CBitmap类、BITMAP结构 

      CBitmap类继承自CGdiObject,是封装了图形设备接口(GDI)的位图,提供成员函数来操纵位图。要使用一个CBitmap对象,构造该对象,用初始化成员函数之一把一个位图句柄连接到该对象,然后调用该对象的成员函数。

      CBitmap类主要用于处理DDB位图,封装了与DDB位图操作函数相关的数据结构和操作函数。结构体BITMAP定义了DDB位图的类型、宽度、高度、颜色和像素值,其定义如下:

    typedef struct _tagBITMAP       
    {
      LONG          bmType ;                      // set to 0       
      LONG          bmWidth ;                     // width in pixels       
      LONG          bmHeight ;                   // height in pixels       
      LONG          bmWidthBytes ;                // width of row in bytes       
      WORD         bmPlanes ;                    // number of color planes       
      WORD         bmBitsPixel ;                 // number of bits per pixel       
      LPVOID       bmBits ;                             // pointer to pixel bits       
    }       
    BITMAP, * PBITMAP ;

      而CBitmap的LoadBitmap、CreateCompatibleBitmap、SetBitmapBits、GetBitmap等成员函数则定义了对DDB位图的装载、创建、设定位值和属性查询等操作。

      创建或装入内存的位图必须用CDC::SelectObject函数来将其选入设备上下文中,然后用CDC的BitBlt或StretchBlt函数显示出来,这两个函数的原型如下:

    BOOL BitBlt(int x, int y, int nWith, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop);

      该函数把源设备上下文中的位图复制到本身的设备上下文中,两个设备下下文可以是内存设备下下文,也可以是同一个设备上下文。

    三、Image类和Bitmap类

      在VC++.NET中,GDI+的Image类封装了对BMP、GIF、JPEG、PNG、TIFF、WMF(Windows元文件)和EMF(增强WMF)图像文件的调入、显示、格式转换以及简单处理(如缩放、旋转、拉伸等)的功能。Bitmap类(注意不是结构BITMAP)是从Image类继承的一个图像(另一个从Image继承的类是Metafile类),它封装了Windows位图操作的常用功能。例如,Bitmap::SetPixel和Bitmap::GetPixel分别用来对位图进行读写像素操作,从而可以为图像的柔化和锐化处理提供一种可能。这些功能和MFC的新类CImage功能基本一样,如果仅用于图像的读取与显示,用Bitmap类或Image类是不错的选择,如果是做图像处理,则CImage可能更符合MFC程序员的编程习惯。

    四、CImage类

      CImage类是VC++.NET中MFC和ATL共享的新类,它能从外部磁盘中调入一个JPEG、GIF、BMP和PNG格式的图像文件加以显示,而且这些文件格式可以相互转换。

      CImage既能处理DIB位图也能处理非DIB位图,但你可以仅用DIB位图来Create或CImage::Load。你也可以使用Attach把一个非DIB位图连接给CImage对象,但你不能使用下列方法,这些方法仅支持DIB位图:GetBits、GetColorTable、GetMaxColorTable、Entries、GetPitch、GetPixelAddress、IsIndexed、SetColorTable。要确定一个连接的位图是否是一个DIB位图,调用IsDibSection。

      由于CImage在不同的Windows操作系统中其某些性能是不一样的,因此在使用时要特别注意。例如,CImage::PlgBlt和CImage::MaskBlt只能在 Windows NT 4.0 或更高版本中使用,但不能运行在Windows 95/98 应用程序中。CImage::AlphaBlend和CImage::TransparentBlt也只能在 Windows 2000/98或其更高版本中使用。即使在Windows 2000运行程序还必须将stdafx.h文件中的WINVER和_WIN32_WINNT的预定义修改成0x0500才能正常使用。 CImage可以在MFC或ATL中使用。当使用CImage创建一个项目时,必须包含atlimage.h文件。

      CImage封装了DIB(设备无关位图)的功能,因而可以让我们能够处理每个位图像素。

      CImage提供了HBITMAP操作符,因此HBITMAP为参数的地方,都可以用CImage来替代。

    五、传统的VC++中的图像处理方法与VC++.NET中的图像处理方法

      由于DIB图不依赖于具体设备,因此可以用来永久性地保存图象。DIB一般是以*.BMP文件的形式保存在磁盘中的,有时也会保存在*.DIB文件中。运行在不同输出设备下的应用程序可以通过DIB来交换图象。因而在数字图象处理中会经常用到DIB位图。DIB还可以用一种RLE算法来压缩图像数据,但一般来说DIB是不压缩的。

      在VC++.net之前MFC未提供现成的类来封装DIB,这给MFC用户带来很多不便。因为用户要想使用DIB,首先应该了解DIB的结构。

      在内存中,一个完整的DIB由两部分组成:一个BITMAPINFO结构和一个存储像素阵列的数组。BITMAPINFO描述了位图的大小,颜色模式和调色板等各种属性,其定义为 typedef struct tagBITMAPINFO { 
        BITMAPINFOHEADER bmiHeader; 
        RGBQUAD bmiColors[1]; //颜色表
    } BITMAPINFO;

    RGBQUAD结构用来描述颜色,其定义为
    typedef struct tagRGBQUAD {
      BYTE     rgbBlue; //蓝色的强度
      BYTE     rgbGreen; //绿色的强度
      BYTE     rgbRed; //红色的强度
      BYTE     rgbReserved; //保留字节,为0
    } RGBQUAD;

    注意,RGBQUAD结构中的颜色顺序是BGR,而不是平常的RGB。

    BITMAPINFOHEADER结构包含了DIB的各种信息,其定义为
    typedef struct tagBITMAPINFOHEADER{
      DWORD     biSize; //该结构的大小
      LONG     biWidth; //位图的宽度(以像素为单位)
      LONG     biHeight; //位图的高度(以像素为单位)
      WORD     biPlanes; //必须为1
      WORD     biBitCount //每个像素的位数(1、4、8、16、24或32)
      DWORD     biCompression; //压缩方式,一般为0或BI_RGB (未压缩)
      DWORD     biSizeImage; //以字节为单位的图象大小(仅用于压缩位图)
      LONG     biXPelsPerMeter; //以目标设备每米的像素数来说明位图的水平分辨率
      LONG     biYPelsPerMeter; //以目标设备每米的像素数来说明位图的垂直分辨率
      DWORD     biClrUsed; /*颜色表的颜色数,若为0则位图使用由biBitCount指定的最大颜色数*/
      DWORD     biClrImportant; //重要颜色的数目,若该值为0则所有颜色都重要
    } BITMAPINFOHEADER;

      与DDB不同,DIB的字节数组是从图象的最下面一行开始的逐行向上存储的,也即等于把图象倒过来然后在逐行扫描。另外,字节数组中每个扫描行的字节数必需是4的倍数,如果不足要用0补齐。

    DIB可以存储在*.BMP或*.DIB文件中。DIB文件是以BITMAPFILEHEADER结构开头的,该结构的定义为
    typedef struct tagBITMAPFILEHEADER {
      WORD     bfType; //文件类型,必须为“BM”
      DWORD     bfSize; //文件的大小
      WORD     bfReserved1; //为0
      WORD     bfReserved2; //为0
      DWORD     bfOffBits; //存储的像素阵列相对于文件头的偏移量
    } BITMAPFILEHEADER;

      紧随该结构的是一个BITMAPINFOHEADER结构,然后是RGBQUAD结构组成的颜色表(如果有的话),文件最后存储的是DIB的像素阵列。

      DIB的颜色信息储存在自己的颜色表中,程序一般要根据颜色表为DIB创建逻辑调色板。在输出一幅DIB之前,程序应该将其逻辑调色板选入到相关的设备上下文中并实现到系统调色板中,然后再调用相关的GDI函数(如::SetDIBitsToDevice或::StretchDIBits)输出DIB。在输出过程中,GDI函数会把DIB转换成DDB,这项工作主要包括以下两步:将DIB的颜色格式转换成与输出设备相同的颜色格式;将DIB像素的逻辑颜色索引转换成系统调色板索引。 

      看到这么多结构,你是不是已经眼花缭乱了,然而更不幸的还在后面。以上说的DIB是Windows中的DIB,还有一种DIB是OS/2 DIB,它和Windows DIB的主要区别是位图信息结构(信息头和颜色表结构)。它们的图像数据的存储方式是完全一样的。在OS/2 DIB中,与WIndows DIB的BITMAPFILEHEADER、BITMAPINFOHEADER、RGBQUAD相对应的结构分别是BITMAPCOREHEADER、BITMAPCOREINFO、和RGBTRIPLE。它们的定义分别如下:

    typedef struct tagBITMAPCOREHEADER  // bmch       
    {       
      DWORD         bcSize ;              // size of the structure = 12       
      WORD          bcWidth ;             // width of image in pixels       
      WORD          bcHeight ;           // height of image in pixels       
      WORD          bcPlanes ;            // = 1       
      WORD          bcBitCount ;          // bits per pixel (1, 4, 8, or 24)       
    }       
    BITMAPCOREHEADER, * PBITMAPCOREHEADER ;

    typedef struct tagBITMAPCOREINFO  // bmci
    {       
      BITMAPCOREHEADER      bmciHeader ;                  // core-header structure       
      RGBTRIPLE             bmciColors[1] ;               // color table array       
    }       
    BITMAPCOREINFO, * PBITMAPCOREINFO ;

    typedef struct tagRGBTRIPLE  // rgbt       
    {       
      BYTE     rgbtBlue ;       // blue level       
      BYTE     rgbtGreen ;      // green level       
      BYTE     rgbtRed ;        // red level       
    }       
    RGBTRIPLE;

      因此再操作之前你应该先根据BITMAPINFOHEADER的大小来判断是哪一种DIB,然后再进行操作。

      由于MFC未提供一个封装好的易用的DIB类,用户在使用DIB时将面临繁重的Windows API编程任务。不信你看看Microsoft提供的MFC的DibLook例程提就知道了。所以传统的图像处理方法一般都会把这些Win32 SDK中的操作DIB位图的APIs做一个封装,作为一个通用的类来使用,以减少后续算法编写中的编程负担。

      VC++.NET提供了多种常用图像文件格式(如BMP、TIF、GIF、JPEG与PNG等)的输入/输出模块,ATL中的CImage类极大地简化了图像数据的操作,因此VC++.NET中的图像处理应以CImage为基础。
      现在市面上有一些冠名教用VC++.NET做图像处理的书,但是编程方法还是老的一套,没有跳出设备相关位图(DDB)与设备无关位图(DIB)概念的条框,编程方法比较复杂、繁琐、低效。用VC++.NET环境做图像处理,应该以CImage类为基础,完全跳出DDB、DIB概念的条框,才能使得处理方法返朴归真,大为简化。
              作者:http://conner-wang.spaces.live.com转载请注明出处!

    August 13

    基于VC.NET的GDI+编程之CImage[转载]

      我们知道,Visual C++的CBitmap类和静态图片控件的功能是比较弱的,它只能显示出在资源中的图标、位图、光标以及图元文件的内容,而不像VB中的Image控件可以显示出绝大多数的外部图像文件(BMP、GIF、JPEG等)。因此,想要在对话框或其他窗口中显示外部图像文件则只能借助于第三方提供的控件或代码。现在,MFC和ATL共享的新类CImage为图像处理提供了许多相应的方法,这使得Visual C++在图像方面的缺憾一去不复返了。

    CImage类概述

      CImage是MFC和ATL共享的新类,它能从外部磁盘中调入一个JPEG、GIF、BMP和PNG格式的图像文件加以显示,而且这些文件格式可以相互转换。由于CImage在不同的Windows操作系统中其某些性能是不一样的,因此在使用时要特别注意。例如,CImage::PlgBlt和CImage::MaskBlt只能在 Windows NT 4.0 或更高版本中使用,但不能运行在Windows 95/98 应用程序中。CImage::AlphaBlend和CImage::TransparentBlt也只能在 Windows 2000/98或其更高版本中使用。即使在Windows 2000运行程序还必须将stdafx.h文件中的WINVER和_WIN32_WINNT的预定义修改成0x0500才能正常使用。

      CImage封装了DIB(设备无关位图)的功能,因而可以让我们能够处理每个位图像素。它具有下列最酷特性:

      1、AlphaBlend支持像素级的颜色混合,从而实现透明和半透明的效果。

      2、PlgBlt能使一个矩形区域的位图映射到一个平行四边形区域中,而且还可能使用位屏蔽操作。

      3、TransparentBlt在目标区域中产生透明图像,SetTransparentColor用来设置某种颜色是透明色。

      4、MaskBlt在目标区域中产生源位图与屏蔽位图合成的效果。

    使用CImage的一般方法

      使用CImage的一般方法是这样的过程:

      (1) 打开应用程序的stdafx.h文件添加CImage类的包含文件:

    #include <atlimage.h>

      (2) 定义一个CImage类对象,然后调用CImage::Load方法装载一个外部图像文件。

      (3) 调用CImage::Draw方法绘制图像。Draw方法具有如下定义:

    程序代码:

    BOOL Draw( HDC hDestDC, int xDest, int yDest,
    int nDestWidth, int nDestHeight, int xSrc, int ySrc,
    int nSrcWidth, int nSrcHeight );
    BOOL Draw( HDC hDestDC, const RECT& rectDest, const RECT& rectSrc );
    BOOL Draw( HDC hDestDC, int xDest, int yDest );
    BOOL Draw( HDC hDestDC, const POINT& pointDest );
    BOOL Draw( HDC hDestDC, int xDest, int yDest,
    int nDestWidth, int nDestHeight );
    BOOL Draw( HDC hDestDC, const RECT& rectDest );

      其中,hDestDC用来指定绘制的目标设备环境句柄,(xDest, yDest)和pointDest用来指定图像显示的位置,这个位置和源图像的左上角点相对应。nDestWidth和nDestHeight分别指定图像要显示的高度和宽度,xSrc、ySrc、nSrcWidth和nSrcHeight用来指定要显示的源图像的某个部分所在的位置和大小。rectDest和rectSrc分别用来指定目标设备环境上和源图像所要显示的某个部分的位置和大小。

      需要说明的是,Draw方法综合了StretchBlt、TransparentBlt和AlphaBlend函数的功能。默认时,Draw的功能和StretchBlt相同。但当图像含有透明色或Alpha通道时,它的功能又和TransparentBlt、AlphaBlend相同。因此,在一般情况下,我们都应该尽量调用CImage::Draw方法来绘制图像。

      例如,下面的示例Ex_Image是实现这样的功能:当选择"文件"ò"打开"菜单命令后,弹出一个文件打开对话框。当选定一个图像文件后,就会在窗口客户区中显示该图像文件内容。这个示例的具体步骤如下:

      (1) 创建一个默认的单文档程序项目Ex_Image。

      (2) 打开stdafx.h文件中添加CImage类的包含文件atlimage.h。

      (3) 在CEx_ImageView类添加ID_FILE_OPEN的COMMAND事件映射程序,并添加下列代码:

    程序代码:

    void CEx_ImageView::OnFileOpen()
    {
     CString strFilter;
     CSimpleArray<GUID> aguidFileTypes;
     HRESULT hResult;

     // 获取CImage支持的图像文件的过滤字符串
     hResult = m_Image.GetExporterFilterString(strFilter,aguidFileTypes,
    _T( "All Image Files") );
     if (FAILED(hResult)) {
      MessageBox("GetExporterFilter调用失败!");
      return;
     }
     CFileDialog dlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, strFilter);
     if(IDOK != dlg.DoModal())
      return;

     m_Image.Destroy();
     // 将外部图像文件装载到CImage对象中
     hResult = m_Image.Load(dlg.GetFileName());
     if (FAILED(hResult)) {
      MessageBox("调用图像文件失败!");
      return;
     }

     // 设置主窗口标题栏内容
     CString str;
     str.LoadString(AFX_IDS_APP_TITLE);
     AfxGetMainWnd()->SetWindowText(str + " - " +dlg.GetFileName());

     Invalidate(); // 强制调用OnDraw
    }

      (4) 定位到CEx_ImageView::OnDraw函数处,添加下列代码:

    程序代码:

    void CEx_ImageView::OnDraw(CDC* pDC)
    {
     CEx_ImageDoc* pDoc = GetDocument();
     ASSERT_VALID(pDoc);
     if (!m_Image.IsNull()) {
      m_Image.Draw(pDC->m_hDC,0,0);
     }
    }

      (5) 打开Ex_ImageView.h文件,添加一个公共的成员数据m_Image:

    程序代码:

    public:
    CImage m_Image;

      (6) 编译并运行。单击"打开"工具按钮,在弹出的对话框中指定一个图像文件后,单击"打开"按钮,其结果如图7.21所示。

    将图片用其它格式保存

      CImage::Save方法能将一个图像文件按另一种格式来保存,它的原型如下:

    程序代码:

    HRESULT Save( LPCTSTR pszFileName, REFGUID guidFileType= GUID_NULL);

      其中,pszFileName用来指定一个文件名,guidFileType用来指定要保存的图像文件格式,当为GUID_NULL时,其文件格式由文件的扩展名来决定,这也是该函数的默认值。它还可以是GUID_BMPFile(BMP文件格式)、GUID_PNGFile(PNG文件格式)、GUID_JPEGFile(JPEG文件格式)和GUID_GIFFile(GIF文件格式)。

      例如,下面的过程是在Ex_Image示例基础上进行的,我们在CEx_ImageView类添加ID_FILE_SAVE_AS的COMMAND事件映射程序,并添加下列代码:

    程序代码:

    void CEx_ImageView::OnFileSaveAs()
    {
     if (m_Image.IsNull()) {
      MessageBox("你还没有打开一个要保存的图像文件!");
      return;
     }

     CString strFilter;
     strFilter = "位图文件|*.bmp|JPEG 图像文件|*.jpg| \
    GIF 图像文件|*.gif|PNG 图像文件|*.png||"
     CFileDialog dlg(FALSE,NULL,NULL,NULL,strFilter);
     if ( IDOK != dlg.DoModal())
      return;

     // 如果用户没有指定文件扩展名,则为其添加一个
     CString strFileName;
     CString strExtension;

     strFileName = dlg.m_ofn.lpstrFile;
     if (dlg.m_ofn.nFileExtension == 0)
     {
      switch (dlg.m_ofn.nFilterIndex)
      {
       case 1:
        strExtension = "bmp" break;
       case 2:
        strExtension = "jpg" break;
       case 3:
        strExtension = "gif" break;
       case 4:
        strExtension = "png" break;
       default:
        break;
      }
      strFileName = strFileName + ’.’ + strExtension;
     }

     // 图像保存
     HRESULT hResult = m_Image.Save(strFileName);
     if (FAILED(hResult))
      MessageBox("保存图像文件失败!");
    }

    柔化和锐化处理

      在图像处理中,我们通常用一些数学手段,对图像进行除去噪声、强调或抽取轮廓特征等图像空间的变换。所谓"图像空间的变换"是借助于一个称之为模板的局部像素域来完成的,不同的模板具有不同的图像效果。

    1. 柔化

      图像的柔化是除去图像中点状噪声的一个有效方法。所谓柔化,是指使图像上任何一个像素与其相邻像素的颜色值的大小不会出现陡突的一种处理方法。设在一个3 x 3的模板中其系数为:

      中间有底纹的表示中心元素,即用那个元素作为处理后的元素。很明显,上述模板(称之为Box模板)是将图像上每个像素用它近旁(包括它本身)的9个像素的平均值取代。这样处理的结果在除噪的同时,也降低图像的对比度,使图像的轮廓模糊。为了避免这一缺陷,我们对各点引入加权系数,将原来的模板改为:

      新的模板可一方面除去点状噪声,同时能较好地保留原图像的对比度,因此该模板得到了广泛的应用。由于这个模板是通过二维高斯(Gauss)函数得到的,故称为高斯模板。

    2. 锐化

      锐化和柔化恰恰相反,它通过增强高频分量减少图像中的模糊,因此又称为高通滤波。锐化处理在增强图像边缘效果的同时增加了图像的噪声。常用的锐化模板是拉普拉斯模板:

      用此模板处理后的图像,轮廓线条将明显得到增强。轮廓线以外的部分将变得较暗,而轮廓线部分将变得比较明亮。

      使用程序对模板进行运算时,要考虑到溢出点的处理。所谓溢出点,指的是大于255或小于0的点。处理时,可令大于255的点取255,而小于0的点取其正值。

    3. 实现代码

      实现柔化和锐化时,我们先调用CImage::GetPixel来依次读取相应的像素,然后用柔化和锐化模板进行处理,最后调用CImage::SetPixel函数将处理后的像素写回到CImage对象中。具体的代码如下:

    程序代码:

    void FilterImage(CImage* image, int nType)
    {
     if (image->IsNull())
      return;
     int smoothGauss[9] = {1,2,1,2,4,2,1,2,1}; // 高斯模板
     int sharpLaplacian[9] = {-1,-1,-1,-1,9,-1,-1,-1,-1}; // 拉普拉斯模板

     int opTemp[9];
     float aver; // 系数
     if ( nType > 1) nType = 0;
     switch( nType ){
      case 0: // 高斯模板
       aver = (float)(1.0/16.0);
       memcpy( opTemp, smoothGauss, 9*sizeof(int));
       break;
      case 1: // 拉普拉斯模板
       aver = 1.0;
       memcpy( opTemp, sharpLaplacian, 9*sizeof(int));
       break;
     }

     int i,j;

     int nWidth = image->GetWidth();
     int nHeight = image->GetHeight();

     for (i = 1; i < nWidth-1; i++){
      for (j = 1; j < nHeight-1; j++){

       int rr = 0, gg = 0, bb = 0;
       int index = 0;

       for (int col = -1; col <= 1; col++){
        for (int row = -1; row <= 1; row++){
         COLORREF clr = image->GetPixel( i+row, j+col);
         rr += GetRValue(clr) * opTemp[index];
         gg += GetGValue(clr) * opTemp[index];
         bb += GetBValue(clr) * opTemp[index];
         index++;
        }
       }
       rr = (int)(rr*aver);
       gg = (int)(gg*aver);
       bb = (int)(bb*aver);

       // 处理溢出点
      if ( rr > 255 ) rr = 255;
      else if ( rr < 0 ) rr = -rr;
      if ( gg > 255 ) gg = 255;
      else if ( gg < 0 ) gg = -gg;
      if ( bb > 255 ) bb = 255;
      else if ( bb < 0 ) bb = -bb;

      // 错位重写以避免前一个像素被新的像素覆盖
      image->SetPixel( i-1, j-1, RGB(rr,gg,bb));
      }
     }
    }
    图7.22是使用上述代码将某个图像处理后的结果。


    变成黑白图片

      由于许多图像文件使用颜色表来发挥显示设备的色彩显示能力,因而将一张彩色图片变成黑色图片时需要调用CImage::IsIndexed来判断是否使用颜色表,若是则修改颜色表,否则直接将像素进行颜色设置。例如下面的代码:

    程序代码:

    void CEx_ImageView::MakeBlackAndwhite(CImage* image)
    {
     if (image->IsNull()) return;

     if (!image->IsIndexed()) {
      // 直接修改像素颜色
      COLORREF pixel;
      int maxY = image->GetHeight(), maxX = image->GetWidth();
      byte r,g,b,avg;
      for (int x=0; x<maxX; x++) {
       for (int y=0; y<maxY; y++) {
        pixel = image->GetPixel(x,y);
        r = GetRValue(pixel);
        g = GetGValue(pixel);
        b = GetBValue(pixel);
        avg = (int)((r + g + b)/3);
        image->SetPixelRGB(x,y,avg,avg,avg);
       }
      }
     } else {
      // 获取并修改颜色表
      int MaxColors = image->GetMaxColorTableEntries();
      RGBQUAD* ColorTable;
      ColorTable = new RGBQUAD[MaxColors];
      image->GetColorTable(0,MaxColors,ColorTable);
      for (int i=0; i<MaxColors; i++)
      {
       int avg = (ColorTable[i].rgbBlue + ColorTable[i].rgbGreen + ColorTable[i].rgbRed)/3;
       ColorTable[i].rgbBlue = avg;
       ColorTable[i].rgbGreen = avg;
       ColorTable[i].rgbRed = avg;
      }
      image->SetColorTable(0,MaxColors,ColorTable);
      delete(ColorTable);
     }
    }

      至此,我们介绍了GDI+和CImage的一般使用方法和技巧。当然,它们本身还有许多更深入的方法,由于篇幅所限,这里不再一一讨论。

    基于VC.NET的GDI+图像处理[转载]

     

      我们知道,在以往的图像处理中,常常要根据不同图像文件的格式及其数据存储结构在不同格式中进行转换。某个图像文件的显示也是依靠对文件数据结构的剖析,然后读取相关图像数据而实现的。现在,GDI+提供了Image和Bitmap类使我们能轻松容易地处理图像。

    概述

      GDI+支持大多数流行的图像文件格式,如BMP、GIF、JPEG、TIFF和PNG等。下面先来介绍这些图像文件,然后再说明Image和Bitmap类支持的特性。

    1.图像文件格式简介

      图像文件是描绘一幅图像的计算机磁盘文件,其文件格式不下数十种。这里仅介绍BMP、GIF、JPEG、TIFF和PNG等图像文件格式。

      BMP文件格式

      BMP图像文件格式是Microsoft为其Windows环境设置的标准图像格式。一个Windows的BMP位图实际上是一些和显示像素相对应的位阵列,它有两种类型:一种称之为GDI位图,另一种是DIB位图(Device-Independent Bitmap)。GDI位图包含了一种和Windows的GDI模块有关的Windows数据结构,该数据结构是与设备有关的,故此位图又称为DDB位图(Device-Dependent Bitmap)。当用户的程序取得位图数据信息时,其位图显示方式视显示卡而定。由于GDI位图的这种设备依赖性,当位图通过网络传送到另一台PC,很可能就会出现问题。

      DIB比GDI位图有很多编程优势,例如它自带颜色信息,从而使调色板管理更加容易。且任何运行Windows的机器都可以处理DIB,并通常以后缀为.BMP的文件形式被保存在磁盘中或作为资源存在于程序的EXE或DLL文件中。

      GIF文件格式

      图形交换格式(GIF--Graphics Interchange Format)最早由CompuServe公司于1987年6月15日制定的标准,主要用于CompuServe网络图形数据的在线传输、存储。GIF提供了足够的信息并很好地组织了这些信息,使得许多不同的输入输出设备能够方便地交换图像,它支持24位彩色,由一个最多256种颜色的调色板实现,图像的大小最多是64K x 64K个像点。GIF的特点是LZW压缩、多图像和交错屏幕绘图。

      JPEG文件格式

      国际标准化组织(ISO)和国际电报电话咨询委员会(CCITT)联合成立的"联合照片专家组"JPEG(Joint Photographic Experts Group)经过五年艰苦细致工作后,于1991年3月提出了ISO CD 10918号建议草案:"多灰度静止图像的数字压缩编码"(通常简称为JPEG标准)。这是一个适用于彩色和单色多灰度或连续色调静止数字图像的压缩标准。它包括无损压缩和基于离散余弦变换和Huffman编码的有损压缩两个部分。前者不会产生失真,但压缩比很小;后一种算法进行图像压缩时,信息虽有损失但压缩比可以很大。例如压缩20~40倍时,人眼基本上看不出失真。

      JPEG图像文件也是一种像素格式的文件格式,但它比BMP等图像文件要复杂许多。所幸的是,GDI+的Image提供了对JPEG文件格式的支持,使得我们不需要对JPEG格式有太多的了解就能处理该格式的图像。

      TIFF文件格式

      TIFF(Tagged Image Format File,标志图像文件格式)最早由Aldus公司于1986年推出的,它能对从单色到24位真彩的任何图像都有很好的支持,而且在不同的平台之间的修改和转换是十分容易的。与其它图像文件格式不同的是,TIFF文件中有一个标记信息区用来定义文件存储的图像数据类型、颜色和压缩方法。

      PNG文件格式

      PNG(Portable Network Graphic,可移植的网络图像)文件格式是由Thomas Boutell、Tom Lane等人提出并设计的,它是为了适应网络数据传输而设计的一种图像文件格式,用于取代格式较为简单、专利限制严格的GIF图像文件格式。而且,这种图像文件格式在某种程度上甚至还可以取代格式比较复杂的TIFF图像文件格式。它的特点主要有:压缩效率通常比GIF要高、提供Alpha通道控制图像的透明度、支持Gamma校正机制用来调整图像的亮度等。

      需要说明的是,PNG文件格式支持三种主要的图像类型:真彩色图像、灰度级图像以及颜色索引数据图像。JPEG只支持前两种图像类型,而GIF虽然可以利用灰度调色板补偿图像的灰度级别,但原则上它仅仅支持第三种图像类型。

    2.Image和Bitmap类概述

      GDI+的Image类封装了对BMP、GIF、JPEG、PNG、TIFF、WMF(Windows元文件)和EMF(增强WMF)图像文件的调入、格式转换以及简单处理的功能。而Bitmap是从Image类继承的一个图像类,它封装了Windows位图操作的常用功能。例如,Bitmap::SetPixel和Bitmap::GetPixel分别用来对位图进行读写像素操作,从而可以为图像的柔化和锐化处理提供一种可能。

    3.DrawImage方法

      DrawImage是GDI+的Graphics类显示图像的核心方法,它的重载函数有许多个。常用的一般重载函数有:

    程序代码:

    Status DrawImage( Image* image, INT x, INT y);
    Status DrawImage( Image* image, const Rect& rect);
    Status DrawImage( Image* image, const Point* destPoints, INT count);
    Status DrawImage( Image* image, INT x, INT y, INT srcx, INT srcy,
    INT srcwidth, INT srcheight, Unit srcUnit);

      其中,(x,y)用来指定图像image显示的位置,这个位置和image图像的左上角点相对应。rect用来指定被图像填充的矩形区域, destPoints和count分别用来指定一个多边形的顶点和顶点个数。若count为3时,则表示该多边形是一个平行四边形,另一个顶点由系统自动给出。此时,destPoints中的数据依次对应于源图像的左上角、右上角和左下角的顶点坐标。srcx、srcy、srcwidth 和srcheight用来指定要显示的源图像的位置和大小,srcUnit用来指定所使用的单位,默认时使用PageUnitPixel,即用像素作为度量单位。

    4.调用和显示图像文件

      在GDI+中调用和显示图像文件是非常容易的,一般先通过Image或Bitmap调入一个图像文件构造一个对象,然后调用Graphics::DrawImage方法在指定位置处显示全部或部分图像。例如下面的代码:

    程序代码:

    void CEx_GDIPlusView::OnDraw(CDC* pDC)
    {
     CEx_GDIPlusDoc* pDoc = GetDocument();
     ASSERT_VALID(pDoc);

     using namespace Gdiplus;
     Graphics graphics( pDC->m_hDC );

     Image image(L"sunflower.jpg");
     graphics.DrawImage(&image, 10,10);

     Rect rect(130, 10, image.GetWidth(), image.GetHeight());
     graphics.DrawImage(&image, rect);
    }
      结果如下图所示,从图中我们可以看出,两次DrawImage的结果是不同的,按理应该相同,这是怎么一回事?原来,DrawImage在不指定显示区域大小时会自动根据设备分辨率进行缩放,从而造成显示结果的不同。


      当然,也可以使用Bitmap类来调入图像文件来构造一个Bitmap对象,其结果也是一样的。例如,上述代码可改为:

    程序代码:

    Bitmap bmp(L"sunflower.jpg");
    graphics.DrawImage(&bmp, 10,10);

    Rect rect(130, 10, bmp.GetWidth(), bmp.GetHeight());
    graphics.DrawImage(&bmp, rect);
      需要说明的是,Image还提供GetThumbnailImage的方法用来获得一个缩略图的指针,调用DrawImage后可将该缩略图显示,这在图像预览时极其有用。例如下面的代码:

    程序代码:

    Graphics graphics( pDC->m_hDC );
    Image image(L"sunflower.jpg");
    Image* pThumbnail = image.GetThumbnailImage(50, 50, NULL, NULL);

    // 显示缩略图
    graphics.DrawImage(pThumbnail, 20, 20);

    // 使用后,不要忘记删除该缩略图指针
    delete pThumbnail;

    5.图像旋转和拉伸

      图像的旋转和拉伸通常是通过在DrawImage中指定destPoints参数来实现,destPoints包含对新的坐标系定义的点的数据。下图说明了坐标系定义的方法。


      从图中可以看出,destPoints中的第一个点是用来定义坐标原点的,第二点用来定义X轴的方法和图像X方向的大小,第三个是用来定义Y轴的方法和图像Y方向的大小。若destPoints定义的新坐标系中两轴方向不垂直,就能达到图像拉伸的效果。

      下面的代码就是图像旋转和拉伸的一个示例,其结果如图7.19所示。

    程序代码:
    Image image(L"sunflower.jpg");
    graphics.DrawImage(&image, 10,10);

    Point points[] = { Point(0, 0), Point(image.GetWidth(), 0),
    Point(0, image.GetHeight())};
    Matrix matrix(1,0,0,1,230,10); // 定义一个单位矩阵,坐标原点在(230,10)
    matrix.Rotate(30); // 顺时针旋转30度

    matrix.Scale(0.63,0.6); // X 和 Y 方向分别乘以0.63和0.6比例因子
    matrix.TransformPoints(points, 3); // 用该矩阵转换points
    graphics.DrawImage(&image, points, 3);

    Point newpoints[] = {Point(450, 10), Point(510, 60), Point(350, 80)};
    graphics.DrawImage(&image, newpoints, 3);

      当然,对于图像旋转还可直接使用Graphics::RotateTransform来进行,例如下面的代码。但这样设置后,以后所有的绘图结果均会旋转,有时可能感觉不方便。

    程序代码:
    Image image(L"sunflower.jpg");
    graphics.TranslateTransform(230,10); // 将原点移动到(230,10)
    graphics.RotateTransform(30); // 顺时针旋转30度
    graphics.DrawImage(&image, 0,0);

    6.调整插补算法的质量

      当图像进行缩放时,需要对图像像素进行插补,不同的插补算法其效果是不一样的。Graphics:: SetInterpolationMode可以让我们根据自己的需要使用不同质量效果的插补算法。当然,质量越高,其渲染时间越长。下面的代码就是使用不同质量效果的插补算法模式,其结果如图7.20所示。

    程序代码:

    Graphics graphics( pDC->m_hDC );
    Image image(L"log.gif");
    UINT width = image.GetWidth();
    UINT height = image.GetHeight();

    // 不进行缩放
    graphics.DrawImage( &image,10,10);

    // 使用低质量的插补算法
    graphics.SetInterpolationMode(InterpolationModeNearestNeighbor);
    graphics.DrawImage( &image,
    Rect(170, 30, (INT)(0.6*width), (INT)(0.6*height)));

    // 使用中等质量的插补算法
    graphics.SetInterpolationMode(InterpolationModeHighQualityBilinear);
    graphics.DrawImage( &image,
    Rect(270, 30, (INT)(0.6*width), (INT)(0.6*height)));

    // 使用高质量的插补算法
    graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);
    graphics.DrawImage( &image,
    Rect(370, 30, (INT)(0.6*width), (INT)(0.6*height)));

      事实上,Image功能还不止这些,例如还有不同格式文件之间的转换等。但这些功能和MFC的新类CImage功能基本一样,但CImage更符合MFC程序员的编程习惯,因此下一节中我们来重点介绍CImage的使用方法和技巧。

    September 20

    Lenna的故事[转载]

      莱娜图在数字图像处理学习与研究中颇为知名,常被用作数字图像处理各种实验的例图。
      该图原本是刊于1972年11月号花花公子杂志上的一张祼体插图照片的一部分,这期花花公子也是历年来最畅销的一期,销量达7,161,561本。1973年6月,美国南加州大学的信号图像处理研究所的一个助理教授和他的一个研究生打算为了一个学术会议找一张数字照片,而他们对于手头现有成堆"无聊"照片感到厌烦。事实上他们需要的是一个人脸照片,同时又能让人眼前一亮。这时正好有人走进实验室,手上带着一本当时的花花公子杂志,结果故事发生了……而限于当时实验室设备和测试图片的需要,lenna的图片只抠到了原图的肩膀部分。
     
      图中人为瑞典模特儿 Lena Soderberg (ne Sjööblom)。现在被广泛使用的英文化名字"Lenna"最初是由花花公子杂志发表此照片时命名的,以方便英语读者近似正确地读出瑞典语中"Lena"的发音。Lena Soderberg 女士现在仍住在她的家乡瑞典,拥有一个有三个孩子的家庭,并且在国家酒类专卖局工作。在1988年的时候,她接受了瑞典一些计算机相关出版社的访问,她对于她的照片有这样的奇遇感到非常的惊奇与兴奋。这是她首次得知她的照片被应用在计算机行业。Lena Soderberg于1997年被邀请为嘉宾,参加了数字图像科学技术50周年学术会议。在该会议上,Lenna成了最受欢迎的人之一,她做了关于自己介绍的简要发言,并被无数的fans索取签名。
     
      莱娜图在图像压缩算法是最广泛应用的标准测试图——她的脸部与裸露的肩部已经变成了事实上的工业标准。然而,这张图像的使用也引起了一些争议。一些人担心它的色情内容;《花花公子》杂志曾经威胁要起诉对莱娜图未经授权的使用。不过这家杂志已经放弃了这种威胁,取而代之的是鼓励因为公众利益使用莱娜图。
     
      戴维·C·蒙森(David C.Munson),IEEE图像处理汇刊(IEEE Transactions on Image Processing)的主编, 在1996年1月引用了两个原因来说明莱娜图在科研领域流行的原因:
    1.该图适度的混合了细节、平滑区域、阴影和纹理,从而能很好的测试各种图像处理算法。
    2.Lenna是个美女,对于图象处理界的研究者(大部分都是男性)来说,美女图可以有效的吸引他们来做研究。
     
      可以在这个网站获得Lenna的更多图片和信息:The Lenna Story - www.lenna.org