Skip to content

Commit

Permalink
Add CropCenter operator and refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
glopesdev committed Aug 8, 2022
1 parent a5da02a commit 12be0fd
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 53 deletions.
187 changes: 187 additions & 0 deletions Bonsai.Vision/CropCenter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using OpenCV.Net;

namespace Bonsai.Vision
{
/// <summary>
/// Represents an operator that crops rectangular regions with fixed size around
/// the specified center for each image in the sequence.
/// </summary>
[Description("Crops rectangular regions with fixed size around the specified center for each image in the sequence.")]
public class CropCenter : Transform<IplImage, IplImage>
{
/// <summary>
/// Gets or sets a value specifying the size of the region of interest to
/// crop from the image.
/// </summary>
[Description("Specifies the size of the region of interest to crop from the image.")]
public Size Size { get; set; }

/// <summary>
/// Gets or sets a <see cref="Scalar"/> specifying the value to which all
/// pixels that fall outside image boundaries will be set to.
/// </summary>
[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);
}

/// <summary>
/// Crops a rectangular region with fixed size around the center of each image
/// in an observable sequence.
/// </summary>
/// <param name="source">The sequence of images to crop.</param>
/// <returns>
/// A sequence of images representing the cropped rectangular regions.
/// </returns>
public override IObservable<IplImage> Process(IObservable<IplImage> source)
{
return source.Select(image => IplImageHelper.CropMakeBorder(
image,
Size,
null,
IplBorder.Constant,
FillValue));
}

/// <summary>
/// Crops a rectangular region with fixed size around the specified center for
/// each image in an observable sequence.
/// </summary>
/// <param name="source">
/// A sequence of pairs representing the image and a 2D position with integer
/// coordinates around which to crop the rectangular region.
/// </param>
/// <returns>
/// A sequence of images representing the rectangular region cropped around
/// each of the specified positions.
/// </returns>
public IObservable<IplImage> Process(IObservable<Tuple<IplImage, Point>> 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);
});
}

/// <summary>
/// Crops a rectangular region with fixed size around the specified center for
/// each image in an observable sequence.
/// </summary>
/// <param name="source">
/// A sequence of pairs representing the image and a 2D position with single-precision
/// floating-point coordinates around which to crop the rectangular region.
/// </param>
/// <returns>
/// A sequence of images representing the rectangular region cropped around
/// each of the specified positions.
/// </returns>
public IObservable<IplImage> Process(IObservable<Tuple<IplImage, Point2f>> 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);
});
}

/// <summary>
/// Crops a rectangular region with fixed size around the center of the specified
/// connected component for each image in an observable sequence.
/// </summary>
/// <param name="source">
/// A sequence of pairs representing the image and the <see cref="ConnectedComponent"/>
/// around which to crop the rectangular region.
/// </param>
/// <returns>
/// A sequence of images representing the rectangular region cropped around
/// the centroid of the specified connected component.
/// </returns>
public IObservable<IplImage> Process(IObservable<Tuple<IplImage, ConnectedComponent>> 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);
});
}

/// <summary>
/// Crops a collection of rectangular regions with fixed size around the center of
/// each connected component for each image in an observable sequence.
/// </summary>
/// <param name="source">
/// A sequence of pairs representing the image and the <see cref="ConnectedComponentCollection"/>
/// specifying the centroids used to crop the rectangular regions.
/// </param>
/// <returns>
/// A sequence of image arrays representing the rectangular regions cropped around
/// each of the connected components.
/// </returns>
public IObservable<IplImage[]> Process(IObservable<Tuple<IplImage, ConnectedComponentCollection>> 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();
});
}
}
}
57 changes: 57 additions & 0 deletions Bonsai.Vision/IplImageHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
59 changes: 6 additions & 53 deletions Bonsai.Vision/ResizeCanvas.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,6 @@ public class ResizeCanvas : Transform<IplImage, IplImage>
[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;
}
}

/// <summary>
/// Resizes the border around each image in an observable sequence without
/// stretching the image.
Expand All @@ -66,45 +52,12 @@ static void AdjustRectangle(ref int left, int right, ref int origin, ref int ext
/// </returns>
public override IObservable<IplImage> Process(IObservable<IplImage> 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));
}
}
}

0 comments on commit 12be0fd

Please sign in to comment.