Counter.cs 12.0 KB
using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace wafer_die_counter
{
    public class Counter
    {
        public static Result docount(string srcfile, string resultfile, bool isdebug=false)
        {
            Result result = new Result();
            Stopwatch sw = Stopwatch.StartNew();
            try
            {
                Mat org = Cv2.ImRead(srcfile, ImreadModes.Grayscale);
                Mat bgimg = Cv2.ImRead(srcfile, ImreadModes.Color);
                var a = Cv2.HoughCircles(org, HoughModes.Gradient, 1, 100, 100, 50, (872-50) / 2, (872+50) / 2);
                foreach (var circle in a)
                { 
                    // 画圆
                    Cv2.Circle(org, circle.Center.ToPoint(), (int)circle.Radius, new Scalar(0, 255, 0), 2); // 最后一个参数是线条宽度
                }
                if (isdebug)
                    ImShow("cycle", org);
                //Cv2.WaitKey(100000);
                var c = Cv2.HoughCircles(org, HoughModes.Gradient, 1, 100, 80, 80, (1150-50) / 2, (1150+50) / 2);
                if (c.Length == 0)
                {
                    result.msg = "未找到圆形";
                    return result;
                }
                var radius = (int)c[0].Radius - 50;
                Cv2.Circle(org, c[0].Center.ToPoint(), radius, new Scalar(125), 5);
                var newp = PointWithAngle(c[0].Center.ToPoint(), 45 + 180, radius);
                Cv2.Circle(org, newp, 10, new Scalar(255), 5);

                var sl = CalculateShortSideLength(radius);
                var rect = new Rect(newp, new OpenCvSharp.Size(sl * 2, sl * 2));

                Cv2.Rectangle(org, rect, new Scalar(255), 5);

                if (isdebug)
                    ImShow("cycle", org);

                if (org.Width < rect.Width + rect.X
                    || org.Height < rect.Height + rect.Y)
                {
                    result.msg = "找到的圆形位置不准确";
                    return result;
                }

                int Threshold = 42;
                int Threshold1 = 42;
                int Threshold2 = 42;
                {// 获得分割阈值方法1
                    Dictionary<int, int> selavg = new Dictionary<int, int>();
                    for (int i = 35; i < 50; i++)
                    {
                        var temp = new Mat(rect.Size, MatType.CV_8U, 0);
                        Cv2.Threshold(org[rect], temp, i, 255, ThresholdTypes.Binary);
                        var avg = temp.Mean().Val0;
                        if (avg >= 254)
                        {
                            i++;
                            continue;
                        }
                        if (avg > 100)
                        {
                            selavg.Add(i, (int)avg);
                        }
                        else
                            break;
                    }
                    var mid = FindMinValueGreater(selavg.Values.ToArray());
                    var sel = selavg.First(x => x.Value == mid);
                    Threshold1 = sel.Key;
                }
                {// 获得分割阈值方法2
                    for (int i = 35; i < 50; i++)
                    {
                        var rect2 = new Rect(0, 0, 110, 110);//截取左上角100x100范围
                        var temp2 = new Mat(rect2.Size, MatType.CV_8U, 0);
                        //获取左上角白色区域二值化后的平均值
                        Cv2.Threshold(org[rect][rect2], temp2, i, 255, ThresholdTypes.Binary);
                        var avg2 = temp2.Mean().Val0;

                        if (avg2 == 0)
                            continue;

                        if (avg2 < 254.3)
                        {
                            Threshold2 = i - 1;
                            break;
                        }

                    }
                }
                Threshold = Math.Min(Threshold1, Threshold2);
                //Mat output=new Mat();
                //Cv2.CalcHist(org[rect].Split(), new int[] { 0}, null, output, 1, new int[] { 255 }, new Rangef[] { new Rangef(0, 255) });
                //var ii = new float[output.Col(0).Rows];
                //var xx = output.Col(0).GetArray<float>(out ii);
                //ImShow("org", output);
                //return;

                Cv2.Threshold(org[rect], org[rect], Threshold, 255, ThresholdTypes.Binary);
                Cv2.Threshold(bgimg, bgimg, Threshold, 255, ThresholdTypes.Binary);
                if (isdebug)
                    ImShow("org", org[rect]);
                //Cv2.ImWrite("d:\\logs\\ttt.png", org[rect]);


                Mat mask = new Mat(org.Size(), MatType.CV_8U, 1);
                mask.SetTo(new Scalar(0));
                Cv2.Rectangle(mask, rect, new Scalar(255), -1);

                string txt = $"Threshold:{Threshold1},{Threshold2},C:{c[0].Center.ToPoint()},R:{c[0].Radius}";

                //BlockTester.FindBlock(org[rect]);

                #region SimpleBlobDetector.Params
                var cparams = new SimpleBlobDetector.Params();
                cparams.ThresholdStep = 10;
                cparams.FilterByColor = true;
                cparams.BlobColor = 0;
                cparams.MinRepeatability = 1;
                cparams.MinDistBetweenBlobs = 0.1f;
                // 改变阈值
                //cparams.MinThreshold = 0;
                //cparams.MaxThreshold = 255;
                //通过面积滤波
                cparams.FilterByArea = true;
                cparams.MinArea = 0.5f;
                cparams.MaxArea = float.MaxValue;
                // 通过圆度滤波
                cparams.FilterByCircularity = true;
                cparams.MinCircularity = 0.001f;
                // 通过凸度滤波
                cparams.FilterByConvexity = true;
                cparams.MinConvexity = 0.001f;
                //cparams.MaxConvexity = 1;
                // 通过惯性比滤波
                cparams.FilterByInertia = true;
                cparams.MinInertiaRatio = 0.001f;
                //cparams.MaxInertiaRatio = 1;
                #endregion
                SimpleBlobDetector simpleBlob = SimpleBlobDetector.Create(cparams);
                KeyPoint[] keypoints = new KeyPoint[0];
                var t = Task.Run(() =>
                {
                    keypoints = simpleBlob.Detect(org, mask);
                });
                var tw = t.Wait(30 * 1000);
                if (tw)
                {
                    var rkeypoints = DBSCAN(keypoints.ToList(), 7,3);
                    var disrkeypoints = new List<KeyPoint>();
                    var green = new Scalar(0, 255, 0);
                    for (int i = 0; i < rkeypoints.Count; i++)
                    {
                        //keypoints[i].Size = 1;
                        for (int j = 0; j < rkeypoints[i].Count; j++)
                        {
                            if (!disrkeypoints.Contains(rkeypoints[i][j])) {
                                disrkeypoints.Add(rkeypoints[i][j]);
                            }
                        }
                    }

                    for (int j = 0; j < disrkeypoints.Count; j++)
                        Cv2.Circle(bgimg, disrkeypoints[j].Pt.ToPoint(), 1, green, 1, LineTypes.Link8);

                    //Cv2.DrawKeypoints(org, keypoints, org, Scalar.FromRgb(255, 0, 0), DrawMatchesFlags.NotDrawSinglePoints);
                    Cv2.PutText(bgimg[rect], $"{disrkeypoints.Count}", new Point(bgimg[rect].Width / 2 - 20, 30), HersheyFonts.HersheySimplex, 1, green, 2);
                    result.success = true;
                    result.qty = disrkeypoints.Count;
                    if (isdebug)
                        ImShow("org1", bgimg[rect]);
                }
                else
                {
                    txt += " Time out";

                }
                result.msg = txt+","+ sw.ElapsedMilliseconds;
                Cv2.PutText(bgimg, txt, new Point(5, bgimg.Height - 40), HersheyFonts.HersheySimplex, 1, Scalar.Blue, 2);
                Cv2.ImWrite(resultfile, bgimg[rect]);
            }
            catch (Exception ex) {
                result.success=false;
                result.msg = ex.ToString();
            }
            return result;
        }


        static int FindMinValueGreater(int[] arr)
        {
            int maxDiff = 0;
            int maxNum = 0;
            for (int i = 0; i < arr.Length - 1; i++)
            {

                var diff = arr[i] - arr[i + 1];
                if (diff > maxDiff)
                {
                    maxDiff = diff;
                    maxNum = Math.Max(arr[i + 1], arr[i]);
                }
            }
            return maxNum;
        }
        public static OpenCvSharp.Point PointWithAngle(OpenCvSharp.Point p1, double angle, double distance)
        {
            var x2 = p1.X + distance * Math.Cos(angle / 180 * Math.PI);
            var y2 = p1.Y + distance * Math.Sin(angle / 180 * Math.PI);
            return new OpenCvSharp.Point((int)x2, (int)y2);
        }
        static void ImShow(string WindowsName, Mat mat)
        {
            Cv2.NamedWindow(WindowsName, WindowFlags.GuiNormal);
            Cv2.ResizeWindow(WindowsName, 1024, 768);
            Cv2.ImShow(WindowsName, mat);
        }
        static double CalculateShortSideLength(double longSideLength)
        {
            // 使用勾股定理计算短边长度
            double shortSideLength = Math.Sqrt((longSideLength * longSideLength) / 2);

            return shortSideLength;
        }

        static List<List<KeyPoint>> DBSCAN(List<KeyPoint> points, double epsilon, int minPoints)
        {
            List<List<KeyPoint>> clusters = new List<List<KeyPoint>>();
            HashSet<KeyPoint> visited = new HashSet<KeyPoint>();

            foreach (var point in points)
            {
                if (visited.Contains(point))
                    continue;

                visited.Add(point);

                List<KeyPoint> neighbors = GetNeighbors(point, points, epsilon);

                if (neighbors.Count < minPoints)
                    continue;

                List<KeyPoint> cluster = new List<KeyPoint> { point };
                visited.Add(point);

                ExpandCluster(cluster, point, neighbors, visited, points, epsilon, minPoints);

                if (cluster.Count >= minPoints)
                    clusters.Add(cluster);
            }

            return clusters;
        }

        // 获取邻近点
        static List<KeyPoint> GetNeighbors(KeyPoint point, List<KeyPoint> points, double epsilon)
        {
            List<KeyPoint> neighbors = new List<KeyPoint>();

            foreach (var otherPoint in points)
            {
                if (CalculateDistance(point.Pt, otherPoint.Pt) <= epsilon)
                    neighbors.Add(otherPoint);
            }

            return neighbors;
        }

        // 扩展群体
        static void ExpandCluster(List<KeyPoint> cluster, KeyPoint point, List<KeyPoint> neighbors, HashSet<KeyPoint> visited, List<KeyPoint> points, double epsilon, int minPoints)
        {
            foreach (var neighbor in neighbors.ToArray())
            {
                if (!visited.Contains(neighbor))
                {
                    visited.Add(neighbor);
                    List<KeyPoint> neighborNeighbors = GetNeighbors(neighbor, points, epsilon);

                    if (neighborNeighbors.Count >= minPoints)
                        neighbors.AddRange(neighborNeighbors);
                }

                if (!cluster.Contains(neighbor))
                    cluster.Add(neighbor);
            }
        }

        // 计算两点之间的距离
        static double CalculateDistance(Point2f p1, Point2f p2)
        {
            return Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2));
        }

    }
    [Serializable]
    public class Result {
        public bool success = false;
        public int qty = 0;
        public string msg = "";
    
    }
}