如何在 OpenCV 中找到多个框的中心?

How do I find the center of multiple boxes in OpenCV?

我正在编写一个 python 脚本,该脚本接收视频输入并且应该找到滴答钉板的中心。截至目前,我能够找到电路板的轮廓,但我不知道如何找到每个小盒子的中心。

代码如下:

import numpy as np
import cv2

video = cv2.VideoCapture(0)
    
while 1:
    ret, frame = video.read()
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    blur = cv2.medianBlur(gray, 3)
    thresh = cv2.adaptiveThreshold(blur, 255, 1, 1, 11, 2)
    contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

    c = 0
    for i in contours:
           area = cv2.contourArea(i)
           if area > 200:
                cv2.drawContours(frame, contours, c, (0, 255, 0), 3)
           c+=1
    cv2.imshow("frame", frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
       break

这是我得到的帧:

我不认为这是正确的方法,因为 tick-tack-toe 板线是连接的,我们很可能总是以单个大轮廓结束。为了使事情变得简单,您可以尝试检测图像中的线条。

您可以尝试cv2.HoughLinesPcv2.createLineSegmentDetector来识别行。我相信你可以在检测到线路后继续前进。

此外,您可以查看 How to detect lines in OpenCV?,不同的用例也可以根据您的目的进行扩展。

概念

  1. 使用基本算法找到图像中的轮廓。在这种情况下,只会检测到 2 个轮廓;整个 tic-tac-toe 网格,以及 tic-tac-toe 网格的中心框。

  2. 得到2个等高线的convex hull,排序,这样我们就知道从哪里访问top-left点,top-right点等2组点中的每一个:

  1. 根据这些信息,我们可以计算出每个盒子的中心。对于 4 个角框中的每一个,只需找到构成框的 2 条线的尖端的中心。对于其余的方框,只需找到组成方框的4个点的中心:

代码

import cv2
import numpy as np

def process(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_blur = cv2.GaussianBlur(img_gray, (5, 5), 0)
    img_canny = cv2.Canny(img_blur, 0, 100)
    kernel = np.ones((2, 2))
    img_dilate = cv2.dilate(img_canny, kernel, iterations=8)
    return cv2.erode(img_dilate, kernel, iterations=2)

def convex_hull(cnt):
    peri = cv2.arcLength(cnt, True)
    approx = cv2.approxPolyDP(cnt, peri * 0.02, True)
    return cv2.convexHull(approx).squeeze()

def centers(inner, outer):
    c = inner[..., 0].argsort()
    top_lef2, top_rit2 = sorted(inner[c][:2], key=list)
    bot_lef2, bot_rit2 = sorted(inner[c][-2:], key=list)
    c1 = outer[..., 0].argsort()
    c2 = outer[..., 1].argsort()
    top_lef, top_rit = sorted(outer[c1][:2], key=list)
    bot_lef, bot_rit = sorted(outer[c1][-2:], key=list)
    lef_top, lef_bot = sorted(outer[c2][:2], key=list)
    rit_top, rit_bot = sorted(outer[c2][-2:], key=list)
    yield inner.mean(0)
    yield np.mean([top_lef, top_rit, top_lef2, top_rit2], 0)
    yield np.mean([bot_lef, bot_rit, bot_lef2, bot_rit2], 0)
    yield np.mean([lef_top, lef_bot, top_lef2, bot_lef2], 0)
    yield np.mean([rit_top, rit_bot, top_rit2, bot_rit2], 0)
    yield np.mean([top_lef, lef_top], 0)
    yield np.mean([bot_lef, lef_bot], 0)
    yield np.mean([top_rit, rit_top], 0)
    yield np.mean([bot_rit, rit_bot], 0)
    
img = cv2.imread(r"D:/OpenCV Projects/TicTacToe centers/tictactoe.png")
contours, _ = cv2.findContours(process(img), cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
inner, outer = sorted(map(convex_hull, contours), key=len)

for x, y in centers(inner, outer):
    cv2.circle(img, (int(x), int(y)), 5, (0, 0, 255), -1)

cv2.imshow("result", img)
cv2.waitKey(0)

输出

解释

  1. 导入必要的库:
import cv2
import numpy as np
  1. 定义一个函数,process(),接受一个图像数组和 return 一个二值图像 (即图像的处理版本)这将允许稍后在图像上进行适当的轮廓检测:
def process(img):
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_blur = cv2.GaussianBlur(img_gray, (5, 5), 0)
    img_canny = cv2.Canny(img_blur, 0, 100)
    kernel = np.ones((2, 2))
    img_dilate = cv2.dilate(img_canny, kernel, iterations=8)
    return cv2.erode(img_dilate, kernel, iterations=2)
  1. 定义一个函数,convex_hull(),接受一个轮廓数组和 returns 一个数组,该数组是轮廓的近似版本的凸包:
def convex_hull(cnt):
    peri = cv2.arcLength(cnt, True)
    approx = cv2.approxPolyDP(cnt, peri * 0.02, True)
    return cv2.convexHull(approx).squeeze()
  1. 定义一个函数,centers(),接受两个数组; tic-tac-toe 网格内框的凸包,以及整个 tic-tac-toe 网格的凸包。在函数中,进行必要的排序,使每个单独的点都在一个变量中,变量名与该点的位置相对应;这将允许轻松计算每个中心点:
def centers(inner, outer):
    c = inner[..., 0].argsort()
    top_lef2, top_rit2 = sorted(inner[c1][:2], key=list)
    bot_lef2, bot_rit2 = sorted(inner[c1][-2:], key=list)
    c1 = outer[..., 0].argsort()
    c2 = outer[..., 1].argsort()
    top_lef, top_rit = sorted(outer[c1][:2], key=list)
    bot_lef, bot_rit = sorted(outer[c1][-2:], key=list)
    lef_top, lef_bot = sorted(outer[c2][:2], key=list)
    rit_top, rit_bot = sorted(outer[c2][-2:], key=list)
  1. 仍在 center() 函数中,生成框的中心。 np.mean() 方法,当使用 0 作为 axis 关键字参数时,将 return 传递给方法的坐标数组中坐标的中心:
    yield inner.mean(0)
    yield np.mean([top_lef, top_rit, top_lef2, top_rit2], 0)
    yield np.mean([bot_lef, bot_rit, bot_lef2, bot_rit2], 0)
    yield np.mean([lef_top, lef_bot, top_lef2, bot_lef2], 0)
    yield np.mean([rit_top, rit_bot, top_rit2, bot_rit2], 0)
    yield np.mean([top_lef, lef_top], 0)
    yield np.mean([bot_lef, lef_bot], 0)
    yield np.mean([top_rit, rit_top], 0)
    yield np.mean([bot_rit, rit_bot], 0)
  1. 读入图像,求其轮廓(也是先用process()函数对图像进行处理),求轮廓的凸包,绘制在具有 centers() 函数的框的中心:
img = cv2.imread(r"D:/OpenCV Projects/TicTacToe centers/tictactoe.png")
contours, _ = cv2.findContours(process(img), cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
inner, outer = sorted(map(convex_hull, contours), key=len)

for x, y in centers(inner, outer):
    cv2.circle(img, (int(x), int(y)), 5, (0, 0, 255), -1)
  1. 最后,显示生成的图像:
cv2.imshow("result", img)
cv2.waitKey(0)