Домашний тир на Raspberry
Привет Хабр.Люблю стрелковое оружие и стрельбу. Однако в для домашних условий это плохое хобби. Нет, ну можно конечно купить травмат и изрешетить квартиру, но думаю домашние этого не оценят. Не желая мирится с этим, решил реализовать свой, в меру безопасный домашний тир. Если заинтересовал — добро пожаловать под кат.Идеи, как это можно реализовать, витали в голове давно. Вот несколько забракованных: — пистолет с фототранзистором + экран монитора. Подсвечивая половину/четверть/одну восьмую/и т.д. экрана, проверяем ответ от фототранзистора и итеративно уточняем часть экрана, в которую направлен пистолет. Идею забраковал из-за низкой частоты обновления мониторов и их инерционности.— пистолет с фототранзистором + экран из светодиодных матриц. Уже лучше, можно обновлять изображение на диодной матрице с достаточной частотой. Даже начал спаивать диодные матрицы, но вовремя одумался.— пистолет с камерой, несколько лазерных светодиодов, образующих метки на стене, по которым камера определяет свое положение. В принципе идея была не плоха. Однако прикинув, как будет смотреться пистолет с прикрученной к нему вебкамерой, так же от нее отказался.Ну и финальная идея — статическая камера, смотрящая на стену и пистолет с лазером. Идея есть, дело за реализацией.Купил первый попавшийся детский пистолет (Desert Eagle калибра 50). Выкинул внутренности, обработал напильником и установил в него лазерный диод, кнопку на спусковой крючок и ардуинину nano. Нет, ну можно конечно поставить туда в место ардуинины конденсатор, так что бы он кнопкой переключался с источника питания на диод и обратно, но это не достаточно гибкий подход. Лазерный диод приклеил на холодную сварку. Пока она застывала, аккуратно корректировал включенный диод, совмещая с прицельной планкой.Скрытый текст Скрытый текст Написал простейший скетч: Скрытый текст void setup () { pinMode (3, OUTPUT);//LED pinMode (2, INPUT);//Button to ground digitalWrite (2, true); }
int t = 10000; bool PreButton = false;
void loop () { bool Button = ! digitalRead (2); if (PreButton == false && Button == true && t > 500) t = 0; if (t<5) digitalWrite(3, true); else digitalWrite(3, false); if (t<10000) t++; PreButton = Button; delay(1); } Пистолет «стреляет» короткими импульсами по 4мс (подобрал в процессе настройки) с максимальной скорострельностью 2 выстрела в секунду.Далее дело за приемной стороной. Купил простейшую вебкумеру. Малинка уже была в закромах. Подключил камеру, направил на стену.Скрытый текст Далее нужно поставить на малинку необходимые пакеты sudo apt-get install libv4l-0 libopencv-dev python-opencv Осталось написать питоновский скрипт. Это был мой первый скрипт на питоне, по этому пришлось убить на его почти день.Скрытый текст #!/usr/bin/python
import sys import cv2 import math import subprocess
if __name__ == '__main__':
#target in camera CenterX = 426.5 CenterY = 190.5 Radius = 40.0
width = 800 height = 640 capture = cv2.VideoCapture (0) capture.set (3, width); capture.set (4, height); image = cv2.imread («target.jpg», cv2.CV_LOAD_IMAGE_COLOR) target_x = float (image.shape[0])*0.5 target_y = float (image.shape[1])*0.5 target_Radius = min (target_x, target_y) target = image.copy () cv2.namedWindow («Result», 1) cv2.imshow («Result», target)
ShotCount = int (); Scoore = 0; while 1: if cv2.waitKey (1) >= 0: break ret, frame = capture.read () grey_image = cv2.cvtColor (frame, cv2.COLOR_BGR2GRAY) ret, grey_image = cv2.threshold (grey_image, 245, 255, cv2.THRESH_BINARY) # grey_image = cv2.erode (grey_image, None, iterations = 1) # grey_image = cv2.dilate (grey_image, None, iterations = 1)
(contour, _) = cv2.findContours (grey_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if contour: subprocess.Popen ('aplay Shot.wav', shell = True) cntr = sorted (contour, key = cv2.contourArea, reverse = True)[0] (x, y), radius = cv2.minEnclosingCircle (cntr) center = (x, y) shot_x = (float (x) — CenterX)/Radius shot_y = (float (y) — CenterY)/Radius dist = math.sqrt (shot_x*shot_x+shot_y*shot_y) shot_x = target_x + shot_x*target_Radius shot_y = target_y + shot_y*target_Radius Shot = (int (shot_x), int (shot_y)) cv2.circle (target, Shot, 5, (60,60,255),10) cv2.circle (target, Shot, 10, (120,120,120),1) cv2.imshow («Result», target) #calibrate #print (center, dist) print («Shots», ShotCount+1) if dist < 1.0: Scoore += 1 - dist ShotCount += 1 if ShotCount > 6: ShotCount = 0; Scoore = Scoore/7.0×100.0 print («You Scoore:», Scoore) Scoore = 0 target = image.copy () cv2.waitKey (300) subprocess.Popen ('aplay 924.wav', shell = True) cv2.waitKey (1000) cv2.waitKey (50)
cv2.destroyAllWindows () Немного пояснений. Скрипт делает снимки с камеры и преобразует их в черно-белые. Далее отсекает все что темнее 245. Как показала практика пятно лазерного диода детектируется очень уверенно даже при длине импульса всего пару миллисекунд. Далее находим контур пятна и минимальную окружность, его описывающую. Рисуем попадания на мишени, проигрываем звук. После семи «выстрелов» подсчитываем очки (коих можно набить максимум 100).Перед стрельбой нужно откалибровать положение мишени в камере.Кстати «мишень»: Скрытый текст У меня камера стоит в трех метрах от мишени. Раскомментируем строку #print (center, dist), стреляем, пока не попадем точно в центр. Смотрим в логе позицию попадания и прописываем в начало скрипта (CenterX, CenterY). Так же там правим Radius под свой размер мишени.Разрешающая способность камеры с трех метров порядка двух миллиметров. Если этого покажется мало, можно просто придвинуть камеру.Все, впадаем в детство приступаем к занятиям по огневой подготовке.Процесс выглядит так (сори за обшарпанные обои — живу на съемной квартире):[embedded content]Исходники к проекту: github.com/DIMOSUS/Laser-shoting
Не забываем про безопасность — на лазер, как и в телескоп на солнце, можно посмотреть только два раза…В будущем хотелось бы установить в пистолет сервомашинку, которая будет дергать груз для симуляции отдачи. Ну и распечатать нормальную мишень.