check point
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
using Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses;
|
||||
|
||||
public class CategoryItem:IFilterParent
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public List<IFilter> Filters { get; set; } = new List<IFilter>();
|
||||
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(Name);
|
||||
writer.Write(Filters.Count);
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
var type = filter.GetType();
|
||||
writer.Write(type.FullName);
|
||||
filter.Save(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public static CategoryItem Load(BinaryReader reader)
|
||||
{
|
||||
var category = new CategoryItem();
|
||||
category.Name = reader.ReadString();
|
||||
var count = reader.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var typeName = reader.ReadString();
|
||||
var type = Type.GetType(typeName);
|
||||
var filter = (IFilter)Activator.CreateInstance(type);
|
||||
filter.Load(reader);
|
||||
filter.Parent = category;
|
||||
category.Filters.Add(filter);
|
||||
}
|
||||
return category;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses;
|
||||
|
||||
public class CategoryTree
|
||||
{
|
||||
public List<CategoryItem> CategoryItems { get; set; }=new List<CategoryItem>();
|
||||
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(CategoryItems.Count);
|
||||
foreach (var category in CategoryItems)
|
||||
{
|
||||
category.Save(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public static CategoryTree Load(BinaryReader reader)
|
||||
{
|
||||
var tree = new CategoryTree();
|
||||
var count = reader.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
tree.CategoryItems.Add(CategoryItem.Load(reader));
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
public static CategoryTree LoadFromFile(string path)
|
||||
{
|
||||
using var file = File.OpenRead(path);
|
||||
using var reader = new BinaryReader(file);
|
||||
return Load(reader);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses
|
||||
{
|
||||
public class ColorConfig
|
||||
{
|
||||
private int _intensityMaxGrey;
|
||||
|
||||
public ColorConfig()
|
||||
{
|
||||
MaxBlobSize = 2000;
|
||||
HueHighSaturationMinGrey = 20;
|
||||
HueHighSaturationMaxGrey = 50;
|
||||
HighSaturationMinGrey = 100;
|
||||
HighSaturationMaxGrey = 255;
|
||||
ClosingCircleRadius = 8.0;
|
||||
IntensityMaxGrey = 255;
|
||||
IntensityMinGrey = 0;
|
||||
MedianRadius = 10;
|
||||
MedianMargin = 1;
|
||||
UseMedianFilter = true;
|
||||
}
|
||||
|
||||
public int MaxBlobSize { get; set; }
|
||||
public int HueHighSaturationMinGrey { get; set; }
|
||||
public int HueHighSaturationMaxGrey { get; set; }
|
||||
public int HighSaturationMinGrey { get; set; }
|
||||
public int HighSaturationMaxGrey { get; set; }
|
||||
|
||||
public int IntensityMaxGrey
|
||||
{
|
||||
get { return _intensityMaxGrey; }
|
||||
set { _intensityMaxGrey = value; }
|
||||
}
|
||||
|
||||
public int IntensityMinGrey { get; set; }
|
||||
|
||||
public double ClosingCircleRadius { get; set; }
|
||||
|
||||
public int MedianRadius { get; set; }
|
||||
public int MedianMargin { get; set; }
|
||||
|
||||
public bool UseMedianFilter { get; set; }
|
||||
|
||||
public bool GroupColorEnabled { get; set; }
|
||||
public List<HSIValue> GroupColorValues { get; set; }=new List<HSIValue>();
|
||||
public HSIValue HSI { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class AddFilterItem : IFilter, IFilterParent
|
||||
{
|
||||
public IFilterParent Parent { get; set; }
|
||||
|
||||
public List<IFilter> Filters { get; set; } = new List<IFilter>();
|
||||
|
||||
public void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
using var tracker = new ResourcesTracker();
|
||||
var temp = tracker.NewMat(res.Size(), MatType.CV_8UC1, Scalar.Black);
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
filter.Transform(ref image, ref temp);
|
||||
}
|
||||
res = res + temp;
|
||||
temp.Dispose();
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(Filters.Count);
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
bw.Write(filter.GetType().FullName);
|
||||
filter.Save(bw);
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var type = reader.ReadString();
|
||||
var filter = (IFilter)Activator.CreateInstance(Type.GetType(type));
|
||||
filter.Load(reader);
|
||||
filter.Parent = this;
|
||||
Filters.Add(filter);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Add";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Text;
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class BlobsFilterItem:IFilter
|
||||
{
|
||||
public int MinArea { get; set; } = 0;
|
||||
public int MaxArea { get; set; } = 999999;
|
||||
public int MinRadius { get; set; } = 0;
|
||||
public int MaxRadius { get; set; } = 999999;
|
||||
|
||||
public IFilterParent Parent { get; set; }
|
||||
public void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
var contours = res
|
||||
.FindContoursAsArray(RetrievalModes.List, ContourApproximationModes.ApproxSimple);
|
||||
|
||||
Mat result = new Mat(res.Rows, res.Cols, MatType.CV_8U, new Scalar(0));
|
||||
|
||||
foreach (Point[] contour in contours)
|
||||
{
|
||||
var area = Cv2.ContourArea(contour);
|
||||
if (area < MinArea || area > MaxArea)
|
||||
continue;
|
||||
var radius = GetContourRadius(contour);
|
||||
if (radius < MinRadius || radius > MaxRadius)
|
||||
continue;
|
||||
|
||||
Cv2.DrawContours(result, new[] { contour }, -1, new Scalar(255), -1);
|
||||
}
|
||||
|
||||
res = result;
|
||||
}
|
||||
int GetContourRadius(Point[] contour)
|
||||
{
|
||||
Cv2.MinEnclosingCircle(contour, out var center, out var radius);
|
||||
return (int)radius;
|
||||
}
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(MinArea);
|
||||
writer.Write(MaxArea);
|
||||
writer.Write(MinRadius);
|
||||
writer.Write(MaxRadius);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
MinArea = reader.ReadInt32();
|
||||
MaxArea = reader.ReadInt32();
|
||||
MinRadius = reader.ReadInt32();
|
||||
MaxRadius = reader.ReadInt32();
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("Blobs: ");
|
||||
sb.Append($"Area:{MinArea}-{MaxArea}, ");
|
||||
sb.Append($"Radius:{MinRadius}-{MaxRadius}");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class BlurFilterItem:IFilter
|
||||
{
|
||||
public IFilterParent Parent { get; set; }
|
||||
public int Radius { get; set; } = 15;
|
||||
public void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
image = image.Blur(new Size(Radius, Radius));
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(Radius);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
Radius = reader.ReadInt32();
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Blur "+Radius;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public interface IFilter
|
||||
{
|
||||
public IFilterParent Parent { get; set; }
|
||||
void Transform(ref Mat image, ref Mat res);
|
||||
void Save(BinaryWriter writer);
|
||||
void Load(BinaryReader reader);
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public interface IFilterParent
|
||||
{
|
||||
public List<IFilter> Filters { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class IntersectionFilterItem : IFilter, IFilterParent
|
||||
{
|
||||
public IFilterParent Parent { get; set; }
|
||||
|
||||
public List<IFilter> Filters { get; set; } = new List<IFilter>();
|
||||
|
||||
public void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
using var tracker = new ResourcesTracker();
|
||||
var temp = tracker.NewMat(res.Size(), MatType.CV_8UC1, Scalar.Black);
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
filter.Transform(ref image, ref temp);
|
||||
}
|
||||
res = res & temp;
|
||||
temp.Dispose();
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(Filters.Count);
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
bw.Write(filter.GetType().FullName);
|
||||
filter.Save(bw);
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var type = reader.ReadString();
|
||||
var filter = (IFilter)Activator.CreateInstance(Type.GetType(type));
|
||||
filter.Load(reader);
|
||||
filter.Parent = this;
|
||||
Filters.Add(filter);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Intersection";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.Diagnostics;
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class MorphologyFilterItem:IFilter
|
||||
{
|
||||
public enum EMorphologyType
|
||||
{
|
||||
Erode,
|
||||
Dilate,
|
||||
Close,
|
||||
Open
|
||||
}
|
||||
|
||||
public int Size { get; set; } = 1;
|
||||
public EMorphologyType MorphologyType { get; set; }=EMorphologyType.Erode;
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
Size = reader.ReadInt32();
|
||||
MorphologyType = (EMorphologyType)reader.ReadInt32();
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public IFilterParent Parent { get; set; }
|
||||
|
||||
public MorphologyFilterItem()
|
||||
{
|
||||
}
|
||||
|
||||
public MorphologyFilterItem(CategoryItem parent)
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
if(Size==0)
|
||||
return;
|
||||
|
||||
var sw=Stopwatch.StartNew();
|
||||
|
||||
var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse,new Size((int)(Size), (int)(Size)));
|
||||
|
||||
switch (MorphologyType)
|
||||
{
|
||||
case EMorphologyType.Dilate:
|
||||
//Cv2.Dilate(res, res,kernel);
|
||||
Cv2.MorphologyEx(res, res, MorphTypes.Dilate, kernel);
|
||||
break;
|
||||
case EMorphologyType.Erode:
|
||||
Cv2.Erode(res, res,kernel);
|
||||
break;
|
||||
case EMorphologyType.Close:
|
||||
Cv2.MorphologyEx(res, res, MorphTypes.Close, kernel);
|
||||
break;
|
||||
case EMorphologyType.Open:
|
||||
Cv2.MorphologyEx(res, res, MorphTypes.Open, kernel);
|
||||
break;
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(Size);
|
||||
writer.Write((int)MorphologyType);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{MorphologyType} {Size}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using System.Windows.Markup;
|
||||
using OpenCvSharp;
|
||||
using Size = OpenCvSharp.Size;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class RangeFilterItem : IFilter
|
||||
{
|
||||
public IFilterParent Parent { get; set; }
|
||||
|
||||
public RangeFilterItem(CategoryItem parent)
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public RangeFilterItem()
|
||||
{
|
||||
}
|
||||
|
||||
public int HMin { get; set; } = 0;
|
||||
public int HMax { get; set; } = 100;
|
||||
public int SMin { get; set; } = 0;
|
||||
public int SMax { get; set; } = 255;
|
||||
public int VMin { get; set; } = 0;
|
||||
public int VMax { get; set; } = 255;
|
||||
public int Dilation { get; set; } = 0;
|
||||
public int Closing { get; set; } = 0;
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"H: {HMin}-{HMax}, S: {SMin}-{SMax}, V: {VMin}-{VMax}";
|
||||
}
|
||||
|
||||
public void Transform(ref Mat hsv, ref Mat res)
|
||||
{
|
||||
var lower = new Scalar(HMin, SMin, VMin);
|
||||
var upper = new Scalar(HMax, SMax, VMax);
|
||||
var mask = hsv.InRange(lower, upper);
|
||||
|
||||
if (Dilation > 0)
|
||||
{
|
||||
// generate circular kernel
|
||||
var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(Dilation, Dilation));
|
||||
mask = mask.Dilate(kernel);
|
||||
kernel.Dispose();
|
||||
}
|
||||
|
||||
if (Closing > 0)
|
||||
{
|
||||
// generate circular kernel
|
||||
var kernel = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(Closing, Closing));
|
||||
mask = mask.MorphologyEx(MorphTypes.Close, kernel);
|
||||
kernel.Dispose();
|
||||
}
|
||||
|
||||
res+=mask;
|
||||
|
||||
mask.Dispose();
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(HMin);
|
||||
writer.Write(HMax);
|
||||
writer.Write(SMin);
|
||||
writer.Write(SMax);
|
||||
writer.Write(VMin);
|
||||
writer.Write(VMax);
|
||||
writer.Write(Dilation);
|
||||
writer.Write(Closing);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
HMin = reader.ReadInt32();
|
||||
HMax = reader.ReadInt32();
|
||||
SMin = reader.ReadInt32();
|
||||
SMax = reader.ReadInt32();
|
||||
VMin = reader.ReadInt32();
|
||||
VMax = reader.ReadInt32();
|
||||
Dilation = reader.ReadInt32();
|
||||
Closing = reader.ReadInt32();
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public static RangeFilterItem Load(BinaryReader reader, CategoryItem parent)
|
||||
{
|
||||
var filter = new RangeFilterItem(parent);
|
||||
filter.HMin = reader.ReadInt32();
|
||||
filter.HMax = reader.ReadInt32();
|
||||
filter.SMin = reader.ReadInt32();
|
||||
filter.SMax = reader.ReadInt32();
|
||||
filter.VMin = reader.ReadInt32();
|
||||
filter.VMax = reader.ReadInt32();
|
||||
filter.Dilation = reader.ReadInt32();
|
||||
filter.Closing = reader.ReadInt32();
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class SobelFilterItem:IFilter
|
||||
{
|
||||
public IFilterParent Parent { get; set; }
|
||||
public void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
var filtered=image.Sobel(MatType.CV_32F, 1, 1, 3);
|
||||
filtered.ConvertTo(image, MatType.CV_8UC3);
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using System.Diagnostics;
|
||||
using OpenCvSharp;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class SpatialFilterItem : IFilter
|
||||
{
|
||||
private Mat? _mask=null;
|
||||
|
||||
public IFilterParent Parent { get; set; }
|
||||
|
||||
public bool Subtract { get; set; }
|
||||
|
||||
public string? FilePath { get; set; }
|
||||
|
||||
public void ReloadMask()
|
||||
{
|
||||
if (!File.Exists(FilePath))
|
||||
{
|
||||
CreateMask();
|
||||
}else
|
||||
if (!string.IsNullOrEmpty(FilePath))
|
||||
{
|
||||
_mask = new Mat(FilePath,ImreadModes.Grayscale);
|
||||
}
|
||||
}
|
||||
|
||||
public SpatialFilterItem()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void CreateMask()
|
||||
{
|
||||
_mask = new Mat(13005 + 256, 256, MatType.CV_8UC1, (Scalar)0);
|
||||
}
|
||||
|
||||
public SpatialFilterItem(CategoryItem parent) : this()
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public Mat Mask
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_mask == null)
|
||||
{
|
||||
if (FilePath != null)
|
||||
{
|
||||
ReloadMask();
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateMask();
|
||||
}
|
||||
}
|
||||
|
||||
return _mask;
|
||||
}
|
||||
set
|
||||
{
|
||||
_mask = value;
|
||||
}
|
||||
}
|
||||
|
||||
struct PixelStruct
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 65536*3)]
|
||||
public byte[] Pixels;
|
||||
}
|
||||
struct PixelStructMask
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3394816)]
|
||||
public byte[] Pixels;
|
||||
}
|
||||
public static Mat lastInput;
|
||||
public unsafe void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
|
||||
//var pixelStruct = Marshal.PtrToStructure<PixelStruct>(new IntPtr(image.DataPointer));
|
||||
var candyBytes = GetPixels<byte>(image);
|
||||
var candidates = new Mat(image.Size(), MatType.CV_8UC1, (Scalar)0);
|
||||
Mat tmp=new Mat();
|
||||
image.CopyTo(tmp);
|
||||
lastInput = tmp;
|
||||
//var pixelStructMask = Marshal.PtrToStructure<PixelStructMask>(new IntPtr(Mask.DataPointer));
|
||||
|
||||
var maskBytes = GetPixels<byte>(Mask);
|
||||
|
||||
var len = candyBytes.Length;
|
||||
var w = image.Width;
|
||||
var mw = Mask.Width;
|
||||
|
||||
var lenDiv3 = len / 3;
|
||||
|
||||
for (var q = 0; q < lenDiv3; q++)
|
||||
{
|
||||
var i = q * 3;
|
||||
|
||||
var p = i / 3;
|
||||
var y = p / w;
|
||||
var x = p % w;
|
||||
|
||||
var yp = (int)candyBytes[i + 0];
|
||||
var xp = candyBytes[i + 1];
|
||||
var vp = ((byte) (candyBytes[i + 2] / 5f)) * 255;
|
||||
|
||||
var hp = yp + vp;
|
||||
|
||||
var maskColor = maskBytes[hp * mw + xp];
|
||||
if (maskColor > 0)
|
||||
candidates.Set<byte>(y, x, 255);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (Subtract && !IsSelected)
|
||||
{
|
||||
res -= candidates;
|
||||
}
|
||||
else
|
||||
{
|
||||
res += candidates;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
string Hash(Span<byte> bytes) {
|
||||
using var sha = SHA256.Create();
|
||||
var hash = sha.ComputeHash(bytes.ToArray());
|
||||
return Convert.ToBase64String(hash);
|
||||
}
|
||||
unsafe Span<T> GetPixels<T>(Mat image) where T : unmanaged
|
||||
{
|
||||
|
||||
return new Span<T>(image.DataPointer, image.Width * image.Height * image.Channels());
|
||||
|
||||
}
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(FilePath??"");
|
||||
writer.Write(Subtract);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
FilePath = reader.ReadString();
|
||||
Console.WriteLine("Loaded path:"+FilePath);
|
||||
if(FilePath.StartsWith("..\\../"))
|
||||
FilePath = FilePath.Replace("..\\../", "..\\");
|
||||
Subtract = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var totalColors = 0;
|
||||
if (Mask != null)
|
||||
{
|
||||
totalColors = Mask.CountNonZero();
|
||||
}
|
||||
return $"{(Subtract ? "-" : "+")}Spatial Filter({totalColors} colors)";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Hawkeye.VisionBuilder.Workflow.Datatypes;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
[Obsolete]
|
||||
public class SpatialFilterItemGPU:IFilter
|
||||
{
|
||||
private Mat? _mask = null;
|
||||
|
||||
private MemoryBuffer1D<byte, Stride1D.Dense> _maskGpu;
|
||||
|
||||
|
||||
|
||||
public IFilterParent Parent { get; set; }
|
||||
|
||||
public bool Subtract { get; set; }
|
||||
|
||||
public string? FilePath { get; set; }
|
||||
|
||||
public void ReloadMask()
|
||||
{
|
||||
if (!File.Exists(FilePath))
|
||||
{
|
||||
CreateMask();
|
||||
}
|
||||
else
|
||||
if (!string.IsNullOrEmpty(FilePath))
|
||||
{
|
||||
_mask = new Mat(FilePath).CvtColor(ColorConversionCodes.BGR2GRAY);
|
||||
}
|
||||
LoadMaskToGpu();
|
||||
}
|
||||
|
||||
private unsafe void LoadMaskToGpu()
|
||||
{
|
||||
var maskBytes = new Span<byte>(Mask.DataPointer, Mask.Width * Mask.Height).ToArray();
|
||||
if (_maskGpu != null)
|
||||
{
|
||||
_maskGpu.Dispose();
|
||||
}
|
||||
_maskGpu = Kernels.Accelerator.Allocate1D<byte>(maskBytes);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static MemoryBuffer1D<byte, Stride1D.Dense>? _imageMemory;
|
||||
private static MemoryBuffer1D<byte, Stride1D.Dense>? _resMemory;
|
||||
|
||||
|
||||
public SpatialFilterItemGPU()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
void CreateMask()
|
||||
{
|
||||
_mask = new Mat(13005 + 256, 256, MatType.CV_8UC1, (Scalar)0);
|
||||
}
|
||||
|
||||
public SpatialFilterItemGPU(CategoryItem parent) : this()
|
||||
{
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public Mat Mask
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_mask == null)
|
||||
{
|
||||
if (FilePath != null)
|
||||
{
|
||||
ReloadMask();
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateMask();
|
||||
}
|
||||
}
|
||||
|
||||
return _mask;
|
||||
}
|
||||
set
|
||||
{
|
||||
_mask = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static Mat? _lastLoadedImage;
|
||||
|
||||
|
||||
public unsafe void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
|
||||
|
||||
|
||||
var w = image.Width;
|
||||
var mw = Mask.Width;
|
||||
|
||||
|
||||
|
||||
if (_lastLoadedImage != image)
|
||||
{
|
||||
var imageBytes = new ReadOnlySpan<byte>(image.DataPointer, image.Width * image.Height * 3);
|
||||
_imageMemory ??= Kernels.Accelerator.Allocate1D<byte>(imageBytes.Length);
|
||||
_imageMemory.View.BaseView.CopyFromCPU(imageBytes);
|
||||
_lastLoadedImage = image;
|
||||
}
|
||||
|
||||
var len = (int)_imageMemory.Length;
|
||||
|
||||
_resMemory ??= Kernels.Accelerator.Allocate1D<byte>(len / 3);
|
||||
|
||||
|
||||
|
||||
Kernels.SpatialKernel(len/3, w, mw, _imageMemory.View, _maskGpu.View, _resMemory.View);
|
||||
var arr = _resMemory.GetAsArray1D();
|
||||
|
||||
|
||||
var candidates=new Mat(new[]{image.Height,image.Width}, MatType.CV_8UC1);
|
||||
candidates.SetArray(arr);
|
||||
|
||||
|
||||
if (Subtract && !IsSelected)
|
||||
{
|
||||
res -= candidates;
|
||||
}
|
||||
else
|
||||
{
|
||||
res |= candidates;
|
||||
|
||||
}
|
||||
|
||||
|
||||
candidates.Dispose();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter writer)
|
||||
{
|
||||
writer.Write(FilePath ?? "");
|
||||
writer.Write(Subtract);
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
FilePath = reader.ReadString();
|
||||
Console.WriteLine("Loaded path:" + FilePath);
|
||||
if (FilePath.StartsWith("..\\../"))
|
||||
FilePath = FilePath.Replace("..\\../", "..\\");
|
||||
Subtract = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var totalColors = 0;
|
||||
if (Mask != null)
|
||||
{
|
||||
totalColors = Mask.CountNonZero();
|
||||
}
|
||||
return $"{(Subtract ? "-" : "+")}Spatial Filter({totalColors} colors)";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses.Filters;
|
||||
|
||||
public class SubtractFilterItem : IFilter,IFilterParent
|
||||
{
|
||||
public IFilterParent Parent { get; set; }
|
||||
|
||||
public List<IFilter> Filters { get; set; } = new List<IFilter>();
|
||||
|
||||
public void Transform(ref Mat image, ref Mat res)
|
||||
{
|
||||
using var tracker = new ResourcesTracker();
|
||||
var temp = tracker.NewMat(res.Size(),MatType.CV_8UC1,Scalar.Black);
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
filter.Transform(ref image, ref temp);
|
||||
}
|
||||
res = res - temp;
|
||||
temp.Dispose();
|
||||
}
|
||||
|
||||
public void Save(BinaryWriter bw)
|
||||
{
|
||||
bw.Write(Filters.Count);
|
||||
foreach (var filter in Filters)
|
||||
{
|
||||
bw.Write(filter.GetType().FullName);
|
||||
filter.Save(bw);
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(BinaryReader reader)
|
||||
{
|
||||
int count = reader.ReadInt32();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var type = reader.ReadString();
|
||||
var filter = (IFilter)Activator.CreateInstance(Type.GetType(type));
|
||||
filter.Load(reader);
|
||||
filter.Parent = this;
|
||||
Filters.Add(filter);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Subtract";
|
||||
}
|
||||
}
|
||||
125
Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/HSIValue.cs
Normal file
125
Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/HSIValue.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses
|
||||
{
|
||||
public class HSIValue
|
||||
{
|
||||
|
||||
public HSIValue()
|
||||
{
|
||||
|
||||
}
|
||||
public int MinSize { get; set; } = 1;
|
||||
public int MinComponentSize { get; set; } = 1;
|
||||
public int PreFilterId { get; set; } = 0;
|
||||
public int PreFilterSize { get; set; } = 10;
|
||||
|
||||
public int Closing { get; set; }
|
||||
|
||||
public int Circle { get; set; } = 1;
|
||||
public string Tag { get; set; }
|
||||
private Range<int> _hue = new Range<int>() {Minimum = 0,Maximum = 10};
|
||||
private Range<int> _saturation = new Range<int>() { Minimum = 0, Maximum = 10 };
|
||||
private Range<int> _intensity = new Range<int>() { Minimum = 0, Maximum = 10 };
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public Range<int> Hue
|
||||
{
|
||||
get { return _hue; }
|
||||
set
|
||||
{
|
||||
if (Equals(value, _hue)) return;
|
||||
_hue = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public Range<int> Saturation
|
||||
{
|
||||
get { return _saturation; }
|
||||
set
|
||||
{
|
||||
if (Equals(value, _saturation)) return;
|
||||
_saturation = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public Range<int> Intensity
|
||||
{
|
||||
get { return _intensity; }
|
||||
set
|
||||
{
|
||||
if (Equals(value, _intensity)) return;
|
||||
_intensity = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Active { get; set; }
|
||||
public List<HSIValue> SubColors { get; set; }=new List<HSIValue>();
|
||||
public Rectangle GetRectangle()
|
||||
{
|
||||
Rectangle th = new Rectangle((int) (Hue.Minimum), (int) (Saturation.Minimum),
|
||||
(int) (Hue.Maximum) - (int) (Hue.Minimum), (int) (Saturation.Maximum) - (int) (Saturation.Minimum));
|
||||
return th;
|
||||
}
|
||||
[XmlIgnore]
|
||||
public bool ShowStar { get; set; }
|
||||
|
||||
public int Dilation { get; set; }
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name} {Hue}:{Saturation}:{Intensity}{(ShowStar?"*":"")}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static explicit operator ColorConfig(HSIValue value)
|
||||
{
|
||||
var config = new ColorConfig()
|
||||
{
|
||||
ClosingCircleRadius = value.Circle,
|
||||
HueHighSaturationMaxGrey = value.Hue.Maximum,
|
||||
HueHighSaturationMinGrey = value.Hue.Minimum,
|
||||
HighSaturationMaxGrey = value.Saturation.Maximum,
|
||||
HighSaturationMinGrey = value.Saturation.Minimum,
|
||||
IntensityMaxGrey = value.Intensity.Maximum,
|
||||
IntensityMinGrey = value.Intensity.Minimum,
|
||||
MaxBlobSize = value.MinSize,
|
||||
MedianMargin = 1,
|
||||
MedianRadius = 10,
|
||||
UseMedianFilter = true,
|
||||
|
||||
};
|
||||
return config;
|
||||
}
|
||||
|
||||
public static implicit operator HSIValue(ColorConfig config)
|
||||
{
|
||||
return new HSIValue()
|
||||
{
|
||||
Hue = new Range<int>(config.HueHighSaturationMinGrey, config.HueHighSaturationMaxGrey),
|
||||
Saturation = new Range<int>(config.HighSaturationMinGrey, config.HighSaturationMaxGrey),
|
||||
Intensity = new Range<int>(config.IntensityMinGrey, config.IntensityMaxGrey),
|
||||
Circle = (int)config.ClosingCircleRadius,
|
||||
MinSize = config.MaxBlobSize
|
||||
};
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
157
Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Kernels.cs
Normal file
157
Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Kernels.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using OpenCvSharp;
|
||||
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses;
|
||||
|
||||
public static class Kernels
|
||||
{
|
||||
static Kernels()
|
||||
{
|
||||
Init();
|
||||
AppDomain.CurrentDomain.ProcessExit +=
|
||||
Destruct;
|
||||
}
|
||||
|
||||
private static void Destruct(object? sender, EventArgs e)
|
||||
{
|
||||
_accelerator.Dispose();
|
||||
_gpuContext.Dispose();
|
||||
}
|
||||
|
||||
|
||||
private static object _lock = new object();
|
||||
private static ILGPU.Context _gpuContext;
|
||||
private static Accelerator _accelerator;
|
||||
private static Action<Index1D, int, int, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>> _spatialKernel;
|
||||
private static Action<Index1D, int, int, ArrayView<byte>, ArrayView<byte>> _dilationKernel;
|
||||
|
||||
public static Action<Index1D, int, int, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>> SpatialKernel => _spatialKernel;
|
||||
|
||||
public static Action<Index1D, int, int, ArrayView<byte>, ArrayView<byte>> Dilation => _dilationKernel;
|
||||
|
||||
public static Accelerator Accelerator
|
||||
{
|
||||
get => _accelerator;
|
||||
set => _accelerator = value;
|
||||
}
|
||||
|
||||
static Dictionary<Mat, MemoryBuffer1D<byte,Stride1D.Dense>> _imageCache = new ();
|
||||
|
||||
public static unsafe MemoryBuffer1D<byte, Stride1D.Dense> TransformImageCached(Mat image)
|
||||
{
|
||||
if (_imageCache.ContainsKey(image))
|
||||
return _imageCache[image];
|
||||
var imageBytes = new Span<byte>(image.DataPointer, image.Width * image.Height * 3);
|
||||
|
||||
var res=Kernels.Accelerator.Allocate1D<byte>(imageBytes.ToArray());
|
||||
_imageCache[image] = res;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
static void Init()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
|
||||
if (_gpuContext != null) return;
|
||||
|
||||
_gpuContext = ILGPU.Context.Create(builder => builder.AllAccelerators());
|
||||
_accelerator = _gpuContext.Devices.First(x => x.AcceleratorType == AcceleratorType.Cuda)
|
||||
.CreateAccelerator(_gpuContext);
|
||||
Console.WriteLine("GPU acceleration initialized");
|
||||
|
||||
|
||||
_spatialKernel = _accelerator
|
||||
.LoadAutoGroupedStreamKernel<Index1D, int, int, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>>(
|
||||
Kernels.TransformSpatialKernel);
|
||||
|
||||
_dilationKernel = _accelerator
|
||||
.LoadAutoGroupedStreamKernel<Index1D, int, int, ArrayView<byte>,ArrayView<byte>>(
|
||||
Kernels.DilationKernel);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void TransformSpatialKernel(Index1D index, int w, int mw, ArrayView<byte> candyBytes, ArrayView<byte> maskBytes, ArrayView<byte> res)
|
||||
{
|
||||
var i = index;
|
||||
var p = i * 3;
|
||||
var y = p / w;
|
||||
var x = p % w;
|
||||
|
||||
|
||||
|
||||
var yp = (int)candyBytes[p + 0];
|
||||
var xp = candyBytes[p + 1];
|
||||
var vp = (byte)(candyBytes[p + 2] / 5f) * 255;
|
||||
|
||||
var hp = yp + vp;
|
||||
var maskColor = maskBytes[hp * mw + xp];
|
||||
if (maskColor > 0)
|
||||
res[i] = 255;
|
||||
else
|
||||
res[i] = 0;
|
||||
}
|
||||
|
||||
private static void DilationKernel(Index1D index, int w, int size, ArrayView<byte> maskBytes,
|
||||
ArrayView<byte> res)
|
||||
{
|
||||
var i = index;
|
||||
var p = i ;
|
||||
var y = p / w;
|
||||
var x = p % w;
|
||||
|
||||
|
||||
var halfSize = size-1 / 2;
|
||||
|
||||
|
||||
if (maskBytes[p] > 0)
|
||||
{
|
||||
for (int j = -halfSize; j <= halfSize; j++)
|
||||
{
|
||||
for (int k = -halfSize; k <= halfSize; k++)
|
||||
{
|
||||
var yp = y + j;
|
||||
var xp = x + k;
|
||||
if (yp < 0 || yp >= w || xp < 0 || xp >= w)
|
||||
continue;
|
||||
res[yp * w + xp] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void ErosionKernel(Index1D index, int w, int size, ArrayView<byte> maskBytes,
|
||||
ArrayView<byte> res)
|
||||
{
|
||||
var i = index;
|
||||
var p = i ;
|
||||
var y = p / w;
|
||||
var x = p % w;
|
||||
|
||||
|
||||
var halfSize = size-1 / 2;
|
||||
|
||||
|
||||
if (maskBytes[p] <255)
|
||||
{
|
||||
for (int j = -halfSize; j <= halfSize; j++)
|
||||
{
|
||||
for (int k = -halfSize; k <= halfSize; k++)
|
||||
{
|
||||
var yp = y + j;
|
||||
var xp = x + k;
|
||||
if (yp < 0 || yp >= w || xp < 0 || xp >= w)
|
||||
continue;
|
||||
res[yp * w + xp] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
64
Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Range.cs
Normal file
64
Hawkeye.VisionBuilder.Workflow/ColorStudioClasses/Range.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
namespace Hawkeye.VisionBuilder.Workflow.ColorStudioClasses
|
||||
{
|
||||
/// <summary>The Range class.</summary>
|
||||
/// <typeparam name="T">Generic parameter.</typeparam>
|
||||
public class Range<T> where T : IComparable<T>
|
||||
{
|
||||
|
||||
public Range()
|
||||
{
|
||||
|
||||
}
|
||||
public Range(T min,T max)
|
||||
{
|
||||
this.Maximum = max;
|
||||
this.Minimum = min;
|
||||
}
|
||||
|
||||
/// <summary>Minimum value of the range.</summary>
|
||||
public T Minimum { get; set; }
|
||||
|
||||
/// <summary>Maximum value of the range.</summary>
|
||||
public T Maximum { get; set; }
|
||||
|
||||
/// <summary>Presents the Range in readable format.</summary>
|
||||
/// <returns>String representation of the Range</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[{0} - {1}]", this.Minimum, this.Maximum);
|
||||
}
|
||||
|
||||
/// <summary>Determines if the range is valid.</summary>
|
||||
/// <returns>True if range is valid, else false</returns>
|
||||
public bool IsValid()
|
||||
{
|
||||
return this.Minimum.CompareTo(this.Maximum) <= 0;
|
||||
}
|
||||
|
||||
/// <summary>Determines if the provided value is inside the range.</summary>
|
||||
/// <param name="value">The value to test</param>
|
||||
/// <returns>True if the value is inside Range, else false</returns>
|
||||
public bool ContainsValue(T value)
|
||||
{
|
||||
return (this.Minimum.CompareTo(value) <= 0) && (value.CompareTo(this.Maximum) <= 0);
|
||||
}
|
||||
|
||||
/// <summary>Determines if this Range is inside the bounds of another range.</summary>
|
||||
/// <param name="Range">The parent range to test on</param>
|
||||
/// <returns>True if range is inclusive, else false</returns>
|
||||
public bool IsInsideRange(Range<T> range)
|
||||
{
|
||||
return this.IsValid() && range.IsValid() && range.ContainsValue(this.Minimum) && range.ContainsValue(this.Maximum);
|
||||
}
|
||||
|
||||
/// <summary>Determines if another range is inside the bounds of this range.</summary>
|
||||
/// <param name="Range">The child range to test</param>
|
||||
/// <returns>True if range is inside, else false</returns>
|
||||
public bool ContainsRange(Range<T> range)
|
||||
{
|
||||
return this.IsValid() && range.IsValid() && this.ContainsValue(range.Minimum) && this.ContainsValue(range.Maximum);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user