Статья не содержит описания важных достижений, просьба относиться к ней как к DIY поделке. Когда искал ответ на вопрос не нашел (плохо искал) решения с применением openCV, а так же двух и более камер для наблюдения за объектами.


Сегодня в сети можно найти множество статей посвященных распознаванию, отслеживанию объектов и дальнейшей обработке полученной информации. Для моего проекта требуется определение положения объекта по "трем осям". То есть помимо определения координат x и y - с чем прекрасно справляется одна вебкамера (камера 1), необходимо получить "глубину" - координаты, которые покажут удаление объекта от камеры 1.

Все задумывалось как тест камер на реальной задаче. Чтобы было не так скучно, перевел всё в игру для двоих, что порадовало домашних.

На картинке ниже объяснение работы скрипта. Камера 1 отслеживает координаты объекта и переводит их в управление мышью на экране. Камера 2 ослеживает зону, при попадании в которую производится клик левой кнопки мыши.

Прицип работы - камера 1 следит за координатами, камера 2 "говорит" когда делать клик
Прицип работы - камера 1 следит за координатами, камера 2 "говорит" когда делать клик

Использовались 2 вебкамеры logitech c270, проектор, компьютер, поросенок.
Скрипт на python, кликер на unity. Кликер при попадании в круг добавляет игроку очко и считает общее количество ходов.

Ниже стандартный код, распознавания объекта по цвету. Комментарии оставил только к тем блокам, которые добавил. Так как основной код расжеван в сети много раз.

import cv2
from collections import deque
import argparse
import pyautogui

#работаем с двумя камерами
camera = cv2.VideoCapture(0)
camera1 = cv2.VideoCapture(1)

ap = argparse.ArgumentParser()
ap.add_argument("-v", "--video",
	help="path to the (optional) video file")
ap.add_argument("-b", "--buffer", type=int, default=64,
	help="max buffer size")
args = vars(ap.parse_args())


colorLower = (4, 100, 100)
colorUpper = (24, 255, 255)
pts = deque(maxlen=args["buffer"])


while True:

  (grabbed, frame) = camera.read()
  (grabbed, frame1) = camera1.read()
  hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

  hsv1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2HSV)
  mask1 = cv2.inRange(hsv1, colorLower, colorUpper)
  mask1 = cv2.erode(mask1, None, iterations=2)
  mask1 = cv2.dilate(mask1, None, iterations=2)
  cnts = cv2.findContours(mask1.copy(), cv2.RETR_EXTERNAL,
                          cv2.CHAIN_APPROX_SIMPLE)[-2]

  hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
  mask = cv2.inRange(hsv, colorLower, colorUpper)
  mask = cv2.erode(mask, None, iterations=2)
  mask = cv2.dilate(mask, None, iterations=2)
  cnts1 = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL,
                          cv2.CHAIN_APPROX_SIMPLE)[-2]

  center = None

  # only proceed if at least one contour was found
  if len(cnts) > 0:
    # find the largest contour in the mask, then use
    # it to compute the minimum enclosing circle and
    # centroid
    c = max(cnts, key=cv2.contourArea)
    ((x, y), radius) = cv2.minEnclosingCircle(c)
    M = cv2.moments(c)
    center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
    #КРУГ-РАДИУС
    # only proceed if the radius meets a minimum size
    if radius > 10:
      # draw the circle and centroid on the frame,
      # then update the list of tracked points
      cv2.circle(frame1, (int(x), int(y)), int(radius),
                 (0, 255, 255), 2)
      cv2.circle(frame1, center, 5, (0, 0, 255), -1)
