65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 和 GDI+ 图像处理入门,第三部分 - 边缘检测过滤器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (8投票s)

2015 年 5 月 11 日

公共领域

4分钟阅读

viewsIcon

33623

downloadIcon

2942

这将是主文章“差值边缘检测”中描述的唯一边缘检测算法的替代方案。

程序概述

用于在图像中查找对象轮廓的简单窗口窗体应用程序。要运行程序的发布版本,需要安装 Microsoft .Net framework ver. 4.0 或更高版本。

程序控件

 - 点击原始图像(左侧图像面板)将打开一个对话框以加载新图像
 - 点击结果图像(右侧图像面板)将打开一个对话框以保存结果图像
 - 更改亮度的阈值,将自动开始
    对原始图像进行新的处理
 - 更改处理后的图像显示类型,将自动开始
    对原始图像进行新的处理

引言

程序检测图像中对象的轮廓(边缘),图像以 JPЕG 或 BMP 标准 24 位 RGB 格式保存。边缘点的检测是通过一个算法实现的,该算法是主文章中显示的“差值边缘检测”算法的一个变种。"差值边缘检测"算法。

"差值边缘检测"基于这样一个假设:当观察点周围的像素对之间存在显著的颜色差异时,人眼就可以注意到图像中对象的轮廓(边缘)。这里指的是可以形成一条穿过观察点的直线的点对。

         0 0 0   0 1 0   1 0 0   0 0 1      2 - 观察点
         1 2 1   0 2 0   0 2 0   0 2 0      1 - 相反的点对
         0 0 0   0 1 0   0 0 1   1 0 0 
 
本质上,我们需要找到四对相反点对之间颜色差异的最大值,如果这个值与预定义的阈值不同,我们就为观察点分配一个不同的颜色值。
在结果图像中,这个点将成为边缘点。
 
程序中实现的上述算法的一个变体是,我们不观察颜色,而是观察图像中相反点对的亮度值的差异。如果亮度差的最大值小于亮度的下限,则该点将其基本颜色更改为黑色;如果大于亮度的上限值,则该点将其基本颜色更改为白色。
 

关注点

尽管 两个 算法的 伪代码 差异很小,但在算法实现上存在显著差异。我们没有使用 unsafe 代码和指针直接访问锁定的位图数据,而是使用了 System.Runtime.Interopservices 中的 Marshal.Copy 方法,该方法允许从托管数组快速安全地复制数据到非托管内存指针,或从非托管内存指针复制到托管数组。

将位图数据从非托管内存指针复制到托管的二维字节数组后,我们使用该数组及其像素颜色 RGB 分量的值来进行边缘检测。

图像处理完成后,新的位图数据被复制从数组到非托管内存指针,并将结果图像显示给用户。

最重要的是,图像处理速度相同,无需考虑使用 unsafe 代码的安全性问题。结果不同,质量取决于许多参数,原始位图图像的分辨率(越高越好,越慢 / 越低越差越快)颜色数量及其 色调(越少越好 / 越多越差)等。

使用代码

最好的方法是下载完整的解决方案,该方案是用 IDE #Develop ver. 4.4,语言 C# ver 4.0 编写的。Microsoft .Net framework 4.0,其中包含完整的程序代码,以及所有必要的图形用户界面控件,可用于构建可执行版本的程序。这里仅展示了轮廓检测的主方法。在此方法中,使用了一个全局变量和几个在该方法外部定义的控件。

  • Original_image Bitmap 类型  用于存储来自文件的原始图像,
  • Lower_Brightness_Limit NumericUpDown 类型  用于选择亮度下限值
  • Upper_Brightness_Limit NumericUpDown 类型  用于选择亮度上限值
  • Invert_Edge_Color CheckBox 类型  用于选择检测到的边缘颜色类型
  • Black_White RadioButton 类型  用于选择结果图像颜色类型
  • Gray_Scale RadioButton 类型  用于选择结果图像颜色类型
  • Image_2 PictureBox 类型   用于向用户显示结果图像

