From 12be0fd1508c2e793a716b4068c6a6bfdc3ffb46 Mon Sep 17 00:00:00 2001 From: glopesdev Date: Mon, 8 Aug 2022 11:16:06 +0100 Subject: [PATCH] Add CropCenter operator and refactoring --- Bonsai.Vision/CropCenter.cs | 187 ++++++++++++++++++++++++++++++++ Bonsai.Vision/IplImageHelper.cs | 57 ++++++++++ Bonsai.Vision/ResizeCanvas.cs | 59 +--------- 3 files changed, 250 insertions(+), 53 deletions(-) create mode 100644 Bonsai.Vision/CropCenter.cs diff --git a/Bonsai.Vision/CropCenter.cs b/Bonsai.Vision/CropCenter.cs new file mode 100644 index 000000000..3b77e7f47 --- /dev/null +++ b/Bonsai.Vision/CropCenter.cs @@ -0,0 +1,187 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using OpenCV.Net; + +namespace Bonsai.Vision +{ + /// + /// Represents an operator that crops rectangular regions with fixed size around + /// the specified center for each image in the sequence. + /// + [Description("Crops rectangular regions with fixed size around the specified center for each image in the sequence.")] + public class CropCenter : Transform + { + /// + /// Gets or sets a value specifying the size of the region of interest to + /// crop from the image. + /// + [Description("Specifies the size of the region of interest to crop from the image.")] + public Size Size { get; set; } + + /// + /// Gets or sets a specifying the value to which all + /// pixels that fall outside image boundaries will be set to. + /// + [Description("Specifies the value to which all pixels that fall outside image boundaries will be set to.")] + public Scalar FillValue { get; set; } + + static void EnsureSize(IplImage image, ref Size cropSize) + { + if (cropSize.Width == 0) cropSize.Width = image.Width; + if (cropSize.Height == 0) cropSize.Height = image.Height; + } + + static Point CenterOffset(Point centroid, Size cropSize) + { + centroid.X = cropSize.Width / 2 - centroid.X; + centroid.Y = cropSize.Height / 2 - centroid.Y; + return centroid; + } + + static Point CenterOffset(Point2f centroid, Size cropSize) + { + centroid.X = cropSize.Width / 2f - centroid.X; + centroid.Y = cropSize.Height / 2f - centroid.Y; + return new Point(centroid); + } + + /// + /// Crops a rectangular region with fixed size around the center of each image + /// in an observable sequence. + /// + /// The sequence of images to crop. + /// + /// A sequence of images representing the cropped rectangular regions. + /// + public override IObservable Process(IObservable source) + { + return source.Select(image => IplImageHelper.CropMakeBorder( + image, + Size, + null, + IplBorder.Constant, + FillValue)); + } + + /// + /// Crops a rectangular region with fixed size around the specified center for + /// each image in an observable sequence. + /// + /// + /// A sequence of pairs representing the image and a 2D position with integer + /// coordinates around which to crop the rectangular region. + /// + /// + /// A sequence of images representing the rectangular region cropped around + /// each of the specified positions. + /// + public IObservable Process(IObservable> source) + { + return source.Select(value => + { + var size = Size; + var image = value.Item1; + EnsureSize(image, ref size); + var offset = CenterOffset(value.Item2, size); + return IplImageHelper.CropMakeBorder( + image, + size, + offset, + IplBorder.Constant, + FillValue); + }); + } + + /// + /// Crops a rectangular region with fixed size around the specified center for + /// each image in an observable sequence. + /// + /// + /// A sequence of pairs representing the image and a 2D position with single-precision + /// floating-point coordinates around which to crop the rectangular region. + /// + /// + /// A sequence of images representing the rectangular region cropped around + /// each of the specified positions. + /// + public IObservable Process(IObservable> source) + { + return source.Select(value => + { + var size = Size; + var image = value.Item1; + EnsureSize(image, ref size); + var offset = CenterOffset(value.Item2, size); + return IplImageHelper.CropMakeBorder( + image, + size, + offset, + IplBorder.Constant, + FillValue); + }); + } + + /// + /// Crops a rectangular region with fixed size around the center of the specified + /// connected component for each image in an observable sequence. + /// + /// + /// A sequence of pairs representing the image and the + /// around which to crop the rectangular region. + /// + /// + /// A sequence of images representing the rectangular region cropped around + /// the centroid of the specified connected component. + /// + public IObservable Process(IObservable> source) + { + return source.Select(value => + { + var size = Size; + var image = value.Item1; + EnsureSize(image, ref size); + var offset = CenterOffset(value.Item2.Centroid, size); + return IplImageHelper.CropMakeBorder( + image, + size, + offset, + IplBorder.Constant, + FillValue); + }); + } + + /// + /// Crops a collection of rectangular regions with fixed size around the center of + /// each connected component for each image in an observable sequence. + /// + /// + /// A sequence of pairs representing the image and the + /// specifying the centroids used to crop the rectangular regions. + /// + /// + /// A sequence of image arrays representing the rectangular regions cropped around + /// each of the connected components. + /// + public IObservable Process(IObservable> source) + { + return source.Select(value => + { + var size = Size; + var image = value.Item1; + EnsureSize(image, ref size); + return value.Item2.Select(component => + { + var offset = CenterOffset(component.Centroid, size); + return IplImageHelper.CropMakeBorder( + image, + size, + offset, + IplBorder.Constant, + FillValue); + }).ToArray(); + }); + } + } +} diff --git a/Bonsai.Vision/IplImageHelper.cs b/Bonsai.Vision/IplImageHelper.cs index aea410da6..93b831d10 100644 --- a/Bonsai.Vision/IplImageHelper.cs +++ b/Bonsai.Vision/IplImageHelper.cs @@ -61,5 +61,62 @@ public static IplImage EnsureColorCopy(IplImage output, IplImage image) else CV.Copy(image, output); return output; } + + static void AdjustRectangle(ref int left, int right, ref int origin, ref int extent) + { + if (left < 0) + { + origin -= left; + extent += left; + left = 0; + } + if (right < 0) + { + extent += right; + } + } + + internal static IplImage CropMakeBorder( + IplImage image, + Size size, + Point? offset, + IplBorder borderType, + Scalar fillValue) + { + if (size.Width == 0) size.Width = image.Width; + if (size.Height == 0) size.Height = image.Height; + + Point origin; + if (offset.HasValue) origin = offset.Value; + else + { + origin.X = (size.Width - image.Width) / 2; + origin.Y = (size.Height - image.Height) / 2; + } + + var right = size.Width - origin.X - image.Width; + var bottom = size.Height - origin.Y - image.Height; + if (origin.X == 0 && origin.Y == 0 && right == 0 && bottom == 0) return image; + + var inputRect = new Rect(0, 0, image.Width, image.Height); + AdjustRectangle(ref origin.X, right, ref inputRect.X, ref inputRect.Width); + AdjustRectangle(ref origin.Y, bottom, ref inputRect.Y, ref inputRect.Height); + if (origin.X <= 0 && origin.Y <= 0 && right <= 0 && bottom <= 0) + { + return image.GetSubRect(inputRect); + } + + var output = new IplImage(size, image.Depth, image.Channels); + if (inputRect.Width < 0 || inputRect.Height < 0) + { + output.Set(fillValue); + } + else + { + using var inputHeader = image.GetSubRect(inputRect); + CV.CopyMakeBorder(inputHeader, output, origin, borderType, fillValue); + } + return output; + } } } diff --git a/Bonsai.Vision/ResizeCanvas.cs b/Bonsai.Vision/ResizeCanvas.cs index b825d6c07..6ef2c43d9 100644 --- a/Bonsai.Vision/ResizeCanvas.cs +++ b/Bonsai.Vision/ResizeCanvas.cs @@ -40,20 +40,6 @@ public class ResizeCanvas : Transform [Description("The optional top-left coordinates where the source image will be placed.")] public Point? Offset { get; set; } - static void AdjustRectangle(ref int left, int right, ref int origin, ref int extent) - { - if (left < 0) - { - origin -= left; - extent += left; - left = 0; - } - if (right < 0) - { - extent += right; - } - } - /// /// Resizes the border around each image in an observable sequence without /// stretching the image. @@ -66,45 +52,12 @@ static void AdjustRectangle(ref int left, int right, ref int origin, ref int ext /// public override IObservable Process(IObservable source) { - return source.Select(input => - { - var targetSize = Size; - if (targetSize.Width == 0) targetSize.Width = input.Width; - if (targetSize.Height == 0) targetSize.Height = input.Height; - - Point offset; - var offsetNullable = Offset; - if (offsetNullable.HasValue) offset = offsetNullable.Value; - else - { - offset.X = (targetSize.Width - input.Width) / 2; - offset.Y = (targetSize.Height - input.Height) / 2; - } - - var right = targetSize.Width - offset.X - input.Width; - var bottom = targetSize.Height - offset.Y - input.Height; - if (offset.X == 0 && offset.Y == 0 && right == 0 && bottom == 0) return input; - - var inputRect = new Rect(0, 0, input.Width, input.Height); - AdjustRectangle(ref offset.X, right, ref inputRect.X, ref inputRect.Width); - AdjustRectangle(ref offset.Y, bottom, ref inputRect.Y, ref inputRect.Height); - if (offset.X <= 0 && offset.Y <= 0 && right <= 0 && bottom <= 0) - { - return input.GetSubRect(inputRect); - } - - var output = new IplImage(targetSize, input.Depth, input.Channels); - if (inputRect.Width < 0 || inputRect.Height < 0) - { - output.Set(FillValue); - } - else - { - using var inputHeader = input.GetSubRect(inputRect); - CV.CopyMakeBorder(inputHeader, output, offset, BorderType, FillValue); - } - return output; - }); + return source.Select(image => IplImageHelper.CropMakeBorder( + image, + Size, + Offset, + BorderType, + FillValue)); } } }