阅读 桌面完整版 machine-learning
本数鸡系列:
这已经是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);
强烈推荐
- 英国代购-畅购英伦
- TopCashBack 返现 (英国购物必备, 积少成多, 我2年来一共得了3000多英镑)
- Quidco 返现 (也是很不错的英国返现网站, 返现率高)
- 注册就送10美元, 免费使用2个月的 DigitalOcean 云主机(性价比超高, 每月只需5美元)
- 注册就送10美元, 免费使用4个月的 Vultr 云主机(性价比超高, 每月只需2.5美元)
- 注册就送10美元, 免费使用2个月的 阿里 云主机(性价比超高, 每月只需4.5美元)
- 注册就送20美元, 免费使用4个月的 Linode 云主机(性价比超高, 每月只需5美元)
(折扣码: PodCastInit2022)
- PlusNet 英国光纤(超快, 超划算! 用户名 doctorlai)
- 刷了美国运通信用卡一年得到的积分 换了 485英镑
- 注册就送50英镑 – 英国最便宜最划算的电气提供商
- 能把比特币莱特币变现的银行卡! 不需要手续费就可以把虚拟货币法币兑换
微信公众号: 小赖子的英国生活和资讯 JustYYUK
阅读 桌面完整版