今天有個需求,識別圖片中的圓的圓心,已知都是完整的圓,而且沒有半截的圓,並且圓的顏色都一樣,沒有其他的干擾因素,要尋找這些圓的圓心,稍微思考了一下,這個問題其實沒有那麼複雜,因爲都是完整的圓,那麼就可以根據圓的性質入手,2條經過圓心的線就可以確定一個圓的圓心了,那麼如何來確定這兩條線呢?
圓還有一個性質就是圓是對稱的,所以任意的穿過圓的線的兩個交點的中心就是處在垂直於這條線並且經過圓心的線上面,同理取2條線,然後取交點,就是圓心了
下面的代碼的實現和上述理論的基本實現,但是沒有做優化,請自行優化
void CircleCenter(Texture2D tex) {
//在這裏做一遍圓心識別算法
bool[, ] isCircleColor = new bool[tex.width, tex.height];
for (int i = 0; i < tex.width; i++) {
for (int j = 0; j < tex.height; j++) {
isCircleColor[i, j] = (Init.HexToColor(0x878787) == tex.GetPixel(i, j));
}
}
Stack<Vector2Int> points = new Stack<Vector2Int>();
//識別出來是否是圓形顏色的區域之後 開始做橫向的掃描
for (int y = 0; y < tex.height; y++) {
for (int x = 1; x < tex.width - 1; x++) {
//取點
if (isCircleColor[x, y]) {
//判斷是不是剛好遇到了左切點
if (isCircleColor[x + 1, y] && !isCircleColor[x - 1, y]) {
//不是切點的時候就說明後面還有點 入棧
points.Push(new Vector2Int(x, y));
} else if (!isCircleColor[x + 1, y] && isCircleColor[x - 1, y]) {
if (points.Count != 0) {
//右切點 的時候 出棧並處理2點中間的數據
Vector2Int startPoint = points.Pop();
for (int clearX = startPoint.x; clearX <= x; clearX++) {
isCircleColor[clearX, y] = false;
}
//然後把中點點亮
isCircleColor[(startPoint.x + x) / 2, y] = true;
}
} else if (!isCircleColor[x + 1, y] && !isCircleColor[x - 1, y]) {
isCircleColor[x, y] = false;
}
//剛好遇到的了切點,那就不用管了
}
}
}
// 開始豎向的掃描
const int rectwidth = 2;
for (int x = 0; x < tex.width; x++) {
for (int y = 1; y < tex.height; y++) {
//每次檢測x 到 x + rectwidth 的矩形範圍 考慮上一輪中可能有誤差 所以 要先把誤差給消除
Vector2Int temp = CheckPoint(isCircleColor, x, y, rectwidth);
Vector2Int lowTemp = CheckLowPoint(isCircleColor, x, y, rectwidth);
if (temp == Vector2Int.zero || lowTemp == Vector2Int.zero) {
continue;
}
isCircleColor[temp.x, temp.y] = false;
isCircleColor[lowTemp.x, temp.y] = true;
}
}
// // 真正的豎向掃描
for (int x = 0; x < tex.width; x++) {
for (int y = 1; y < tex.height - 1; y++) {
//取點
if (isCircleColor[x, y]) {
//判斷是不是剛好遇到了左切點
if (isCircleColor[x, y + 1] && !isCircleColor[x, y - 1]) {
//不是切點的時候就說明後面還有點 入棧
points.Push(new Vector2Int(x, y));
} else if (!isCircleColor[x, y + 1] && isCircleColor[x, y - 1]) {
if (points.Count != 0) {
//右切點 的時候 出棧並處理2點中間的數據
Vector2Int startPoint = points.Pop();
for (int clearY = startPoint.y; clearY <= y; clearY++) {
isCircleColor[x, clearY] = false;
}
//然後把中點點亮
isCircleColor[x, (startPoint.y + y) / 2] = true;
}
} else if (!isCircleColor[x, y + 1] && !isCircleColor[x, y - 1]) {
isCircleColor[x, y] = false;
}
//剛好遇到的了切點,那就不用管了
}
}
}
for (int i = 0; i < tex.width; i++) {
for (int j = 0; j < tex.height; j++) {
if (isCircleColor[i, j]) {
tex.SetPixel(i, j, Color.red);
}
}
}
tex.Apply();
}
/// <summary>
/// 檢測下一行是否有點
/// </summary>
Vector2Int CheckLowPoint(bool[, ] isCircleColor, int x, int y, int rectwidth) {
if (y - 1 < 0) {
return Vector2Int.zero;
}
int width = isCircleColor.GetLength(0);
int height = isCircleColor.GetLength(1);
for (int i = Mathf.Max(x - rectwidth, 0); i < Mathf.Min(x + rectwidth, width); i++) {
if (isCircleColor[i, y - 1]) {
return new Vector2Int(i, y);
}
}
return Vector2Int.zero;
}
/// <summary>
/// 檢測上一行是否有點
/// </summary>
Vector2Int CheckHighPoint(bool[, ] isCircleColor, int x, int y, int rectwidth) {
int width = isCircleColor.GetLength(0);
int height = isCircleColor.GetLength(1);
if (y + 1 >= height) {
return Vector2Int.zero;
}
for (int i = Mathf.Max(x - rectwidth, 0); i < Mathf.Min(x + rectwidth, width); i++) {
if (isCircleColor[i, y + 1]) {
return new Vector2Int(i, y);
}
}
return Vector2Int.zero;
}
/// <summary>
/// 檢測當前一行是否有點
/// </summary>
Vector2Int CheckPoint(bool[, ] isCircleColor, int x, int y, int rectwidth) {
int width = isCircleColor.GetLength(0);
int height = isCircleColor.GetLength(1);
for (int i = Mathf.Max(x - rectwidth, 0); i < Mathf.Min(x + rectwidth, width); i++) {
if (isCircleColor[i, y]) {
return new Vector2Int(i, y);
}
}
return Vector2Int.zero;
}