C# 类似PS的魔棒工具(1)
来源:互联网 发布:在线字体生成网站源码 编辑:程序博客网 时间:2024/05/21 08:14
最近一段时间在开发一个画图软件,其中需要实现魔棒功能。网上浏览了一圈,没有找到。苦思之后,终于开窍了:。思路是:先用漫水填充算法, 获得一张连通区域的二值图。然后对这幅图进行边缘检测,获取边缘。如果使用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库)
我的这篇文章其实没有实现魔棒功能,只是一个边缘的获取显示,所有我又写了第二篇。
我项目中的软件已经实现了魔棒功能,目前正在认真完善”羽化“功能。
阅读全文