当前位置: 58彩票app下载 > 计算机网络 > 正文

种类笔记,举行图像管理的二种办法

时间:2019-11-13 17:22来源:计算机网络
项目笔记---CSharp图片处理 最近由于项目上需要对图片进行二值化处理,就学习了相关的图片处理上的知识,从开始的二值化的意义到动态阀值检测二值化等等,并用C#得以应用,学到了

项目笔记---CSharp图片处理

最近由于项目上需要对图片进行二值化处理,就学习了相关的图片处理上的知识,从开始的二值化的意义到动态阀值检测二值化等等,并用C#得以应用,学到了很多的知识和大家分享下我个人的经验,希望对大家有帮助。

 

 

 

二值化

 

二值化简而言之是对一副彩色图片进行0/1运算,最终显示一副黑白相间的图片,其意义多数在于对二值化处理后的图片进行分割识别,一些自动识别的验证码工具大多是先进行二值化,然后在模式识别,最终推断出验证码;我的项目中是由于硬件只支持黑色和白色,所以要对用户的图片进行处理,然后显示在硬件上。

 

在深入了解二值化的过程中就发现了很多有意思,或者说十分令我感兴趣的东西,就是各种图片处理算法。

 

因为一个普通,色彩少,图像相对简单的图片经二值化处理后还算能勉强接受。但是一副精美的图片在经过二值化处理后显得十分难看,或根本看不出图片有任何意义。这其中大家就开始研究思考。

 

先补充下知识,因为我觉得如果不把基本原理讲清楚的话,可能大家不会发现这些算法是多么的有意思(当然,也可能是我太喜欢这些东西而已)。过程是这样的,一副彩色的图片要先进行灰度化(有的是 将R+G+B加起来除以3取平均值再付给R=G=B,也可以依据权值进行灰度划分,如(0.299 * r + 0.587 * g + 0.114 * b) 这是一个依据人眼对不同RGB颜色的区分度进行优化的灰度算法,很有意思,想不到人类对不同颜色识别轻重还不一样)。在经过灰度化之后,实际每个颜色的色值是 R=G=B=(0-255之间)的数值,这样我们当然可以依据127划分,如果小于127则认为接近黑色0,反之则认为接近白色255,将所有色值依据127划分后图像就成为黑白的二值化图片。

 

回过头来,我们来看看,这样经过二值化处理的图片“失真”还是很严重,有没有什么办法能优化呢,当然这难不倒这些研究算法的专家们。

 

Ordered dithering有序抖动就是一个化腐朽为神奇的算法,具体算法细节不去深究,大概就是依据一个算法矩阵,然后对图像点进行处理。下面是图片对比。

 

 

 

这幅图片是原图

 

 

 

 

 

这幅图是已128为全局阀值的二值化图片

 

 

 

 

 

这幅图是有序抖动处理后的二值化图片,黑白的二值化图片(并非灰度),所有的点非黑即白,视觉上会产生灰度图片的视觉误差,这就是神奇之处。(注:原图如果是大图的话,效果更明显。)

 

此外,还有很多优秀的算法对图片进行处理,大都是围绕如何处理判断“阀值”而产生的。

 

AForge.Net.Image

 

在查找C#开源类库的时候,发现了强大的AForge.Net,您可以先参考其官方网站了解更多详情。这个开源类库实在是太强大了,不仅包含图片处理的各种算法方式,视频处理方式,还包含人工智能方面的各种实践,都是基于C#写的,代码整洁程度也是值得学习的,所以,今后如果有时间应该仔细研究研究。而且官方文档及Sample都十分完善,十分强大。

 

N多种图片处理方式,参照Demo,你会发现使用起来极其简单~~

 

代码样例

 

啰嗦了那么多,下面就演示下代码如何实现的:

 

这里代码可能不全,请参照官方AForge.NET Framework-2.2.5SamplesImagingFiltersDemo这个Demo

 

复制代码

Bitmap temp = AForge.Imaging.Image.Clone(new Bitmap(SrcPic), PixelFormat.Format24bppRgb); // 加载图片,并强制转换成Format24bppRgb这种格式

 

 

temp = Grayscale.CommonAlgorithms.RMY.Apply(temp); // 将图片依照RY算法进行灰度化,很多算法都是先灰度然后再处理的。

 

 

pictureBox.Image =(new OrderedDithering()).Apply(sourceImage); // 应用Filter,这里选取OrderedDithering类型的Filter

复制代码

 

 

这是应用AForge.Net实现的多种处理图片的代码,很简单并且扩展性很强,值得学习。

 

更多代码请参考官方Sample,有任何问题,请回复我。

 

后记

 

虽然有了十分强大的AForge.Net,但是针对一些特定图片处理需求还是要自己写代码的,当然也可以用AForge实现,这里我只是强调一下如果自己手动写代码的话是如何处理的并且有哪些需要注意的地方。

 

首先从彩色图片灰度化说起:所谓灰度化就是按照一定的算法将R,G,B的值转换成同一个值,这其中比较普遍的做法一个是(R+G+B)/3取平均值,另一个是加权算法依据人眼对不同颜色的识别而权值化的算法 (0.299 * r + 0.587 * g + 0.114 * b) = R=G=B。

 

复制代码

        /// <summary>

        /// 灰度化实现

        /// </summary>

        /// <param name="bmp"></param>

        /// <param name="foo"></param>

        /// <returns></returns>

        private static Bitmap WeightGrayScaleImple(Bitmap bmp, Func<double, double, double, byte> foo)

        {

            Bitmap thisMap = bmp;

            Rectangle rect = new Rectangle(0, 0, thisMap.Width, thisMap.Height);

            BitmapData bmpData = thisMap.LockBits(rect, ImageLockMode.ReadWrite, thisMap.PixelFormat);

            unsafe

            {

                byte* ptr = (byte*)(bmpData.Scan0);

                for (int i = 0; i < bmpData.Height; i++)

                {

                    for (int j = 0; j < bmpData.Width; j++)

                    {

                        ptr[0] = ptr[1] = ptr[2] = foo(ptr[2], ptr[1], ptr[0]);

                        ptr += 4;

                    }

                    ptr += bmpData.Stride - bmpData.Width * 4;

                }

            }

            thisMap.UnlockBits(bmpData);

            return thisMap;

        }

复制代码

复制代码

        

     // Foo 实现

     private static byte WeightGrayBinaraztion(double r, double g, double b)

        {

            return (byte)(0.299 * r + 0.587 * g + 0.114 * b);// Feature Weight 

        }

复制代码

注:在C#下对图片值这种指针类型的处理时,必须启用unsafe,否则效率极其低。(项目中打开unsafe开关:项目属性--->生成--->允许不安全代码)

 

 

 

基本上以上内容就是项目中所用到的处理图片的所有内容,希望通过以上内容的介绍对大家有帮助。 有问题,欢迎回复,谢谢。

 

 

最近由于项目上需要对图片进行二值化处理,就学习了相关的图片处理上的知识,从开始的二值化的意义到动态阀...

本文转自: 本文讨论了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.
    // This code is specific to a bitmap with 24 bits per pixels.
    int bytes = bmp.Width * bmp.Height * 3;
    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 作者:转载请注明出处!

编辑:计算机网络 本文来源:种类笔记,举行图像管理的二种办法

关键词: