/// <summary> /// Constructor (copy) /// </summary> public CvBlobs(IEnumerable <KeyValuePair <int, CvBlob> > blobData, int[,] labelData) { foreach (KeyValuePair <int, CvBlob> pair in blobData) { Add(pair.Key, pair.Value); } Labels = new LabelData(labelData); }
/// <summary> /// Label the connected parts of a binary image. (cvLabel) /// </summary> /// <param name="img">Input binary image (depth=IPL_DEPTH_8U and num. channels=1).</param> /// <returns>Number of pixels that has been labeled.</returns> public int Label(IplImage img) { if (img == null) { throw new ArgumentNullException("img"); } Labels = new LabelData(img.Height, img.Width, img.ROI); return(Labeller.Perform(img, this)); }
/// <summary> /// Label the connected parts of a binary image. (cvLabel) /// </summary> /// <param name="img">Input binary image (depth=IPL_DEPTH_8U and num. channels=1).</param> /// <returns>Number of pixels that has been labeled.</returns> public int Label(Mat img) { if (img == null) { throw new ArgumentNullException(nameof(img)); } Labels = new LabelData(img.Height, img.Width); return(Labeller.Perform(img, this)); }
/// <summary> /// Draws or prints information about a blob. /// </summary> /// <param name="labels">Label data.</param> /// <param name="blob">Blob.</param> /// <param name="imgSource">Input image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="imgDest">Output image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="mode">Render mode. By default is CV_BLOB_RENDER_COLOR|CV_BLOB_RENDER_CENTROID|CV_BLOB_RENDER_BOUNDING_BOX|CV_BLOB_RENDER_ANGLE.</param> /// <param name="color">Color to render (if CV_BLOB_RENDER_COLOR is used).</param> /// <param name="alpha">If mode CV_BLOB_RENDER_COLOR is used. 1.0 indicates opaque and 0.0 translucent (1.0 by default).</param> public static void RenderBlob(LabelData labels, CvBlob blob, IplImage imgSource, IplImage imgDest, RenderBlobsMode mode, CvScalar color, double alpha) { if (labels == null) { throw new ArgumentNullException("labels"); } if (blob == null) { throw new ArgumentNullException("blob"); } if (imgSource == null) { throw new ArgumentNullException("imgSource"); } if (imgDest == null) { throw new ArgumentNullException("imgDest"); } BlobRenderer.PerformOne(labels, blob, imgSource, imgDest, mode, color, alpha); }
/// <summary> /// Draws or prints information about a blob. /// </summary> /// <param name="labels">Label data.</param> /// <param name="blob">Blob.</param> /// <param name="imgSource">Input image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="imgDest">Output image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="mode">Render mode. By default is CV_BLOB_RENDER_COLOR|CV_BLOB_RENDER_CENTROID|CV_BLOB_RENDER_BOUNDING_BOX|CV_BLOB_RENDER_ANGLE.</param> /// <param name="color">Color to render (if CV_BLOB_RENDER_COLOR is used).</param> /// <param name="alpha">If mode CV_BLOB_RENDER_COLOR is used. 1.0 indicates opaque and 0.0 translucent (1.0 by default).</param> public static void RenderBlob(LabelData labels, CvBlob blob, Mat imgSource, Mat imgDest, RenderBlobsMode mode, Scalar color, double alpha = 1.0) { if (labels == null) { throw new ArgumentNullException(nameof(labels)); } if (blob == null) { throw new ArgumentNullException(nameof(blob)); } if (imgSource == null) { throw new ArgumentNullException(nameof(imgSource)); } if (imgDest == null) { throw new ArgumentNullException(nameof(imgDest)); } BlobRenderer.PerformOne(labels, blob, imgSource, imgDest, mode, color, alpha); }
/// <summary> /// Draws or prints information about a blob. /// </summary> /// <param name="labels">Label data.</param> /// <param name="blob">Blob.</param> /// <param name="imgSource">Input image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="imgDest">Output image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="mode">Render mode. By default is CV_BLOB_RENDER_COLOR|CV_BLOB_RENDER_CENTROID|CV_BLOB_RENDER_BOUNDING_BOX|CV_BLOB_RENDER_ANGLE.</param> /// <param name="color">Color to render (if CV_BLOB_RENDER_COLOR is used).</param> public static void RenderBlob(LabelData labels, CvBlob blob, IplImage imgSource, IplImage imgDest, RenderBlobsMode mode, CvScalar color) { RenderBlob(labels, blob, imgSource, imgDest, mode, color, 1.0); }
/// <summary> /// Draws or prints information about a blob. /// </summary> /// <param name="labels">Label data.</param> /// <param name="blob">Blob.</param> /// <param name="imgSource">Input image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="imgDest">Output image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="mode">Render mode. By default is CV_BLOB_RENDER_COLOR|CV_BLOB_RENDER_CENTROID|CV_BLOB_RENDER_BOUNDING_BOX|CV_BLOB_RENDER_ANGLE.</param> public static void RenderBlob(LabelData labels, CvBlob blob, IplImage imgSource, IplImage imgDest, RenderBlobsMode mode) { RenderBlob(labels, blob, imgSource, imgDest, mode, CvColor.White, 1.0); }
/// <summary> /// Draws or prints information about a blob. /// </summary> /// <param name="labels">Label data.</param> /// <param name="blob">Blob.</param> /// <param name="imgSource">Input image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="imgDest">Output image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="mode">Render mode. By default is CV_BLOB_RENDER_COLOR|CV_BLOB_RENDER_CENTROID|CV_BLOB_RENDER_BOUNDING_BOX|CV_BLOB_RENDER_ANGLE.</param> public static void RenderBlob(LabelData labels, CvBlob blob, Mat imgSource, Mat imgDest, RenderBlobsMode mode) { RenderBlob(labels, blob, imgSource, imgDest, mode, Scalar.White, 1.0); }
/// <summary> /// /// </summary> /// <param name="labels"></param> /// <param name="blob"></param> /// <param name="imgSrc"></param> /// <param name="imgDst"></param> /// <param name="mode"></param> /// <param name="color"></param> /// <param name="alpha"></param> public static unsafe void PerformOne(LabelData labels, CvBlob blob, Mat imgSrc, Mat imgDst, RenderBlobsMode mode, Scalar color, double alpha) { if (labels == null) throw new ArgumentNullException("labels"); if (blob == null) throw new ArgumentNullException("blob"); if (imgSrc == null) throw new ArgumentNullException("imgSrc"); if (imgDst == null) throw new ArgumentNullException("imgDst"); if (imgDst.Type() != MatType.CV_8UC3) throw new ArgumentException("'img' must be a 3-channel U8 image."); if ((mode & RenderBlobsMode.Color) == RenderBlobsMode.Color) { var pSrc = imgSrc.GetGenericIndexer<Vec3b>(); var pDst = imgDst.GetGenericIndexer<Vec3b>(); for (int r = blob.MinY; r < blob.MaxY; r++) { for (int c = blob.MinX; c < blob.MaxX; c++) { if (labels[r, c] == blob.Label) { byte v0 = (byte) ((1.0 - alpha)*pSrc[r, c].Item0 + alpha*color.Val0); byte v1 = (byte) ((1.0 - alpha)*pSrc[r, c].Item1 + alpha*color.Val1); byte v2 = (byte) ((1.0 - alpha)*pSrc[r, c].Item2 + alpha*color.Val2); pDst[r, c] = new Vec3b(v0, v1, v2); } } } } if (mode != RenderBlobsMode.None) { if ((mode & RenderBlobsMode.BoundingBox) == RenderBlobsMode.BoundingBox) { Cv2.Rectangle( imgDst, new Point(blob.MinX, blob.MinY), new Point(blob.MaxX - 1, blob.MaxY - 1), new Scalar(255, 0, 0)); } if ((mode & RenderBlobsMode.Angle) == RenderBlobsMode.Angle) { double angle = blob.Angle(); double lengthLine = Math.Max(blob.MaxX - blob.MinX, blob.MaxY - blob.MinY) / 2.0; double x1 = blob.Centroid.X - lengthLine * Math.Cos(angle); double y1 = blob.Centroid.Y - lengthLine * Math.Sin(angle); double x2 = blob.Centroid.X + lengthLine * Math.Cos(angle); double y2 = blob.Centroid.Y + lengthLine * Math.Sin(angle); Cv2.Line(imgDst, new Point((int)x1, (int)y1), new Point((int)x2, (int)y2), new Scalar(0, 255, 0)); } if ((mode & RenderBlobsMode.Centroid) == RenderBlobsMode.Centroid) { Cv2.Line(imgDst, new Point((int)blob.Centroid.X - 3, (int)blob.Centroid.Y), new Point((int)blob.Centroid.X + 3, (int)blob.Centroid.Y), new Scalar(255, 0, 0)); Cv2.Line(imgDst, new Point((int)blob.Centroid.X, (int)blob.Centroid.Y - 3), new Point((int)blob.Centroid.X, (int)blob.Centroid.Y + 3), new Scalar(255, 0, 0)); } } }
/// <summary> /// Draws or prints information about a blob. /// </summary> /// <param name="labels">Label data.</param> /// <param name="blob">Blob.</param> /// <param name="imgSource">Input image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="imgDest">Output image (depth=IPL_DEPTH_8U and num. channels=3).</param> public static void RenderBlob(LabelData labels, CvBlob blob, Mat imgSource, Mat imgDest) { RenderBlob(labels, blob, imgSource, imgDest, (RenderBlobsModes)0x000f, Scalar.White, 1.0); }
/// <summary> /// Constructor (copy) /// </summary> public CvBlobs(IEnumerable <KeyValuePair <int, CvBlob> > blobData, LabelData labelData) : this(blobData, labelData?.Values?.GetBuffer() ?? throw new ArgumentNullException(nameof(labelData))) { }
/// <summary> /// Draws or prints information about a blob. /// </summary> /// <param name="labels">Label data.</param> /// <param name="blob">Blob.</param> /// <param name="imgSource">Input image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="imgDest">Output image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="mode">Render mode. By default is CV_BLOB_RENDER_COLOR|CV_BLOB_RENDER_CENTROID|CV_BLOB_RENDER_BOUNDING_BOX|CV_BLOB_RENDER_ANGLE.</param> /// <param name="color">Color to render (if CV_BLOB_RENDER_COLOR is used).</param> /// <param name="alpha">If mode CV_BLOB_RENDER_COLOR is used. 1.0 indicates opaque and 0.0 translucent (1.0 by default).</param> public static void RenderBlob(LabelData labels, CvBlob blob, Mat imgSource, Mat imgDest, RenderBlobsMode mode, Scalar color, double alpha = 1.0) { if (labels == null) throw new ArgumentNullException(nameof(labels)); if (blob == null) throw new ArgumentNullException(nameof(blob)); if (imgSource == null) throw new ArgumentNullException(nameof(imgSource)); if (imgDest == null) throw new ArgumentNullException(nameof(imgDest)); BlobRenderer.PerformOne(labels, blob, imgSource, imgDest, mode, color, alpha); }
/// <summary> /// Draws or prints information about a blob. /// </summary> /// <param name="labels">Label data.</param> /// <param name="blob">Blob.</param> /// <param name="imgSource">Input image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="imgDest">Output image (depth=IPL_DEPTH_8U and num. channels=3).</param> /// <param name="mode">Render mode. By default is CV_BLOB_RENDER_COLOR|CV_BLOB_RENDER_CENTROID|CV_BLOB_RENDER_BOUNDING_BOX|CV_BLOB_RENDER_ANGLE.</param> /// <param name="color">Color to render (if CV_BLOB_RENDER_COLOR is used).</param> /// <param name="alpha">If mode CV_BLOB_RENDER_COLOR is used. 1.0 indicates opaque and 0.0 translucent (1.0 by default).</param> public static void RenderBlob(LabelData labels, CvBlob blob, IplImage imgSource, IplImage imgDest, RenderBlobsMode mode, CvScalar color, double alpha) { if (labels == null) throw new ArgumentNullException("labels"); if (blob == null) throw new ArgumentNullException("blob"); if (imgSource == null) throw new ArgumentNullException("imgSource"); if (imgDest == null) throw new ArgumentNullException("imgDest"); BlobRenderer.PerformOne(labels, blob, imgSource, imgDest, mode, color, alpha); }
/// <summary> /// /// </summary> /// <param name="img"></param> /// <param name="blobs"></param> /// <returns></returns> public static int Perform(IplImage img, CvBlobs blobs) { if (img == null) { throw new ArgumentNullException("img"); } if (blobs == null) { throw new ArgumentNullException("blobs"); } if (img.Depth != BitDepth.U8 || img.NChannels != 1) { throw new ArgumentException("'img' must be a 1-channel U8 image."); } LabelData labels = blobs.Labels; if (labels == null) { throw new ArgumentException(""); } //if(labels.GetLength(0) != h || labels.GetLength(1) != w) if (labels.Rows != img.Height || labels.Cols != img.Width) { throw new ArgumentException("img.Size != labels' size"); } int numPixels = 0; blobs.Clear(); int step = img.WidthStep; CvRect roi = img.ROI; int w = roi.Width; int h = roi.Height; int offset = 0; if (img.ROIPointer != IntPtr.Zero) { IplROI r = img.ROIValue; w = r.width; h = r.height; offset = r.xOffset + (r.yOffset * step); } byte[] imgIn; unsafe { byte *imgInPtr = img.ImageDataPtr + offset; imgIn = new byte[h * step]; Marshal.Copy(new IntPtr(imgInPtr), imgIn, 0, imgIn.Length); } int label = 0; int lastLabel = 0; CvBlob lastBlob = null; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { if (imgIn[x + y * step] == 0) { continue; } bool labeled = labels[y, x] != 0; if (!labeled && ((y == 0) || (imgIn[x + (y - 1) * step] == 0))) { labeled = true; // Label contour. label++; if (label == MarkerValue) { throw new Exception(); } labels[y, x] = label; numPixels++; // XXX This is not necessary at all. I only do this for consistency. if (y > 0) { labels[y - 1, x] = MarkerValue; } CvBlob blob = new CvBlob(label, x, y); blobs.Add(label, blob); lastLabel = label; lastBlob = blob; blob.Contour.StartingPoint = new CvPoint(x, y); int direction = 1; int xx = x; int yy = y; bool contourEnd = false; do { for (int numAttempts = 0; numAttempts < 3; numAttempts++) { bool found = false; for (int i = 0; i < 3; i++) { int nx = xx + MovesE[direction, i, 0]; int ny = yy + MovesE[direction, i, 1]; if ((nx < w) && (nx >= 0) && (ny < h) && (ny >= 0)) { if (imgIn[nx + ny * step] != 0) { found = true; blob.Contour.ChainCode.Add((CvChainCode)MovesE[direction, i, 3]); xx = nx; yy = ny; direction = MovesE[direction, i, 2]; break; } labels[ny, nx] = MarkerValue; } } if (!found) { direction = (direction + 1) % 4; } else { if (labels[yy, xx] != label) { labels[yy, xx] = label; numPixels++; if (xx < blob.MinX) { blob.MinX = xx; } else if (xx > blob.MaxX) { blob.MaxX = xx; } if (yy < blob.MinY) { blob.MinY = yy; } else if (yy > blob.MaxY) { blob.MaxY = yy; } blob.Area++; blob.M10 += xx; blob.M01 += yy; blob.M11 += xx * yy; blob.M20 += xx * xx; blob.M02 += yy * yy; } break; } contourEnd = ((xx == x) && (yy == y) && (direction == 1)); if (contourEnd) { break; } } } while (!contourEnd); } if ((y + 1 < h) && (imgIn[x + (y + 1) * step] == 0) && (labels[y + 1, x] == 0)) { labeled = true; // Label internal contour int l; CvBlob blob; if (labels[y, x] == 0) { l = labels[y, x - 1]; labels[y, x] = l; numPixels++; if (l == lastLabel) { blob = lastBlob; } else { blob = blobs[l]; lastLabel = l; lastBlob = blob; } if (blob == null) { throw new Exception(); } blob.Area++; blob.M10 += x; blob.M01 += y; blob.M11 += x * y; blob.M20 += x * x; blob.M02 += y * y; } else { l = labels[y, x]; if (l == lastLabel) { blob = lastBlob; } else { blob = blobs[l]; lastLabel = l; lastBlob = blob; } } if (blob == null) { throw new Exception(); } // XXX This is not necessary (I believe). I only do this for consistency. labels[y + 1, x] = MarkerValue; var contour = new CvContourChainCode { StartingPoint = new CvPoint(x, y) }; int direction = 3; int xx = x; int yy = y; do { for (int numAttempts = 0; numAttempts < 3; numAttempts++) { bool found = false; for (int i = 0; i < 3; i++) { int nx = xx + MovesI[direction, i, 0]; int ny = yy + MovesI[direction, i, 1]; if (imgIn[nx + ny * step] != 0) { found = true; contour.ChainCode.Add((CvChainCode)MovesI[direction, i, 3]); xx = nx; yy = ny; direction = MovesI[direction, i, 2]; break; } labels[ny, nx] = MarkerValue; } if (!found) { direction = (direction + 1) % 4; } else { if (labels[yy, xx] == 0) { labels[yy, xx] = l; numPixels++; blob.Area++; blob.M10 += xx; blob.M01 += yy; blob.M11 += xx * yy; blob.M20 += xx * xx; blob.M02 += yy * yy; } break; } } } while (!(xx == x && yy == y)); blob.InternalContours.Add(contour); } //else if (!imageOut(x, y)) if (!labeled) { // Internal pixel int l = labels[y, x - 1]; labels[y, x] = l; numPixels++; CvBlob blob; if (l == lastLabel) { blob = lastBlob; } else { blob = blobs[l]; lastLabel = l; lastBlob = blob; } if (blob == null) { throw new Exception(); } blob.Area++; blob.M10 += x; blob.M01 += y; blob.M11 += x * y; blob.M20 += x * x; blob.M02 += y * y; } } } foreach (var kv in blobs) { kv.Value.SetMoments(); } return(numPixels); }
/// <summary> /// /// </summary> /// <param name="labels"></param> /// <param name="blob"></param> /// <param name="imgSrc"></param> /// <param name="imgDst"></param> /// <param name="mode"></param> /// <param name="color"></param> /// <param name="alpha"></param> public static void PerformOne(LabelData labels, CvBlob blob, Mat imgSrc, Mat imgDst, RenderBlobsMode mode, Scalar color, double alpha) { if (labels == null) { throw new ArgumentNullException(nameof(labels)); } if (blob == null) { throw new ArgumentNullException(nameof(blob)); } if (imgSrc == null) { throw new ArgumentNullException(nameof(imgSrc)); } if (imgDst == null) { throw new ArgumentNullException(nameof(imgDst)); } if (imgDst.Type() != MatType.CV_8UC3) { throw new ArgumentException("'img' must be a 3-channel U8 image."); } if ((mode & RenderBlobsMode.Color) == RenderBlobsMode.Color) { var pSrc = imgSrc.GetGenericIndexer <Vec3b>(); var pDst = imgDst.GetGenericIndexer <Vec3b>(); for (int r = blob.MinY; r <= blob.MaxY; r++) { for (int c = blob.MinX; c <= blob.MaxX; c++) { if (labels[r, c] == blob.Label) { byte v0 = (byte)((1.0 - alpha) * pSrc[r, c].Item0 + alpha * color.Val0); byte v1 = (byte)((1.0 - alpha) * pSrc[r, c].Item1 + alpha * color.Val1); byte v2 = (byte)((1.0 - alpha) * pSrc[r, c].Item2 + alpha * color.Val2); pDst[r, c] = new Vec3b(v0, v1, v2); } } } } if (mode != RenderBlobsMode.None) { if ((mode & RenderBlobsMode.BoundingBox) == RenderBlobsMode.BoundingBox) { Cv2.Rectangle( imgDst, new Point(blob.MinX, blob.MinY), new Point(blob.MaxX, blob.MaxY), new Scalar(255, 0, 0)); } if ((mode & RenderBlobsMode.Angle) == RenderBlobsMode.Angle) { double angle = blob.Angle(); double lengthLine = Math.Max(blob.MaxX - blob.MinX, blob.MaxY - blob.MinY) / 2.0; double x1 = blob.Centroid.X - lengthLine * Math.Cos(angle); double y1 = blob.Centroid.Y - lengthLine * Math.Sin(angle); double x2 = blob.Centroid.X + lengthLine * Math.Cos(angle); double y2 = blob.Centroid.Y + lengthLine * Math.Sin(angle); Cv2.Line(imgDst, new Point((int)x1, (int)y1), new Point((int)x2, (int)y2), new Scalar(0, 255, 0)); } if ((mode & RenderBlobsMode.Centroid) == RenderBlobsMode.Centroid) { Cv2.Line(imgDst, new Point((int)blob.Centroid.X - 3, (int)blob.Centroid.Y), new Point((int)blob.Centroid.X + 3, (int)blob.Centroid.Y), new Scalar(255, 0, 0)); Cv2.Line(imgDst, new Point((int)blob.Centroid.X, (int)blob.Centroid.Y - 3), new Point((int)blob.Centroid.X, (int)blob.Centroid.Y + 3), new Scalar(255, 0, 0)); } } }
/// <summary> /// /// </summary> /// <param name="labels"></param> /// <param name="blob"></param> /// <param name="imgSrc"></param> /// <param name="imgDst"></param> /// <param name="mode"></param> /// <param name="color"></param> /// <param name="alpha"></param> public static unsafe void PerformOne(LabelData labels, CvBlob blob, IplImage imgSrc, IplImage imgDst, RenderBlobsMode mode, CvScalar color, double alpha) { if (labels == null) { throw new ArgumentNullException("labels"); } if (blob == null) { throw new ArgumentNullException("blob"); } if (imgSrc == null) { throw new ArgumentNullException("imgSrc"); } if (imgDst == null) { throw new ArgumentNullException("imgDst"); } if (imgDst.Depth != BitDepth.U8 || imgDst.NChannels != 3) { throw new ArgumentException("'img' must be a 3-channel U8 image."); } if ((mode & RenderBlobsMode.Color) == RenderBlobsMode.Color) { int stepSrc = imgSrc.WidthStep; int stepDst = imgDst.WidthStep; int offsetSrc = 0; int offsetDst = 0; CvRect roiSrc = imgSrc.ROI; CvRect roiDst = imgDst.ROI; if (roiSrc.Size != imgSrc.Size) { offsetSrc = roiSrc.Y * stepSrc + roiSrc.X; } if (roiDst.Size != imgDst.Size) { offsetDst = roiDst.Y * stepDst + roiDst.X; } byte *pSrc = (byte *)imgSrc.ImageData + offsetSrc + (blob.MinY * stepSrc); byte *pDst = (byte *)imgDst.ImageData + offsetDst + (blob.MinY * stepDst); for (int r = blob.MinY; r < blob.MaxY; r++) { for (int c = blob.MinX; c < blob.MaxX; c++) { if (labels[r, c] == blob.Label) { pDst[c * 3 + 0] = (byte)((1.0 - alpha) * pSrc[c + 0] + alpha * color.Val0); pDst[c * 3 + 1] = (byte)((1.0 - alpha) * pSrc[c + 1] + alpha * color.Val1); pDst[c * 3 + 2] = (byte)((1.0 - alpha) * pSrc[c + 2] + alpha * color.Val2); } } pSrc += stepSrc; pDst += stepDst; } } if (mode != RenderBlobsMode.None) { if ((mode & RenderBlobsMode.BoundingBox) == RenderBlobsMode.BoundingBox) { Cv.Rectangle( imgDst, new CvPoint(blob.MinX, blob.MinY), new CvPoint(blob.MaxX - 1, blob.MaxY - 1), new CvColor(255, 0, 0)); } if ((mode & RenderBlobsMode.Angle) == RenderBlobsMode.Angle) { double angle = blob.Angle(); double lengthLine = Math.Max(blob.MaxX - blob.MinX, blob.MaxY - blob.MinY) / 2.0; double x1 = blob.Centroid.X - lengthLine * Math.Cos(angle); double y1 = blob.Centroid.Y - lengthLine * Math.Sin(angle); double x2 = blob.Centroid.X + lengthLine * Math.Cos(angle); double y2 = blob.Centroid.Y + lengthLine * Math.Sin(angle); Cv.Line(imgDst, new CvPoint((int)x1, (int)y1), Cv.Point((int)x2, (int)y2), new CvColor(0, 255, 0)); } if ((mode & RenderBlobsMode.Centroid) == RenderBlobsMode.Centroid) { Cv.Line(imgDst, new CvPoint((int)blob.Centroid.X - 3, (int)blob.Centroid.Y), new CvPoint((int)blob.Centroid.X + 3, (int)blob.Centroid.Y), new CvColor(0, 0, 255)); Cv.Line(imgDst, new CvPoint((int)blob.Centroid.X, (int)blob.Centroid.Y - 3), new CvPoint((int)blob.Centroid.X, (int)blob.Centroid.Y + 3), new CvColor(0, 0, 255)); } } }
/// <summary> /// /// </summary> /// <param name="img"></param> /// <param name="blobs"></param> /// <returns></returns> public static int Perform(Mat img, CvBlobs blobs) { if (img == null) { throw new ArgumentNullException(nameof(img)); } if (blobs == null) { throw new ArgumentNullException(nameof(blobs)); } if (img.Type() != MatType.CV_8UC1) { throw new ArgumentException("'img' must be a 1-channel U8 image."); } if (blobs.Labels == null) { throw new ArgumentException("blobs.Labels == null", nameof(blobs)); } LabelData labels = blobs.Labels; //if(labels.GetLength(0) != h || labels.GetLength(1) != w) if (labels.Rows != img.Height || labels.Cols != img.Width) { throw new ArgumentException("img.Size != labels' size"); } int numPixels = 0; blobs.Clear(); int w = img.Cols; int h = img.Rows; int step = (int)img.Step(); byte[] imgIn; unsafe { byte *imgInPtr = (byte *)img.Data; if ((long)h * step > int.MaxValue) { throw new ArgumentException("Too big image (image data > 2^31)"); } int length = h * step; imgIn = new byte[length]; Marshal.Copy(new IntPtr(imgInPtr), imgIn, 0, imgIn.Length); } int label = 0; int lastLabel = 0; CvBlob?lastBlob = null; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { if (imgIn[x + y * step] == 0) { continue; } bool labeled = labels[y, x] != 0; if (!labeled && ((y == 0) || (imgIn[x + (y - 1) * step] == 0))) { labeled = true; // Label contour. label++; if (label == MarkerValue) { throw new Exception(); } labels[y, x] = label; numPixels++; // XXX This is not necessary at all. I only do this for consistency. if (y > 0) { labels[y - 1, x] = MarkerValue; } var blob = new CvBlob(label, x, y); blobs.Add(label, blob); lastLabel = label; lastBlob = blob; blob.Contour.StartingPoint = new Point(x, y); int direction = 1; int xx = x; int yy = y; bool contourEnd = false; do { for (int numAttempts = 0; numAttempts < 3; numAttempts++) { bool found = false; for (int i = 0; i < 3; i++) { int nx = xx + MovesE[direction, i, 0]; int ny = yy + MovesE[direction, i, 1]; if ((nx < w) && (nx >= 0) && (ny < h) && (ny >= 0)) { if (imgIn[nx + ny * step] != 0) { found = true; blob.Contour.ChainCode.Add((CvChainCode)MovesE[direction, i, 3]); xx = nx; yy = ny; direction = MovesE[direction, i, 2]; break; } labels[ny, nx] = MarkerValue; } } if (!found) { direction = (direction + 1) % 4; } else { if (labels[yy, xx] != label) { labels[yy, xx] = label; numPixels++; if (xx < blob.MinX) { blob.MinX = xx; } else if (xx > blob.MaxX) { blob.MaxX = xx; } if (yy < blob.MinY) { blob.MinY = yy; } else if (yy > blob.MaxY) { blob.MaxY = yy; } blob.Area++; blob.M10 += xx; blob.M01 += yy; blob.M11 += xx * yy; blob.M20 += xx * xx; blob.M02 += yy * yy; } break; } contourEnd = ((xx == x) && (yy == y) && (direction == 1)); if (contourEnd) { break; } } } while (!contourEnd); } if ((y + 1 < h) && (imgIn[x + (y + 1) * step] == 0) && (labels[y + 1, x] == 0)) { labeled = true; // Label internal contour int l; CvBlob?blob; if (labels[y, x] == 0) { l = labels[y, x - 1]; labels[y, x] = l; numPixels++; if (l == lastLabel) { blob = lastBlob; } else { blob = blobs[l]; lastLabel = l; lastBlob = blob; } if (blob == null) { throw new Exception(); } blob.Area++; blob.M10 += x; blob.M01 += y; blob.M11 += x * y; blob.M20 += x * x; blob.M02 += y * y; } else { l = labels[y, x]; if (l == lastLabel) { blob = lastBlob; } else { blob = blobs[l]; lastLabel = l; lastBlob = blob; } } if (blob == null) { throw new Exception(); } // XXX This is not necessary (I believe). I only do this for consistency. labels[y + 1, x] = MarkerValue; var contour = new CvContourChainCode { StartingPoint = new Point(x, y) }; int direction = 3; int xx = x; int yy = y; do { for (int numAttempts = 0; numAttempts < 3; numAttempts++) { bool found = false; for (int i = 0; i < 3; i++) { int nx = xx + MovesI[direction, i, 0]; int ny = yy + MovesI[direction, i, 1]; if (imgIn[nx + ny * step] != 0) { found = true; contour.ChainCode.Add((CvChainCode)MovesI[direction, i, 3]); xx = nx; yy = ny; direction = MovesI[direction, i, 2]; break; } labels[ny, nx] = MarkerValue; } if (!found) { direction = (direction + 1) % 4; } else { if (labels[yy, xx] == 0) { labels[yy, xx] = l; numPixels++; blob.Area++; blob.M10 += xx; blob.M01 += yy; blob.M11 += xx * yy; blob.M20 += xx * xx; blob.M02 += yy * yy; } break; } } } while (!(xx == x && yy == y)); blob.InternalContours.Add(contour); } //else if (!imageOut(x, y)) if (!labeled) { // Internal pixel int l = labels[y, x - 1]; labels[y, x] = l; numPixels++; CvBlob?blob; if (l == lastLabel) { blob = lastBlob; } else { blob = blobs[l]; lastLabel = l; lastBlob = blob; } if (blob == null) { throw new Exception(); } blob.Area++; blob.M10 += x; blob.M01 += y; blob.M11 += x * y; blob.M20 += x * x; blob.M02 += y * y; } } } foreach (var kv in blobs) { kv.Value.SetMoments(); } GC.KeepAlive(img); return(numPixels); }
/// <summary> /// Constructor (copy) /// </summary> public CvBlobs(IEnumerable <KeyValuePair <int, CvBlob> > blobData, LabelData labelData) : this(blobData, labelData.Values) { }
/// <summary> /// /// </summary> /// <param name="labels"></param> /// <param name="blob"></param> /// <param name="imgSrc"></param> /// <param name="imgDst"></param> /// <param name="mode"></param> /// <param name="color"></param> /// <param name="alpha"></param> public static unsafe void PerformOne(LabelData labels, CvBlob blob, IplImage imgSrc, IplImage imgDst, RenderBlobsMode mode, CvScalar color, double alpha) { if (labels == null) throw new ArgumentNullException("labels"); if (blob == null) throw new ArgumentNullException("blob"); if (imgSrc == null) throw new ArgumentNullException("imgSrc"); if (imgDst == null) throw new ArgumentNullException("imgDst"); if (imgDst.Depth != BitDepth.U8 || imgDst.NChannels != 3) throw new ArgumentException("'img' must be a 3-channel U8 image."); if ((mode & RenderBlobsMode.Color) == RenderBlobsMode.Color) { int stepSrc = imgSrc.WidthStep; int stepDst = imgDst.WidthStep; int offsetSrc = 0; int offsetDst = 0; CvRect roiSrc = imgSrc.ROI; CvRect roiDst = imgDst.ROI; if (roiSrc.Size != imgSrc.Size) offsetSrc = roiSrc.Y * stepSrc + roiSrc.X; if (roiDst.Size != imgDst.Size) offsetDst = roiDst.Y * stepDst + roiDst.X; byte* pSrc = (byte*)imgSrc.ImageData + offsetSrc + (blob.MinY * stepSrc); byte* pDst = (byte*)imgDst.ImageData + offsetDst + (blob.MinY * stepDst); for (int r = blob.MinY; r < blob.MaxY; r++) { for (int c = blob.MinX; c < blob.MaxX; c++) { if (labels[r, c] == blob.Label) { pDst[c*3 + 0] = (byte) ((1.0 - alpha)*pSrc[c + 0] + alpha*color.Val0); pDst[c*3 + 1] = (byte) ((1.0 - alpha)*pSrc[c + 1] + alpha*color.Val1); pDst[c*3 + 2] = (byte) ((1.0 - alpha)*pSrc[c + 2] + alpha*color.Val2); } } pSrc += stepSrc; pDst += stepDst; } } if (mode != RenderBlobsMode.None) { if ((mode & RenderBlobsMode.BoundingBox) == RenderBlobsMode.BoundingBox) { Cv.Rectangle( imgDst, new CvPoint(blob.MinX, blob.MinY), new CvPoint(blob.MaxX - 1, blob.MaxY - 1), new CvColor(255, 0, 0)); } if ((mode & RenderBlobsMode.Angle) == RenderBlobsMode.Angle) { double angle = blob.Angle(); double lengthLine = Math.Max(blob.MaxX - blob.MinX, blob.MaxY - blob.MinY) / 2.0; double x1 = blob.Centroid.X - lengthLine * Math.Cos(angle); double y1 = blob.Centroid.Y - lengthLine * Math.Sin(angle); double x2 = blob.Centroid.X + lengthLine * Math.Cos(angle); double y2 = blob.Centroid.Y + lengthLine * Math.Sin(angle); Cv.Line(imgDst, new CvPoint((int)x1, (int)y1), Cv.Point((int)x2, (int)y2), new CvColor(0, 255, 0)); } if ((mode & RenderBlobsMode.Centroid) == RenderBlobsMode.Centroid) { Cv.Line(imgDst, new CvPoint((int)blob.Centroid.X - 3, (int)blob.Centroid.Y), new CvPoint((int)blob.Centroid.X + 3, (int)blob.Centroid.Y), new CvColor(0, 0, 255)); Cv.Line(imgDst, new CvPoint((int)blob.Centroid.X, (int)blob.Centroid.Y - 3), new CvPoint((int)blob.Centroid.X, (int)blob.Centroid.Y + 3), new CvColor(0, 0, 255)); } } }
private void Label(IplImage src, out IplImage binary, out IplImage labelsOld, out LabelData labelsNew, out Blob.Old.CvBlobs blobsOld, out CvBlobs blobsNew) { binary = new IplImage(src.Size, BitDepth.U8, 1); binary.ROI = src.ROI; Cv.Threshold(src, binary, 0, 255, ThresholdType.Otsu); // old labeling labelsOld = new IplImage(src.Width, src.Height, BitDepth.F32, 1); blobsOld = new OpenCvSharp.Blob.Old.CvBlobs(binary, labelsOld); // new labeling blobsNew = new CvBlobs(binary); labelsNew = blobsNew.Labels; }