using HalconDotNet;
|
using OpenCvSharp;
|
using Point = OpenCvSharp.Point;
|
|
namespace LB_VisionProcesses.Alogrithms.OpenCvSharp
|
{
|
[Process("OpenCvSharp_斑点工具", Category = "OpenCvSharp工具", Description = "创建OpenCvSharp_斑点工具")]
|
public class BlobTool : TAlgorithm
|
{
|
public BlobTool()
|
{
|
strProcessClass = "LB_VisionProcesses.Alogrithms.OpenCvSharp.BlobTool";
|
strProcessName = "OpenCvSharp_斑点工具";
|
|
Params.Inputs.Add("MinThreshold", 127);
|
Params.Inputs.Add("MaxThreshold", 255);
|
Params.Inputs.Add("MinArea", 0);
|
Params.Inputs.Add("MaxArea", double.MaxValue);
|
Params.Inputs.Add("MinRow", 0.0);
|
Params.Inputs.Add("MaxRow", double.MaxValue);
|
Params.Inputs.Add("MinColumn", 0.0);
|
Params.Inputs.Add("MaxColumn", double.MaxValue);
|
Params.Inputs.Add("MinCircularity", 0.0);
|
Params.Inputs.Add("MaxCircularity", 1.0); ;
|
Params.Inputs.Add("MinRectangularity", 0.0);
|
Params.Inputs.Add("MaxRectangularity", 1.0);
|
Params.Inputs.Add("MinCount", 0);
|
Params.Inputs.Add("MaxCount", 9999);
|
|
Params.Outputs.Add("CenterX", new List<double>());
|
Params.Outputs.Add("CenterY", new List<double>());
|
Params.Outputs.Add("Angle", new List<double>());
|
Params.Outputs.Add("Width", new List<double>());
|
Params.Outputs.Add("Height", new List<double>());
|
Params.Outputs.Add("Area", new List<double>());
|
Params.Outputs.Add("Count", 0);
|
}
|
|
/// <summary>
|
/// 算子逻辑
|
/// </summary>
|
public override void TAlgorithmMain()
|
{
|
#region 初始化变量
|
HObject ho_Regions, ho_ConnectedRegions, ho_SelectedRegions;
|
HOperatorSet.GenEmptyObj(out ho_Regions);
|
HOperatorSet.GenEmptyObj(out ho_ConnectedRegions);
|
#endregion
|
|
try
|
{
|
if (InputImage == null)
|
{
|
Msg = "输入图片为空";
|
Result = false;
|
return;
|
}
|
if (!(InputImage is Mat))
|
{
|
Msg = "输入图片格式不为Mat";
|
Result = false;
|
return;
|
}
|
|
#region 裁剪区域
|
object DomainImage = null;
|
if (!ReduceDomainImage(InputImage, ref DomainImage))
|
{
|
Msg = "裁剪区域失败";
|
Result = false;
|
return;
|
}
|
Mat hoDomainImage = DomainImage as Mat;
|
//判断是否为灰度图
|
try
|
{
|
if (hoDomainImage.Channels() != 1)
|
Cv2.CvtColor(hoDomainImage, hoDomainImage, ColorConversionCodes.RGB2GRAY);
|
|
//转换后再次检查是否为灰度图
|
if (hoDomainImage.Channels() != 1)
|
{
|
Msg = "输入图片不为灰度图";
|
Result = false;
|
return;
|
}
|
}
|
catch
|
{
|
Msg = "输入图片不为灰度图且转换失败";
|
Result = false;
|
return;
|
}
|
#endregion
|
|
#region 算子逻辑
|
Record = new ObjectRecord();
|
double MinThreshold = Convert.ToDouble(Params.Inputs["MinThreshold"]);
|
double MaxThreshold = Convert.ToDouble(Params.Inputs["MaxThreshold"]);
|
double MinArea = Convert.ToDouble(Params.Inputs["MinArea"]);
|
double MaxArea = Convert.ToDouble(Params.Inputs["MaxArea"]);
|
double MinRow = Convert.ToDouble(Params.Inputs["MinRow"]);
|
double MaxRow = Convert.ToDouble(Params.Inputs["MaxRow"]);
|
double MinColumn = Convert.ToDouble(Params.Inputs["MinColumn"]);
|
double MaxColumn = Convert.ToDouble(Params.Inputs["MaxColumn"]);
|
double MinCircularity = Convert.ToDouble(Params.Inputs["MinCircularity"]);
|
double MaxCircularity = Convert.ToDouble(Params.Inputs["MaxCircularity"]);
|
double MinRectangularity = Convert.ToDouble(Params.Inputs["MinRectangularity"]);
|
double MaxRectangularity = Convert.ToDouble(Params.Inputs["MaxRectangularity"]);
|
List<Blob> results = AnalyzeBlobs(hoDomainImage, MinArea, MaxArea, MinCircularity, MinRectangularity, MinThreshold, MaxThreshold);
|
#endregion
|
|
#region 结果处理
|
//计数用于后续判断
|
List<double> CenterX = new List<double>();
|
List<double> CenterY = new List<double>();
|
List<double> Angle = new List<double>();
|
List<double> Width = new List<double>();
|
List<double> Height = new List<double>();
|
List<double> Area = new List<double>();
|
for (int i = 0; i < results.Count; i++)
|
{
|
// 填充为矩形
|
CenterX.Add(Convert.ToDouble(results[i].RotatedBoundingBox.Center.X));
|
CenterY.Add(Convert.ToDouble(results[i].RotatedBoundingBox.Center.Y));
|
Angle.Add(Convert.ToDouble(results[i].RotatedBoundingBox.Angle));
|
Width.Add(Convert.ToDouble(results[i].RotatedBoundingBox.Size.Width));
|
Height.Add(Convert.ToDouble(results[i].RotatedBoundingBox.Size.Height));
|
Area.Add(results[i].ContourArea);
|
|
//HOperatorSet.GenRectangle2(out HObject hRectangle, CenterY[i], CenterX[i], -1 * Angle[i] / 180 * Math.PI
|
// , Width[i] / 2, Height[i] / 2);
|
//((ObjectRecord)Record).AddRecord(hRectangle);
|
|
var contourArray = results[i].Contour.ToArray();
|
int count = contourArray.Length;
|
|
int[] Xpoints = new int[count + 1];
|
int[] Ypoints = new int[count + 1];
|
|
Parallel.For(0, count, j =>
|
{
|
var point = contourArray[j];
|
Xpoints[j] = point.X;
|
Ypoints[j] = point.Y;
|
});
|
|
Xpoints[count] = Xpoints[0];
|
Ypoints[count] = Ypoints[0];
|
|
HOperatorSet.GenContourPolygonXld(out HObject contour, new HTuple(Ypoints), new HTuple(Xpoints));
|
((ObjectRecord)Record).AddXld(contour);
|
}
|
|
|
|
|
|
Params.Outputs["CenterX"] = CenterX;
|
Params.Outputs["CenterY"] = CenterY;
|
Params.Outputs["Angle"] = Angle;
|
Params.Outputs["Width"] = Width;
|
Params.Outputs["Height"] = Height;
|
Params.Outputs["Area"] = Area;
|
Params.Outputs["Count"] = CenterX.Count;
|
#endregion
|
|
#region 生成OutputImage给后续处理
|
try
|
{
|
OutputImage = DomainImage;
|
}
|
catch (Exception ex)
|
{
|
Msg = "生成OutputImage失败,原因是:" + ex.ToString();
|
Result = false;
|
return;
|
}
|
#endregion
|
|
if (Msg == "运行超时")
|
{
|
Result = false;
|
return;
|
}
|
|
int MinCount = ProcessParams.ConvertToInt32(Params.Inputs["MinCount"]);
|
int MaxCount = ProcessParams.ConvertToInt32(Params.Inputs["MaxCount"]);
|
|
if (CenterX.Count < MinCount || CenterX.Count > MaxCount)
|
{
|
Msg = string.Format("结果个数超出范围({0},{1})", MinCount, MaxCount);
|
Record.ChangeAll2False();
|
Result = false;
|
return;
|
}
|
|
Msg = "运行成功";
|
Result = true;
|
return;
|
}
|
catch (Exception ex)
|
{
|
Msg = "运行失败,原因是:" + ex.ToString().TrimEnd();
|
OutputImage = null;
|
Result = false;
|
return;
|
}
|
finally
|
{
|
bCompleted = true;
|
|
#region 内存释放
|
ho_Regions.Dispose();
|
ho_ConnectedRegions.Dispose();
|
#endregion
|
}
|
}
|
|
public class BlobRotatedRect
|
{
|
public Point2f Center { get; set; }
|
public Size2f Size { get; set; }
|
public float Angle { get; set; }
|
public Point2f[] Points { get; set; }
|
|
public BlobRotatedRect() { }
|
|
public BlobRotatedRect(RotatedRect rect)
|
{
|
Center = rect.Center;
|
Size = rect.Size;
|
Angle = rect.Angle;
|
Points = Cv2.BoxPoints(rect);
|
}
|
}
|
|
public class Blob
|
{
|
public int Id { get; set; }
|
//public Rect BoundingBox { get; set; }
|
public BlobRotatedRect RotatedBoundingBox { get; set; }
|
//public Point2f Centroid { get; set; }
|
//public double Area { get; set; }
|
public double Perimeter { get; set; }
|
public double Circularity { get; set; }
|
public double Rectangularity { get; set; }
|
public double AspectRatio { get; set; }
|
public List<Point> Contour { get; set; }
|
public double ContourArea { get; set; }
|
//public double RotatedRectArea { get; set; }
|
}
|
|
public static List<Blob> AnalyzeBlobs(Mat image,
|
double minArea = 50,
|
double maxArea = 10000,
|
double minCircularity = 0.1,
|
double minRectangularity = 0.5,
|
double minThreshold = 0,
|
double maxThreshold = 255,
|
ThresholdTypes thresholdType = ThresholdTypes.Binary)
|
{
|
var blobs = new List<Blob>();
|
|
// 转换为灰度图
|
Mat gray = new Mat();
|
if (image.Channels() == 3)
|
Cv2.CvtColor(image, gray, ColorConversionCodes.BGR2GRAY);
|
else
|
image.CopyTo(gray);
|
|
// 阈值分割
|
Mat binary = new Mat();
|
Cv2.Threshold(gray, binary, minThreshold, maxThreshold, thresholdType);
|
|
// 查找轮廓
|
Point[][] contours;
|
HierarchyIndex[] hierarchy;
|
Cv2.FindContours(binary, out contours, out hierarchy,
|
RetrievalModes.External, ContourApproximationModes.ApproxSimple);
|
|
// 处理每个轮廓
|
for (int i = 0; i < contours.Length; i++)
|
{
|
var contour = contours[i];
|
if (contour.Length < 5) continue; // 至少需要5个点才能拟合旋转矩形
|
|
double area = Cv2.ContourArea(contour);
|
|
// 面积过滤
|
if (area < minArea || area > maxArea)
|
continue;
|
|
// 计算周长
|
double perimeter = Cv2.ArcLength(contour, true);
|
|
// 计算圆形度
|
double circularity = (4 * Math.PI * area) / (perimeter * perimeter);
|
|
// 圆形度过滤
|
if (circularity < minCircularity)
|
continue;
|
|
// 计算外接矩形
|
Rect boundingBox = Cv2.BoundingRect(contour);
|
|
// 计算最小外接矩形(带角度)
|
RotatedRect rotatedRect = Cv2.MinAreaRect(contour);
|
BlobRotatedRect rotatedBoundingBox = new BlobRotatedRect(rotatedRect);
|
|
// 计算质心
|
Moments moments = Cv2.Moments(contour);
|
Point2f centroid = new Point2f(
|
(float)(moments.M10 / moments.M00),
|
(float)(moments.M01 / moments.M00)
|
);
|
|
// 计算矩形度
|
double rectangularity = CalculateRectangularity(contour, rotatedRect);
|
|
// 矩形度过滤
|
if (rectangularity < minRectangularity)
|
continue;
|
|
// 计算宽高比
|
double aspectRatio = CalculateAspectRatio(rotatedRect);
|
|
blobs.Add(new Blob
|
{
|
Id = i,
|
//Area = area,
|
Perimeter = perimeter,
|
Circularity = circularity,
|
Rectangularity = rectangularity,
|
AspectRatio = aspectRatio,
|
//BoundingBox = boundingBox,
|
RotatedBoundingBox = rotatedBoundingBox,
|
//Centroid = centroid,
|
Contour = new List<Point>(contour),
|
ContourArea = area
|
//RotatedRectArea = rotatedRect.Size.Width * rotatedRect.Size.Height
|
});
|
}
|
|
gray.Dispose();
|
binary.Dispose();
|
|
return blobs;
|
}
|
|
/// <summary>
|
/// 计算矩形度(轮廓面积与最小外接矩形面积的比值)
|
/// </summary>
|
private static double CalculateRectangularity(Point[] contour, RotatedRect rotatedRect)
|
{
|
double contourArea = Cv2.ContourArea(contour);
|
double rotatedRectArea = rotatedRect.Size.Width * rotatedRect.Size.Height;
|
|
if (rotatedRectArea == 0) return 0;
|
|
return contourArea / rotatedRectArea;
|
}
|
|
/// <summary>
|
/// 计算宽高比
|
/// </summary>
|
private static double CalculateAspectRatio(RotatedRect rotatedRect)
|
{
|
double width = rotatedRect.Size.Width;
|
double height = rotatedRect.Size.Height;
|
|
if (height == 0) return double.MaxValue;
|
|
// 确保宽高比始终 >= 1
|
return width >= height ? width / height : height / width;
|
}
|
|
/// <summary>
|
/// 根据矩形度过滤Blob
|
/// </summary>
|
public static List<Blob> FilterByRectangularity(List<Blob> blobs, double minRectangularity, double maxRectangularity = 1.0)
|
{
|
return blobs.Where(b => b.Rectangularity >= minRectangularity && b.Rectangularity <= maxRectangularity).ToList();
|
}
|
|
/// <summary>
|
/// 根据宽高比过滤Blob
|
/// </summary>
|
public static List<Blob> FilterByAspectRatio(List<Blob> blobs, double minAspectRatio, double maxAspectRatio)
|
{
|
return blobs.Where(b => b.AspectRatio >= minAspectRatio && b.AspectRatio <= maxAspectRatio).ToList();
|
}
|
|
/// <summary>
|
/// 根据方向角度过滤Blob
|
/// </summary>
|
public static List<Blob> FilterByAngle(List<Blob> blobs, double minAngle, double maxAngle)
|
{
|
return blobs.Where(b =>
|
{
|
double angle = Math.Abs(b.RotatedBoundingBox.Angle);
|
// 处理角度周期性
|
if (angle > 90) angle = 180 - angle;
|
return angle >= minAngle && angle <= maxAngle;
|
}).ToList();
|
}
|
}
|
}
|