本数鸡系列:
- 机器学习系列之: 怎么样数鸡?
- 机器学习系列之: 怎么样数鸡鸡? 大津算法来计算阈值
- 机器学习系列之: 怎么样数鸡鸡? 分类 Clustering
这已经是3个月前的事了, 后来懒癌, 加收益变少没动力, 于是一直搁放着, 今天突然想起, 有点时间变想把这事完结了.
上次说到, 通过大津算法 Otsu’s Method计算出一个阀值, 然后就可以把图片变成黑白的, 比如白的是鸡, 黑的是空气.
我们就可以遍历这张黑白图片的每个相素点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /// <summary> /// 遍历图片的每个相素点 /// </summary> /// <param name="binImage">黑白图片</param> private static void ClusterAllPoints(Bitmap binImage) { for (int i = 0; i < binImage.Width; i++) { for (int j = 0; j < binImage.Height; j++) { if (binImage.GetPixel(i, j).ToArgb() == Color.White.ToArgb()) { ClusterPoint(new Point(i, j)); } } } } |
/// <summary> /// 遍历图片的每个相素点 /// </summary> /// <param name="binImage">黑白图片</param> private static void ClusterAllPoints(Bitmap binImage) { for (int i = 0; i < binImage.Width; i++) { for (int j = 0; j < binImage.Height; j++) { if (binImage.GetPixel(i, j).ToArgb() == Color.White.ToArgb()) { ClusterPoint(new Point(i, j)); } } } }
这里会用到 ClusterPoint 这个方法对于当前点进行分组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | /// <summary> /// KNN 算法 /// </summary> /// <param name="p">相素点</param> private static void ClusterPoint(Point p) { List<Point> chosenCluster = null; double votesCast = 0d; // 如果还没有发现任何 Clusters 也就是第一个相素点 if (Clusters.Count == 0) { List<Point> l = new List<Point>(); l.Add(p); Clusters.Add(l); } else { // 否则我们需要遍历当前所有的 类 Clusters.ForEach(cluster => { // 找到和这个 点在规定阀值内的所有点 List<Point> votingPoints = cluster.FindAll(point => IsCloseTo(point, p)); // 然后需要把这些点的投票权重加起来 double totalVotes = votingPoints.AsParallel().Sum( aPoint => CalculateVoteOfPoint(aPoint, p)); // 如果投票总值比当前的大, 那么更新选定类别 if (totalVotes > votesCast) { votesCast = totalVotes; chosenCluster = cluster; } }); // 把这一点加到选定的类别中 if (chosenCluster != null) { chosenCluster.Add(p); } else { // 如果找不到类别, 那么新创建一个并添加该点. List<Point> l = new List<Point>(); l.Add(p); Clusters.Add(l); } } } |
/// <summary> /// KNN 算法 /// </summary> /// <param name="p">相素点</param> private static void ClusterPoint(Point p) { List<Point> chosenCluster = null; double votesCast = 0d; // 如果还没有发现任何 Clusters 也就是第一个相素点 if (Clusters.Count == 0) { List<Point> l = new List<Point>(); l.Add(p); Clusters.Add(l); } else { // 否则我们需要遍历当前所有的 类 Clusters.ForEach(cluster => { // 找到和这个 点在规定阀值内的所有点 List<Point> votingPoints = cluster.FindAll(point => IsCloseTo(point, p)); // 然后需要把这些点的投票权重加起来 double totalVotes = votingPoints.AsParallel().Sum( aPoint => CalculateVoteOfPoint(aPoint, p)); // 如果投票总值比当前的大, 那么更新选定类别 if (totalVotes > votesCast) { votesCast = totalVotes; chosenCluster = cluster; } }); // 把这一点加到选定的类别中 if (chosenCluster != null) { chosenCluster.Add(p); } else { // 如果找不到类别, 那么新创建一个并添加该点. List<Point> l = new List<Point>(); l.Add(p); Clusters.Add(l); } } }
计算每个点的投票值, 我们可以用距离来算, 距离越远, 那么它对当前点的投票权重就越低.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /// <summary> /// 两点之间的 Euclidean 距离 /// </summary> /// <param name="p1">第一个点</param> /// <param name="p2">第二个点</param> /// <returns></returns> private static double EuclideanDistanceBetween(Point p1, Point p2) { return Math.Sqrt( Math.Pow((p1.X - p2.X), 2) + Math.Pow((p1.Y - p2.Y), 2)); } /// <summary> /// 根据 Euclidean 距离来计算权重 /// </summary> /// <param name="neighbour"></param> /// <param name="candidate"></param> /// <returns></returns> private static double CalculateVoteOfPoint( Point neighbour, Point candidate) { return 1 / EuclideanDistanceBetween(neighbour, candidate); } |
/// <summary> /// 两点之间的 Euclidean 距离 /// </summary> /// <param name="p1">第一个点</param> /// <param name="p2">第二个点</param> /// <returns></returns> private static double EuclideanDistanceBetween(Point p1, Point p2) { return Math.Sqrt( Math.Pow((p1.X - p2.X), 2) + Math.Pow((p1.Y - p2.Y), 2)); } /// <summary> /// 根据 Euclidean 距离来计算权重 /// </summary> /// <param name="neighbour"></param> /// <param name="candidate"></param> /// <returns></returns> private static double CalculateVoteOfPoint( Point neighbour, Point candidate) { return 1 / EuclideanDistanceBetween(neighbour, candidate); }
这样一来, 我们就可以得到几个类别, 不过我们需要进一步提高精度, 因为有些白色的点是属于背景噪音, 比如我们可以假定, 小于10个相素点的类别不算鸡(可能是毛啊啥的)
1 2 3 4 5 6 7 8 9 | /// <summary> /// 去掉背景噪音 /// </summary> /// <param name="noiseThreshold">The threshold</param> private static void CullNoiseClusters(int noiseThreshold) { Clusters.RemoveAll(cluster => cluster.Count < noiseThreshold); } |
/// <summary> /// 去掉背景噪音 /// </summary> /// <param name="noiseThreshold">The threshold</param> private static void CullNoiseClusters(int noiseThreshold) { Clusters.RemoveAll(cluster => cluster.Count < noiseThreshold); }
还有就是, 有可能两个类别的中心靠得太近了, 所以我们需要合并两个非常近的鸡, 有可能是鸡展开翅膀.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /// <summary> /// 如果中心太靠近, 合并两个类 /// </summary> private static void MergeClusters() { List<List<Point>> delList = new List<List<Point>>(); List<Point>[] c = Clusters.ToArray(); int ptr1 = 0; int ptr2 = 1; while (ptr1 < c.Length) { while (ptr2 < c.Length) { List<Point> l1 = c[ptr1]; List<Point> l2 = c[ptr2]; Point ctr1 = GetClusterCentrePoint(l1); Point ctr2 = GetClusterCentrePoint(l2); if (EuclideanDistanceBetween(ctr1, ctr2) < MergeThreshold) { l1.AddRange(l2); delList.Add(l2); } ptr2++; } ptr1++; ptr2 = ptr1 + 1; } delList.ForEach(list => Clusters.Remove(list)); } |
/// <summary> /// 如果中心太靠近, 合并两个类 /// </summary> private static void MergeClusters() { List<List<Point>> delList = new List<List<Point>>(); List<Point>[] c = Clusters.ToArray(); int ptr1 = 0; int ptr2 = 1; while (ptr1 < c.Length) { while (ptr2 < c.Length) { List<Point> l1 = c[ptr1]; List<Point> l2 = c[ptr2]; Point ctr1 = GetClusterCentrePoint(l1); Point ctr2 = GetClusterCentrePoint(l2); if (EuclideanDistanceBetween(ctr1, ctr2) < MergeThreshold) { l1.AddRange(l2); delList.Add(l2); } ptr2++; } ptr1++; ptr2 = ptr1 + 1; } delList.ForEach(list => Clusters.Remove(list)); }
类取中心的方法很简单:
1 2 3 4 5 6 7 8 9 10 11 | /// <summary> /// 返回每个鸡类的中心点 /// </summary> /// <param name="cluster">类别</param> /// <returns>The centre point</returns> private static Point GetClusterCentrePoint(List<Point> cluster) { return new Point( (int)cluster.AsParallel().Average(p => p.X), (int)cluster.AsParallel().Average(p => p.Y)); } |
/// <summary> /// 返回每个鸡类的中心点 /// </summary> /// <param name="cluster">类别</param> /// <returns>The centre point</returns> private static Point GetClusterCentrePoint(List<Point> cluster) { return new Point( (int)cluster.AsParallel().Average(p => p.X), (int)cluster.AsParallel().Average(p => p.Y)); }
这样, 数鸡就完成了:
1 2 3 4 | // 数了多少只? Console.WriteLine( "\nI 发现 {0} 只鸡 ", Clusters.Count); |
// 数了多少只? Console.WriteLine( "\nI 发现 {0} 只鸡 ", Clusters.Count);
GD Star Rating
loading...
本文一共 543 个汉字, 你数一下对不对.loading...
上一篇: [活动] 晒晒你的18岁 What did you look like when you were 18?
下一篇: 买房这事, 或许还挺靠谱的 - 记 STEEM大涨
扫描二维码,分享本文到微信朋友圈