#дублируем движение объекта, курсором мыши
      pyautogui.moveTo(int(x)*2, int(y)*2)

  if len(cnts1) > 0:
    # find the largest contour in the mask, then use
    # it to compute the minimum enclosing circle and
    # centroid
    c = max(cnts1, key=cv2.contourArea)
    ((x, y), radius) = cv2.minEnclosingCircle(c)
    M = cv2.moments(c)
    center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
    #КРУГ-РАДИУС
    # only proceed if the radius meets a minimum size
    if radius > 10:
      # draw the circle and centroid on the frame,
      # then update the list of tracked points
      cv2.circle(frame, (int(x), int(y)), int(radius),
                 (0, 255, 255), 2)
      cv2.circle(frame, center, 5, (0, 0, 255), -1)
#клик, когда шарик попадает в зону "клика" второй камеры
      if int(x)>290:
        pyautogui.click()
#задержка чтобы наш поросенок отлетел от стены, иначе накрутит много очков
        cv2.waitKey(700)

  # update the points queue
  pts.appendleft(center)
#линия настройки, на картинке получаемой с камеры контроля "клика"
  cv2.line(frame, (320, 0), (320, 512), (0, 255, 0), thickness=2)
  cv2.line(frame, (295, 0), (295, 512), (0, 255, 0), thickness=2)

  #cv2.imshow("Frame", frame)
  #cv2.imshow("Frame1", frame1)

  key = cv2.waitKey(1) & 0xFF

  # if the 'q' key is pressed, stop the loop
  if key == ord("q"):
    break

# cleanup the camera and close any open windows
camera.release()
cv2.destroyAllWindows()

Почему использовался плюшевый поросенок? Изначально все затачивалось под тенисный мячик. И планировалось как игра на двоих с тенисными ракетками. Но камера 2 не успевает "засечь" мячик и сделать клик. Поросенок по цвету отлично подошел для отслеживания.
Почему на стену проецировалось изображение с проектора? Изначально была мысль делать большую мишень из бумаги, но узнав, что крепиться она будет на новые обои, мысль зарубили на корню. Но тут есть плюсы, можно использовать проектор для любых игр кликеров.

Я в ВК https://vk.com/zxgamevr ссылка на Git (если нужен скрипт по получению цвета для отслеживания) https://github.com/zxgame0/PuzikFly

Комментарии (3)


  1. Daddy_Cool
    13.01.2023 15:10

    Спасибо за статью, очень интересно!
    А какая задержка всё же? Сколько времени требуется чтобы понять, что поросенок в круге?
    Мне нужно нечто похожее, но у меня счет может идти на доли секунды.


    1. zxgame Автор
      13.01.2023 16:25

      Тест проводился как раз для этого, чтобы выяснить проблемные моменты. Что хочу сказать по камерам c270: на количество кадров в секунду вляет освещение, по тесту при комнатном освещении показывает в пределах 20 fps в лучшем случае. Точно расчитать задержку не высчитывал, но исходя, что был фоновый звук, разницу прикинул где-то в 0,3 секунды(это на глаз). Прямой отскок теннисного шарика от стенки не фиксирует. Плюс к минусам - эти камеры видят медленно движущийся теннисный шарик на расстоянии не более 2 метров. Не могу пока что сообразить кто "ест" больше времени задержки - медленные камеры однозначно(нужно искать с реальными 60fps), сколько уходит на обработку это уже вопрос.

      Честно сказать искать с высоким фпс камеры не буду - мне требуется отслеживать спокойное перемещение человека, поэтому точно не скажу. Повторюсь если исходить из звука - у свиньи пуговица стукала об стенку, число на экране было с задержкой где то 0,3сек.


      1. lxsmkv
        14.01.2023 07:23

        Попробуйте может воспользоваться телефонной камерой. Через приложение типа https://reincubate.com/camo/ .Все настраивается довольно просто. Приложение может снимать с камеры до 60 кадров в секунду в зависимости от настроек. Думаю телефонные камеры технически лучше подходят для захвата движущегося изображения.

        ЗЫ: Телефонные камеры просто на порядок лучше чем любые современные вебкамеры. Я уже как-то сокрушался по этому поводу, что продавать вебкамеры сегодня это развод для лохов, когда телефонная камера настолько круче.