Как стать автором
Обновить

Кликер «полет поросенка» — распознавание и «клики» с opencv

Время на прочтение3 мин
Количество просмотров4K

Статья не содержит описания важных достижений, просьба относиться к ней как к 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

Теги:
Хабы:
Всего голосов 1: ↑1 и ↓0+1
Комментарии3

Публикации

Истории

Работа

Data Scientist
79 вакансий
Python разработчик
116 вакансий

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань