C# 类似PS的魔棒工具(1)

最近一段时间在开发一个画图软件,其中需要实现魔棒功能。网上浏览了一圈,没有找到。苦思之后,终于开窍了:。思路是:先用漫水填充算法, 获得一张连通区域的二值图。然后对这幅图进行边缘检测,获取边缘。如果使用emgucv或者opencv,可以直接使用函数floodFill()获得区域,再函数Canny()与FindContours()函数获得边界。

1.漫水填充

这里我不适用emgucv的方法,使用的是一个网友算法,改了一点点。

public Bitmap FloodFill(Bitmap src, Point location, Color fillColor, int threshould)
        {
            try
            {
                Bitmap srcbmp = src;
                Bitmap dstbmp = new Bitmap(src.Width,src.Height);
                int w = srcbmp.Width;
                int h = srcbmp.Height;
                Stack<Point> fillPoints = new Stack<Point>(w * h);
                System.Drawing.Imaging.BitmapData bmpData = srcbmp.LockBits(new Rectangle(0, 0, srcbmp.Width, srcbmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                System.Drawing.Imaging.BitmapData dstbmpData = dstbmp.LockBits(new Rectangle(0, 0, dstbmp.Width, dstbmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
           
                IntPtr ptr = bmpData.Scan0;
                int stride = bmpData.Stride;
                int bytes = bmpData.Stride * srcbmp.Height;
                byte[] grayValues = new byte[bytes];
                System.Runtime.InteropServices.Marshal.Copy(ptr, grayValues, 0, bytes);
                Color backColor = Color.FromArgb(grayValues[location.X * 3 + 2 + location.Y * stride], grayValues[location.X * 3 + 1 + location.Y * stride], grayValues[location.X * 3 + location.Y * stride]);

                IntPtr dstptr = dstbmpData.Scan0;
                byte[] temp = new byte[bytes];
                System.Runtime.InteropServices.Marshal.Copy(dstptr, temp, 0, bytes);

                int gray = (int)((backColor.R + backColor.G + backColor.B) / 3);
                if (location.X < 0 || location.X >= w || location.Y < 0 || location.Y >= h) return null;
                fillPoints.Push(new Point(location.X, location.Y));
                int[,] mask = new int[w, h];
                
                while (fillPoints.Count > 0)
                {
                    
                    Point p = fillPoints.Pop();
                    mask[p.X, p.Y] = 1;
                    temp[3 * p.X + p.Y * stride] = (byte)fillColor.B;
                    temp[3 * p.X + 1 + p.Y * stride] = (byte)fillColor.G;
                    temp[3 * p.X + 2 + p.Y * stride] = (byte)fillColor.R;
                    if (p.X > 0 && (Math.Abs(gray - (int)((grayValues[3 * (p.X - 1) + p.Y * stride] + grayValues[3 * (p.X - 1) + 1 + p.Y * stride] + grayValues[3 * (p.X - 1) + 2 + p.Y * stride]) / 3)) < threshould) && (mask[p.X - 1, p.Y] != 1))
                    {
                        temp[3 * (p.X - 1) + p.Y * stride] = (byte)fillColor.B;
                        temp[3 * (p.X - 1) + 1 + p.Y * stride] = (byte)fillColor.G;
                        temp[3 * (p.X - 1) + 2 + p.Y * stride] = (byte)fillColor.R;
                        fillPoints.Push(new Point(p.X - 1, p.Y));
                        mask[p.X - 1, p.Y] = 1;
                    }
                    if (p.X < w - 1 && (Math.Abs(gray - (int)((grayValues[3 * (p.X + 1) + p.Y * stride] + grayValues[3 * (p.X + 1) + 1 + p.Y * stride] + grayValues[3 * (p.X + 1) + 2 + p.Y * stride]) / 3)) < threshould) && (mask[p.X + 1, p.Y] != 1))
                    {
                        temp[3 * (p.X + 1) + p.Y * stride] = (byte)fillColor.B;
                        temp[3 * (p.X + 1) + 1 + p.Y * stride] = (byte)fillColor.G;
                        temp[3 * (p.X + 1) + 2 + p.Y * stride] = (byte)fillColor.R;
                        fillPoints.Push(new Point(p.X + 1, p.Y));
                        mask[p.X + 1, p.Y] = 1;
                    }
                    if (p.Y > 0 && (Math.Abs(gray - (int)((grayValues[3 * p.X + (p.Y - 1) * stride] + grayValues[3 * p.X + 1 + (p.Y - 1) * stride] + grayValues[3 * p.X + 2 + (p.Y - 1) * stride]) / 3)) < threshould) && (mask[p.X, p.Y - 1] != 1))
                    {
                        temp[3 * p.X + (p.Y - 1) * stride] = (byte)fillColor.B;
                        temp[3 * p.X + 1 + (p.Y - 1) * stride] = (byte)fillColor.G;
                        temp[3 * p.X + 2 + (p.Y - 1) * stride] = (byte)fillColor.R;
                        fillPoints.Push(new Point(p.X, p.Y - 1));
                        mask[p.X, p.Y - 1] = 1;
                    }
                    if (p.Y < h - 1 && (Math.Abs(gray - (int)((grayValues[3 * p.X + (p.Y + 1) * stride] + grayValues[3 * p.X + 1 + (p.Y + 1) * stride] + grayValues[3 * p.X + 2 + (p.Y + 1) * stride]) / 3)) < threshould) && (mask[p.X, p.Y + 1] != 1))
                    {
                        temp[3 * p.X + (p.Y + 1) * stride] = (byte)fillColor.B;
                        temp[3 * p.X + 1 + (p.Y + 1) * stride] = (byte)fillColor.G;
                        temp[3 * p.X + 2 + (p.Y + 1) * stride] = (byte)fillColor.R;
                        fillPoints.Push(new Point(p.X, p.Y + 1));
                        mask[p.X, p.Y + 1] = 1;
                    }
                }
                fillPoints.Clear();

                System.Runtime.InteropServices.Marshal.Copy(temp, 0, dstptr, bytes);
                srcbmp.UnlockBits(bmpData);
                dstbmp.UnlockBits(dstbmpData);

                return dstbmp;
            }
            catch (Exception exp)
            {
                MessageBox.Show(exp.Message);
                return null;
            }
        }
实际上这个函数时有缺陷的,转换位图数据时用了
System.Drawing.Imaging.PixelFormat.Format24bppRgb
这是不可取的。应该是32位argb。否则无法处理透明与黑色。这里灰度值使用平均值,我觉得应该使用PS开源程序的加权方法。

这里获得了一个连通的区域,实际上相当于一张掩码图。利用这张图,一是方便追踪边界,而是对原图进行掩码操作,进行分离等。

2.边缘追踪

使用边缘追踪算法,可以真正将边缘寻找出来,这样可以得到有序的点集合,可惜我不太理解EmguCV的CvInvoke.FindContours()获得的Emgu.CV.Util.VectorOfVectorOfPoint

是怎么个有序排列发,只好粗暴地显示出来算数。

最可靠的还那张掩码图,而不是这些莫名其妙的边界点。

private bool FloodFillTOcanny()
        {
            Image<Bgr, Byte> srcimg = new Image<Bgr, Byte>((Bitmap)pictureBox2.Image);
            //转成灰度图
            Image<Gray, Byte> grayimg =  srcimg.Convert<Gray, Byte>() ;
           // CvInvoke.BitwiseNot(grayimg, grayimg);
            //Canny 边缘检测
            Image<Gray, Byte> cannyGrayimg  = grayimg.Canny((int)numericUD_FloodFill.Value, (int)numericUD_FloodFill.Value);
            Gray bkGrayWhite = new Gray(255);
            Emgu.CV.IOutputArray hierarchy = new Image<Gray, Byte>(srcimg.Width, srcimg.Height, bkGrayWhite);
            Image<Rgb, Byte> imgresult = new Image<Rgb, Byte>(srcimg.Width, srcimg.Height, new Rgb(255, 255, 255));

            CvInvoke.FindContours(  cannyGrayimg,
                                    contours,
                                    (Emgu.CV.IOutputArray)hierarchy,
                                    Emgu.CV.CvEnum.RetrType.Ccomp,
                                    Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxNone//保存为
                                     );
            GraphicsPath myGraphicsPath = new GraphicsPath();
            Region myRegion = new Region();
            myGraphicsPath.Reset();
           
           

            int areaMax = 0, idx = 0 ;
            for (int ii = 0; ii < contours.Size ; ii++)
            {
                int area = (int)CvInvoke.ContourArea(contours[ii]);
                if (area > areaMax) { areaMax = area; idx = ii;}
                if (area < 1) continue;
                CvInvoke.DrawContours(imgresult, contours, ii, new MCvScalar(0, 0, 0), 1, Emgu.CV.CvEnum.LineType.EightConnected, (Emgu.CV.IInputArray)null, 2147483647);
                imageBox1.Image = imgresult;
                try
                {
                    myGraphicsPath.AddPolygon( contours[ii].ToArray() );
                }
                catch  
                {
                    //MessageBox.Show(e.Message);
                }
            }

            myRegion.MakeEmpty();
            myRegion.Union(myGraphicsPath);

            pictureBox1.Refresh();
            Pen pen = new Pen(Color.Red, 1);
            pen.DashStyle = DashStyle.Dot;
            Graphics gs = pictureBox1.CreateGraphics();
             gs.DrawPath(pen, myGraphicsPath);
             if (myRegion.IsVisible(lastPoint) )
            { 
               //gs.DrawPolygon(pen, respts);
            }
           else 
            {
               //gs.DrawRectangle(pen, new Rectangle(0,0,pictureBox1.Image.Width, pictureBox1.Image.Height));
           } 
            gs.Dispose();
            return true;
        }
这个函数也是不可取的,我只做一个演示,其中
CvInvoke.FindContours(  cannyGrayimg,
                                    contours,
                                    (Emgu.CV.IOutputArray)hierarchy,
                                    Emgu.CV.CvEnum.RetrType.Ccomp,
                                    Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxNone//保存方式
                                     );
函数最后的一个参数影响还是挺大的,使用是可以试试不同枚举参数;

3.测试

拉了几个控件,测试一番

我的鼠标猥琐地点了大腿那里,第二图显示漫水算法得到掩码图,最后一张图是边缘获取的结果,并且加到了原图上面。


源码:http://download.csdn.net/download/wangzibigan/10172940

(不知道怎么设成免费;文件28m其实大部分是emgucv库)

我的这篇文章其实没有实现魔棒功能,只是一个边缘的获取显示,所有我又写了第二篇。

我项目中的软件已经实现了魔棒功能,目前正在认真完善”羽化“功能。


  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值