unsafe HashSet <int> CreateLabelColorMapping() { HashSet <int> colors = new HashSet <int>(); for (int i = 0; i < denseMap.Length; i++) { denseMap[i] = 0; strokeMap[i] = -1; } using (BitmapIterator iter = new BitmapIterator(preBrushedImage, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)) { int *data = (int *)iter.PixelData; for (int y = 0; y < preBrushedImage.Height; y++) { for (int x = 0; x < preBrushedImage.Width; x++) { int idx = x + y * preBrushedImage.Width; int color = data[idx]; if (color != 0) { denseMap[idx] = hardness * K; strokeMap[idx] = color; colors.Add(color); } } } } return(colors); }
// 各ピクセルを左上端としてエッジ画像に充填できる正方形の大きさ // DPで求める unsafe int[] CalcBallSize(Bitmap mono, out int maxSize, out int minSize) { // 各ピクセルを左上端としてエッジ画像に充填できる正方形の大きさ // DPで求める maxSize = 0; minSize = int.MaxValue; int[] ballSize = new int[mono.Width * mono.Height]; using (BitmapIterator iter = new BitmapIterator(mono, ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed)) { byte *data = (byte *)iter.PixelData; // 右辺 for (int y = mono.Height - 1; y >= 0; y--) { int pixelIdx = mono.Width - 1 + y * iter.Stride; byte val = data[pixelIdx]; if (val <= edgeThreshold) { int ballIdx = mono.Width - 1 + y * mono.Width; ballSize[ballIdx] = 1; } } // 下辺 for (int x = mono.Width - 1; x >= 0; x--) { int pixelIdx = x + (mono.Height - 1) * iter.Stride; byte val = data[pixelIdx]; if (val <= edgeThreshold) { int ballIdx = x + (mono.Height - 1) * mono.Width; ballSize[ballIdx] = 1; } } // それ以外 for (int y = mono.Height - 2; y >= 0; y--) { for (int x = mono.Width - 2; x >= 0; x--) { int idx = x + y * iter.Stride; byte val = data[idx]; if (val <= 10) { int ballIdx = x + y * mono.Width; int ballIdxR = (x + 1) + y * mono.Width; int ballIdxB = x + (y + 1) * mono.Width; int ballIdxD = (x + 1) + (y + 1) * mono.Width; ballSize[ballIdx] = 1 + Math.Min(ballSize[ballIdxR], Math.Min(ballSize[ballIdxB], ballSize[ballIdxD])); maxSize = Math.Max(maxSize, ballSize[ballIdx]); minSize = Math.Min(minSize, ballSize[ballIdx]); } } } } return(ballSize); }
unsafe public static void Load(GraphicsDevice GraphicsDevice, System.Drawing.Bitmap bmp, out Texture2D texture, out Color[] texData, bool reverse = false) { if (GraphicsDevice == null || bmp == null) { texture = null; texData = null; return; } using (BitmapIterator iter = new BitmapIterator(bmp, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)) { Stopwatch sw = Stopwatch.StartNew(); texData = new Color[bmp.Width * bmp.Height]; byte *data = (byte *)iter.PixelData; int texIdx = 0; Console.WriteLine("0: " + sw.ElapsedMilliseconds + "ms"); sw.Restart(); for (int y = 0; y < bmp.Height; y++) { for (int x = 0; x < bmp.Width; x++) { int idx = !reverse ? 4 * x + y * iter.Stride : 4 * (bmp.Width - 1 - x) + y * iter.Stride; if (data[idx + 3] == 0) { texData[texIdx] = Color.Transparent; } else { texData[texIdx].R = data[idx + 2]; texData[texIdx].G = data[idx + 1]; texData[texIdx].B = data[idx + 0]; texData[texIdx].A = data[idx + 3]; } texIdx++; } } Console.WriteLine("1: " + sw.ElapsedMilliseconds + "ms"); sw.Restart(); texture = new Texture2D(GraphicsDevice, bmp.Width, bmp.Height); texture.SetData(texData); Console.WriteLine("2: " + sw.ElapsedMilliseconds + "ms"); sw.Restart(); } }
/// AnnotationSketch:f8bd4fff-3382-4282-b7b1-44f6236f3ff9.txt unsafe float SqColorDist(BitmapIterator iter, int x0, int y0, int x1, int y1) { byte *data = (byte *)iter.PixelData; int offset0 = 4 * x0 + y0 * iter.Stride; int offset1 = 4 * x1 + y1 * iter.Stride; byte r0 = data[offset0 + 2]; byte g0 = data[offset0 + 1]; byte b0 = data[offset0 + 0]; byte r1 = data[offset1 + 2]; byte g1 = data[offset1 + 1]; byte b1 = data[offset1 + 0]; float dr = r1 - r0; float dg = g1 - g0; float db = b1 - b0; float sqDist = dr * dr + dg * dg + db * db; return(sqDist); }
unsafe void FlashArrayToBitmap(int[] array, int w, int h, Bitmap bmp) { using (var iter = new BitmapIterator(bmp, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb)) { byte *data = (byte *)iter.PixelData; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { int idx = 4 * x + y * iter.Stride; int arIdx = x + y * w; data[idx + 0] = (byte)array[arIdx]; data[idx + 1] = (byte)array[arIdx]; data[idx + 2] = (byte)array[arIdx]; data[idx + 3] = (byte)255; } } } }
unsafe public void Execute() { HashSet <int> colors = CreateLabelColorMapping(); CalcEdgeWeights(); for (int i = 0; i < labelMap.Length; i++) { labelMap[i] = -1; } foreach (int color in colors.Take(colors.Count - 1)) { UpdateEdgeWeights(); IntPtr grid = CreateGraph(edgeImage.Width, edgeImage.Height); SetEdgeWeights(grid, edgeImage.Width, edgeImage.Height, edgeHorizon, edgeVertical); Execute(grid, color, edgeImage.Width, edgeImage.Height, denseMap, strokeMap, labelMap); DeleteGraph(grid); } using (BitmapIterator brushIter = new BitmapIterator(brushedImage, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb)) // using (BitmapIterator preBrushIter = new BitmapIterator(preBrushedImage, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)) using (BitmapIterator intIter = new BitmapIterator(edgeImage, ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed)) { int *data = (int *)brushIter.PixelData; // int* predata = (int*)preBrushIter.PixelData; byte *intData = (byte *)intIter.PixelData; for (int y = 0; y < brushedImage.Height; y++) { for (int x = 0; x < brushedImage.Width; x++) { int idx = x + y * brushedImage.Width; int intIdx = x + y * intIter.Stride; data[idx] = labelMap[idx]; // intData[intIdx] >= 10 ? intData[intIdx] : labelMap[idx]; } } } }
unsafe void CalcEdgeWeights() { using (BitmapIterator iter = new BitmapIterator(edgeImage, ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed)) { byte *data = (byte *)iter.PixelData; for (int y = 0; y < edgeImage.Height; y++) { for (int x = 0; x < edgeImage.Width - 1; x++) { edgeHorizon[x + y * edgeImage.Width] = 1 + K * Math.Pow(data[x + y * iter.Stride] * colorScale, gamma); } } for (int x = 0; x < edgeImage.Width; x++) { for (int y = 0; y < edgeImage.Height - 1; y++) { edgeVertical[x + y * edgeImage.Width] = 1 + K * Math.Pow(data[x + y * iter.Stride] * colorScale, gamma); } } } }
void AddToContour(HashSet <int> uncoloredPixels, PriorityQueue <ContourPixel> contour, int[] labelMap, int x, int y, ContourPixel src, BitmapIterator orgIter, float threshold) { int idx = x + y * edgeImage.Width; if (uncoloredPixels.Contains(idx) && SqColorDist(orgIter, src.x, src.y, x, y) <= threshold) { contour.Push(new ContourPixel(x, y, 0)); labelMap[idx] = labelMap[src.x + src.y * edgeImage.Width]; uncoloredPixels.Remove(idx); } }
/// AnnotationSketch:regiongrowing.png unsafe void RegionGrowing(HashSet <int> uncoloredPixels, int[] labelMap, BitmapIterator orgIter) { float threshold = sqColorDistThreshold; PriorityQueue <ContourPixel> contour = new PriorityQueue <ContourPixel>(); while (uncoloredPixels.Count >= 1 && threshold <= float.MaxValue / 4) { foreach (int idx in uncoloredPixels) { int x, y; y = Math.DivRem(idx, edgeImage.Width, out x); if (0 <= x - 1 && !uncoloredPixels.Contains(idx - 1) && labelMap[idx - 1] >= 1) { contour.Push(new ContourPixel(x - 1, y, 0)); } if (0 <= y - 1 && !uncoloredPixels.Contains(idx - edgeImage.Width) && labelMap[idx - edgeImage.Width] >= 1) { contour.Push(new ContourPixel(x, y - 1, 0)); } if (edgeImage.Width > x + 1 && !uncoloredPixels.Contains(idx + 1) && labelMap[idx + 1] >= 1) { contour.Push(new ContourPixel(x + 1, y, 0)); } if (edgeImage.Height > y + 1 && !uncoloredPixels.Contains(idx + edgeImage.Width) && labelMap[idx + edgeImage.Width] >= 1) { contour.Push(new ContourPixel(x, y + 1, 0)); } } Console.WriteLine("init contour: done"); while (contour.Count >= 1) { ContourPixel cpt = contour.Top; contour.Pop(); if (cpt.error >= sqColorDistThreshold) { break; } AddToContour(uncoloredPixels, contour, labelMap, cpt.x - 1, cpt.y, cpt, orgIter, threshold); AddToContour(uncoloredPixels, contour, labelMap, cpt.x + 1, cpt.y, cpt, orgIter, threshold); AddToContour(uncoloredPixels, contour, labelMap, cpt.x, cpt.y - 1, cpt, orgIter, threshold); AddToContour(uncoloredPixels, contour, labelMap, cpt.x, cpt.y + 1, cpt, orgIter, threshold); } Console.WriteLine("region growing: done (" + uncoloredPixels.Count + ", " + threshold + ")"); threshold *= 2; } List <byte>[] r = new List <byte> [segmentCnt]; List <byte>[] g = new List <byte> [segmentCnt]; List <byte>[] b = new List <byte> [segmentCnt]; int[] c = new int[segmentCnt]; byte * orgData = (byte *)orgIter.PixelData; for (int y = 0; y < edgeImage.Height; y++) { for (int x = 0; x < edgeImage.Width; x++) { int label = labelMap[x + y * edgeImage.Width]; if (1 <= label && label < segmentCnt) { if (r[label] == null) { r[label] = new List <byte>(); g[label] = new List <byte>(); b[label] = new List <byte>(); } r[label].Add(orgData[4 * x + y * orgIter.Stride + 0]); g[label].Add(orgData[4 * x + y * orgIter.Stride + 1]); b[label].Add(orgData[4 * x + y * orgIter.Stride + 2]); c[label]++; } } } for (int i = 1; i < r.Length; i++) { if (c[i] >= 1) { r[i].Sort(); g[i].Sort(); b[i].Sort(); } } // 隣接している似た色の領域をまとめる Dictionary <int, int> segmentCorrespondence = new Dictionary <int, int>(); /* * int id = 0; * for (int y = 0; y < edgeImage.Height; y++) * { * for (int x = 0; x < edgeImage.Width; x++) * { * if (x + 1 < edgeImage.Width && IsSameRegion(labelMap[id], labelMap[id + 1], r, g, b)) * { * segmentCorrespondence[Math.Max(labelMap[id], labelMap[id + 1])] = Math.Min(labelMap[id], labelMap[id + 1]); * } * if (y + 1 < edgeImage.Height && IsSameRegion(labelMap[id], labelMap[id + edgeImage.Width], r, g, b)) * { * segmentCorrespondence[Math.Max(labelMap[id], labelMap[id + edgeImage.Width])] = Math.Min(labelMap[id], labelMap[id + edgeImage.Width]); * } * id++; * } * } * for (int i = 0; i < labelMap.Length; i++) * while (segmentCorrespondence.ContainsKey(labelMap[i])) * labelMap[i] = segmentCorrespondence[labelMap[i]]; */ }
unsafe bool CanFill(int idx, int idxSeg, int ptX, int ptY, int srcX, int srcY, HashSet <int> filled, HashSet <int> uncoloredPixels, IplImage segmentImage, BitmapIterator orgIter, Stopwatch sw2 = null) { // sw2.Start(); if (!uncoloredPixels.Contains(idx)) { // sw2.Stop(); return(false); } if (filled.Contains(idx)) { // sw2.Stop(); return(false); } if (ptX < 0 || edgeImage.Width <= ptX) { // sw2.Stop(); return(false); } if (ptY < 0 || edgeImage.Height <= ptY) { // sw2.Stop(); return(false); } if (segmentImage.ImageDataPtr[idxSeg] != 0) { // sw2.Stop(); return(false); } if (SqColorDist(orgIter, srcX, srcY, ptX, ptY) >= sqColorDistThreshold) { // sw2.Stop(); return(false); } // sw2.Stop(); return(true); }
/// AnnotationSketch:floodfill2.png unsafe public CvRect FloodFill(HashSet <int> initPoints, IplImage segmentImage, Color c, HashSet <int> uncoloredPixels, BitmapIterator orgIter, Stopwatch sw2 = null) { int roiMinX = int.MaxValue; int roiMinY = int.MaxValue; int roiMaxX = int.MinValue; int roiMaxY = int.MinValue; List <int> curs = initPoints.ToList(); List <int> nexts = new List <int>(); HashSet <int> filled = new HashSet <int>(initPoints); byte * partData = segmentImage.ImageDataPtr; while (curs.Count > 0) { for (int i = 0; i < curs.Count; i++) { int src = curs[i]; int x, y; y = Math.DivRem(src, edgeImage.Width, out x); if (x < roiMinX) { roiMinX = x; } if (x > roiMaxX) { roiMaxX = x; } if (y < roiMinY) { roiMinY = y; } if (y > roiMaxY) { roiMaxY = y; } int offset = 4 * x + y * segmentImage.WidthStep; if (CanFill(src - 1, offset - 4 + 3, x - 1, y, x, y, filled, uncoloredPixels, segmentImage, orgIter, sw2)) { nexts.Add(src - 1); filled.Add(src - 1); } if (CanFill(src + 1, offset + 4 + 3, x + 1, y, x, y, filled, uncoloredPixels, segmentImage, orgIter, sw2)) { nexts.Add(src + 1); filled.Add(src + 1); } if (CanFill(src - edgeImage.Width, offset - segmentImage.WidthStep + 3, x, y - 1, x, y, filled, uncoloredPixels, segmentImage, orgIter, sw2)) { nexts.Add(src - segmentImage.Width); filled.Add(src - segmentImage.Width); } if (CanFill(src + edgeImage.Width, offset + segmentImage.WidthStep + 3, x, y + 1, x, y, filled, uncoloredPixels, segmentImage, orgIter, sw2)) { nexts.Add(src + segmentImage.Width); filled.Add(src + segmentImage.Width); } partData[offset + 0] = c.B; partData[offset + 1] = c.G; partData[offset + 2] = c.R; partData[offset + 3] = 255; } curs.Clear(); FMath.Swap(ref curs, ref nexts); } return(new CvRect( Math.Max(0, roiMinX - 1), Math.Max(0, roiMinY - 1), Math.Min(edgeImage.Width, roiMaxX - roiMinX + 2), Math.Min(edgeImage.Height, roiMaxY - roiMinY + 2))); }
unsafe List <IplImage> MoveBall(HashSet <int> orgUncoloredPixels, int[] labelMap, int radius, BitmapIterator edgeIter, BitmapIterator orgIter, int[] ballSizeList) { System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); System.Diagnostics.Stopwatch sw2 = new Stopwatch(); List <IplImage> segments = new List <IplImage>(); byte * edgeData = (byte *)edgeIter.PixelData; int ballSize = 1 + 2 * radius; HashSet <int> uncoloredPixels = new HashSet <int>(orgUncoloredPixels); Queue <int> uncoloredPixelList = new Queue <int>(orgUncoloredPixels); while (uncoloredPixels.Count >= 1) { int idx = uncoloredPixelList.Dequeue(); if (!uncoloredPixels.Contains(idx)) { continue; } uncoloredPixels.Remove(idx); int x, y; y = Math.DivRem(idx, edgeImage.Width, out x); // ボールが入るか if (ballSizeList[idx] < ballSize) { continue; } // 彩色済みなら塗らない bool colored = false; for (int yy = y; yy < y + ballSize; yy++) { for (int xx = x; xx < x + ballSize; xx++) { if (!(y == yy && x == xx) && !uncoloredPixels.Contains(xx + yy * edgeImage.Width)) { colored = true; goto COLORED_DECIDED; } } } COLORED_DECIDED: if (colored) { continue; } // 新しいセグメント IplImage segmentImage = new IplImage(edgeImage.Width, edgeImage.Height, BitDepth.U8, 4); Cv.Set(segmentImage, new CvScalar(0, 0, 0, 0)); Random rand = new Random(); Color c = Color.FromArgb(rand.Next(255) + 1, rand.Next(255) + 1, rand.Next(255) + 1); Cv.Rectangle(segmentImage, x + 1, y + 1, x + ballSize - 1, y + ballSize - 1, Cv.RGB(c.R, c.G, c.B)); // 新しい色でFlood fill HashSet <int> initPoints = new HashSet <int>(); for (int yy = y; yy < y + ballSize - 1; yy++) { initPoints.Add(x + yy * edgeImage.Width); initPoints.Add((x + ballSize - 1) + yy * edgeImage.Width); } for (int xx = x; xx < x + ballSize - 1; xx++) { initPoints.Add(xx + y * edgeImage.Width); initPoints.Add(xx + (y + ballSize - 1) * edgeImage.Width); } CvRect roi = FloodFill(initPoints, segmentImage, c, uncoloredPixels, orgIter, sw2); sw2.Start(); Cv.SetImageROI(segmentImage, roi); { IplConvKernel kernel = new IplConvKernel(2 * radius + 1, 2 * radius + 1, radius, radius, ElementShape.Ellipse); Cv.Erode(segmentImage, segmentImage, kernel, 1); Cv.Dilate(segmentImage, segmentImage, kernel, 1); } Cv.ResetImageROI(segmentImage); for (int yy = roi.Y; yy < roi.Bottom; yy++) { for (int xx = roi.X; xx < roi.Right; xx++) { if (segmentImage.ImageDataPtr[4 * xx + yy * segmentImage.WidthStep + 3] != 0) { int idx2 = xx + yy * edgeImage.Width; labelMap[idx2] = segmentCnt; uncoloredPixels.Remove(idx2); orgUncoloredPixels.Remove(idx2); } } } segmentCnt++; segments.Add(segmentImage); sw2.Stop(); } //if (segments.Count >= 1) segments.First().Save(radius + ".png"); // if (sw.ElapsedMilliseconds >= 1000) // { // Console.WriteLine("[radius=" + radius + "]"); // Console.WriteLine("total= " + sw.ElapsedMilliseconds + " ms"); // Console.WriteLine("total= " + sw2.ElapsedMilliseconds + " ms"); // } return(segments); }
unsafe public void Execute() { const int deltaRadius = 3; segmentCnt = 1; // 充填できるボールサイズを事前計算 int maxBallSize, minBallSize; int[] ballSizeList = CalcBallSize(edgeImage, out maxBallSize, out minBallSize); int maxRadius = ((maxBallSize / 2) / deltaRadius - 1) * deltaRadius; int minRadius = ((minBallSize / 2) / deltaRadius + 1) * deltaRadius; // FlashArrayToBitmap(ballSizeList, edgeImage.Width, edgeImage.Height, brushedImage); // 出力 Dictionary <IplImage, int> segmentList = new Dictionary <IplImage, int>(); int[] labelMap = new int[edgeImage.Width * edgeImage.Height]; // 塗るべきピクセルを追加 HashSet <int> uncoloredPixels = new HashSet <int>(); using (BitmapIterator edgeIter = new BitmapIterator(edgeImage, ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed)) { byte *edgeData = (byte *)edgeIter.PixelData; int i = 0; for (int y = 0; y < edgeImage.Height; y++) { for (int x = 0; x < edgeImage.Width; x++) { if (edgeData[x + y * edgeIter.Stride] <= edgeThreshold) { uncoloredPixels.Add(i); } else { // エッジは取り除く labelMap[i] = -1; } i++; } } } // エッジの間に大雑把に色を塗っていく using (BitmapIterator edgeIter = new BitmapIterator(edgeImage, ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed)) using (BitmapIterator orgIter = new BitmapIterator(orgImage, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)) { byte *edgeData = (byte *)edgeIter.PixelData; byte *orgData = (byte *)orgIter.PixelData; // ボールを徐々に小さくしながらTrappedBallSegmentation for (int radius = maxRadius; radius >= Math.Max(10, minRadius); radius -= deltaRadius) { List <IplImage> segments = MoveBall(uncoloredPixels, labelMap, radius, edgeIter, orgIter, ballSizeList); for (int i = 0; i < segments.Count; i++) { segmentList[segments[i]] = radius; } } Console.WriteLine("move ball: done"); int cnt = labelMap.Count(val => val == 0); Debug.Assert(cnt == uncoloredPixels.Count); RegionGrowing(uncoloredPixels, labelMap, orgIter); Dictionary <int, Color> colors = new Dictionary <int, Color>(); colors[-1] = Color.Black; colors[0] = Color.Black; List <byte>[] r = new List <byte> [segmentList.Count]; List <byte>[] g = new List <byte> [segmentList.Count]; List <byte>[] b = new List <byte> [segmentList.Count]; int[] c = new int[segmentList.Count]; for (int y = 0; y < edgeImage.Height; y++) { for (int x = 0; x < edgeImage.Width; x++) { int label = labelMap[x + y * edgeImage.Width]; if (1 <= label && label < segmentList.Count) { if (r[label] == null) { r[label] = new List <byte>(); g[label] = new List <byte>(); b[label] = new List <byte>(); } r[label].Add(orgData[4 * x + y * orgIter.Stride + 0]); g[label].Add(orgData[4 * x + y * orgIter.Stride + 1]); b[label].Add(orgData[4 * x + y * orgIter.Stride + 2]); c[label]++; } } } Random rand = new Random(); for (int i = 1; i < segmentList.Count; i++) { if (c[i] >= 1) { r[i].Sort(); g[i].Sort(); b[i].Sort(); if (randColorMode) { colors[i] = Color.FromArgb( rand.Next(155) + 100, rand.Next(155) + 100, rand.Next(155) + 100); } else { colors[i] = Color.FromArgb( r[i][r[i].Count / 2], g[i][g[i].Count / 2], b[i][b[i].Count / 2]); } } } using (BitmapIterator brushedIter = new BitmapIterator(brushedImage, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb)) { byte *brushedData = (byte *)brushedIter.PixelData; for (int y = 0; y < edgeImage.Height; y++) { /// AnnotationSketch:83892581-23e5-484e-928a-ea6d4ca4892d.txt for (int x = 0; x < edgeImage.Width; x++) { int label = labelMap[x + y * edgeImage.Width]; if (!colors.ContainsKey(label)) { colors[label] = Color.FromArgb( orgData[4 * x + y * orgIter.Stride + 0], orgData[4 * x + y * orgIter.Stride + 1], orgData[4 * x + y * orgIter.Stride + 2]); } brushedData[4 * x + y * brushedIter.Stride + 0] = colors[label].R; brushedData[4 * x + y * brushedIter.Stride + 1] = colors[label].G; brushedData[4 * x + y * brushedIter.Stride + 2] = colors[label].B; brushedData[4 * x + y * brushedIter.Stride + 3] = 255; } } } } }