如果您想使用此处提供的此方法,则上述变量和控件必须定义在您的程序中。

        void Start_Contour_Detection()
        {
            // Copy original image to new bitmap
            Bitmap bmp = (Bitmap)Original_image.Clone();

            int Image_width = bmp.Width;
            int Image_height = bmp.Height;
            
            // Rectangle size
            Rectangle Rectangle = new Rectangle(0, 0, Image_width, Image_height);
            
            // Lock bitmap inside memory for faster processing and load bitmap data
            BitmapData bmp_Data = bmp.LockBits(Rectangle, ImageLockMode.ReadWrite, bmp.PixelFormat);
            
            
            // Load adress of the first byte of the bitmap,
            // it is pointer to the adress
            IntPtr bmp_First_byte_adress = bmp_Data.Scan0;

            
            //
            // Declaration of the matrix that should contain all bytes of the bitmap data
            //
            // Bitmap must contain 24 bits (three bytes) per pixel
            // One byte for each component of RGB color of pixels
            int Number_of_pixels = Image_width * Image_height;
            int Number_of_bytes  = Number_of_pixels * 3;
            //
            // Because of the way the bitmap data are stored in the memory, 
            // so called 'bytes fine alignment inside the row',
            // number of bytes in a row is rounded up to the nearest number divisible by four
            // So that one row of the bitmap is always containing the same or larger number of bytes,
            // that is necessary for memorising data for the actual number of points in row
            int Exact_number_of_bytes_in_row  = bmp_Data.Stride;
            int Necessary_number_of_bytes = Image_width*3;
            //
            int Number_of_alignment_bytes =  Exact_number_of_bytes_in_row - Necessary_number_of_bytes;
            //
            // Total count of bytes necesary for all image pixels
            Number_of_bytes += Image_height * Number_of_alignment_bytes;
            //
            // One dimensional matrix for memorizing bitmap
            // values of RGB components of color of pixels
            byte[] bmp_RGB_values = new byte[Number_of_bytes];
            
            
            // Copy values of RGB components of pixel color from bitmap to matrix
            Marshal.Copy(bmp_First_byte_adress, bmp_RGB_values, 0, Number_of_bytes);
            
            
            
            // Two dimensional matrix for memorizing bitmap
            // values of RGB components of color of pixels
            byte [,,] RGB = new byte[Image_width,Image_height,3];
            
            // Matrix for memorizing values of brightness of pixels
            float [,] Brightness = new float[Image_width,Image_height];
            
            // Byte counter inside one dimenzional matrix
            int bmp_k = 0;
            
            
            // Copy bitmap values of RGB components of color of pixels,
            // from one dimenzional to two dimenzional matrix
            // and fill matrix Brightness with values of brightness of pixels
            //
            // NOTICE :
            // When loading bitmap data
            // BitmapData bmp_Data = bmp.LockBits(Rectangle, ImageLockMode.ReadWrite, bmp.PixelFormat); ,
            // values of RGB components of color of pixels are returned in opposite direction
            // RGB -> BGR
            //
            for (int i=0;i < Image_height;i++)
            {
                for(int j=0;j < Image_width;j++)
                {
                    // Value of R component of pixel color
                    RGB[j,i,0] = bmp_RGB_values[bmp_k+2];
                    
                    // Value of G component of pixel color
                    RGB[j,i,1] = bmp_RGB_values[bmp_k+1];
                    
                    // Value of B component of pixel color
                    RGB[j,i,2] = bmp_RGB_values[bmp_k+0];
                    
                    // Value of pixel brightness
                    Brightness[j,i] = Color.FromArgb
                        (
                            bmp_RGB_values[bmp_k+2],
                            bmp_RGB_values[bmp_k+1],
                            bmp_RGB_values[bmp_k+0]
                        ).GetBrightness();
                    
                    bmp_k+=3;
                }
                
                bmp_k+= Number_of_alignment_bytes;
            }


            // Load lower and upper limit of the brightness 
            float lower_limit  = (float) Lower_Brightness_Limit.Value;
            float upper_limit  = (float) Upper_Brightness_Limit.Value;
            
            // Maximum found value for the difference in brightness 
            // between the opposing pixels
            float mfd = 0;
            
            for(int i=1;i < Image_height-1;i++)
            {
                for(int j=1;j < Image_width-1;j++)
                {
                    
                    //
                    mfd = Math.Abs(Brightness[j-1,i-1]-Brightness[j+1,i+1]);
                    
                    //
                    if(mfd < Math.Abs(Brightness[j-1,i+1]-Brightness[j+1,i-1]))
                        mfd=Math.Abs(Brightness[j-1,i+1]-Brightness[j+1,i-1]);
                    
                    //
                    if(mfd < Math.Abs(Brightness[j,i+1]-Brightness[j,i-1]))
                        mfd=Math.Abs(Brightness[j,i+1]-Brightness[j,i-1]);
                    
                    //
                    if(mfd < Math.Abs(Brightness[j-1,i]-Brightness[j+1,i]))
                        mfd=Math.Abs(Brightness[j-1,i]-Brightness[j+1,i]);
                    
                    //
                    if(Invert_Edge_Color.Checked)
                    {
                        if(mfd < lower_limit)
                        {
                            RGB[j,i,0] = (byte) 255;
                            RGB[j,i,1] = (byte) 255;
                            RGB[j,i,2] = (byte) 255;
                        }
                        else if(mfd > upper_limit)
                        {
                            RGB[j,i,0] = (byte) 0;
                            RGB[j,i,1] = (byte) 0;
                            RGB[j,i,2] = (byte) 0;
                        }
                    }
                    else
                    {
                        if(mfd < lower_limit)
                        {
                            RGB[j,i,0] = (byte) 0;
                            RGB[j,i,1] = (byte) 0;
                            RGB[j,i,2] = (byte) 0;
                        }
                        else if(mfd > upper_limit)
                        {
                            RGB[j,i,0] = (byte) 255;
                            RGB[j,i,1] = (byte) 255;
                            RGB[j,i,2] = (byte) 255;
                        }
                    }
                    
                }
            }
            
            
            if(Black_White.Checked)
            {
                for(int i=1;i < Image_height-1;i++)
                {
                    for(int j=1;j < Image_width-1;j++)
                    {
                        if(Invert_Edge_Color.Checked)
                        {
                            if(RGB[j,i,0] < 255 || RGB[j,i,1] < 255 || RGB[j,i,2] < 255)
                                RGB[j,i,0] = RGB[j,i,1] = RGB[j,i,2] = (byte) 0;
                        }
                        else
                        {
                            if(RGB[j,i,0] > 0 || RGB[j,i,1] > 0 || RGB[j,i,2] > 0)
                                RGB[j,i,0] = RGB[j,i,1] = RGB[j,i,2] = (byte) 255;
                        }
                    }
                }
            }
            
            
            if(Gray_Scale.Checked)
            {
                for(int i=1;i < Image_height-1;i++)
                {
                    for(int j=1;j < Image_width-1;j++)
                    {
                        RGB[j,i,0] = RGB[j,i,1] = RGB[j,i,2] =
                            (byte)
                            (
                                (0.299*RGB[j,i,0]) +
                                (0.587*RGB[j,i,1]) +
                                (0.114*RGB[j,i,2])
                            );
                    }
                }
            }
            
            
            // Byte counter inside one dimenzional matrix
            bmp_k = 0;
            
            // Copy new bitmap values of RGB components of color of pixels,
            // from two dimenzional to one dimenzional matrix
            for (int i=0;i < Image_height;i++)
            {
                for(int j=0;j < Image_width;j++)
                {
                    // Value of R component of pixel color
                    bmp_RGB_values[bmp_k+2] = RGB[j,i,0];
                    
                    // Value of G component of pixel color
                    bmp_RGB_values[bmp_k+1] = RGB[j,i,1];
                    
                    // Value of B component of pixel color
                    bmp_RGB_values[bmp_k+0] = RGB[j,i,2];
                    
                    bmp_k+=3;
                }
                
                bmp_k+=Number_of_alignment_bytes;
            }
            
            // Copy new values of RGB components of pixel color from matrix back to bitmap
            Marshal.Copy(bmp_RGB_values, 0, bmp_First_byte_adress, Number_of_bytes);
            
            // Unlock bitmap inside memory
            bmp.UnlockBits(bmp_Data);
            
            // Show the processed image
            Image_2.Image = bmp;
        }

历史

最后修改日期 2015.05.10. 19:00,作者

© . All rights reserved.