Домашній тир на 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 під свій розмір мішені.
Роздільна здатність камери з трьох метрів близько двох міліметрів. Якщо цього здасться мало, можна просто присунути камеру.
Все, впадаємо в дитинство приступаємо до занять з вогневої підготовки.
Процес виглядає так (сорі за обшарпані шпалери — живу на знімній квартирі):


Исходники до проекту:
github.com/DIMOSUS/Laser-shoting

У майбутньому хотілося б встановити пістолет сервомашинку, яка буде смикати вантаж для симуляції віддачі. Ну і роздрукувати нормальну мішень.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.