habrahabr

Домашний тир на Raspberry

  • вторник, 20 января 2015 г. в 02:11:27
http://habrahabr.ru/post/248181/

Привет Хабр.
Люблю стрелковое оружие и стрельбу. Однако в домашних условий это плохое хобби. Нет, ну можно конечно купить травмат и изрешетить квартиру, но, думаю, домашние этого не оценят. Не желая мирится с этим, решил реализовать свой, в меру безопасный домашний тир. Если заинтересовал — добро пожаловать под кат.



Идеи, как это можно реализовать, витали в голове давно. Вот несколько забракованных:
— пистолет с фототранзистором + экран монитора. Подсвечивая половину/четверть/одну восьмую/и т.д. экрана, проверяем ответ от фототранзистора и итеративно уточняем часть экрана, в которую направлен пистолет. Идею забраковал из-за низкой частоты обновления мониторов и их инерционности.
— пистолет с фототранзистором + экран из светодиодных матриц. Уже лучше, можно обновлять изображение на диодной матрице с достаточной частотой. Даже начал спаивать диодные матрицы, но вовремя одумался.
— пистолет с камерой, несколько лазерных светодиодов, образующих метки на стене, по которым камера определяет свое положение. В принципе идея была не плоха. Однако прикинув, как будет смотреться пистолет с прикрученной к нему вебкамерой, так же от нее отказался.
Ну и финальная идея — статическая камера, смотрящая на стену и пистолет с лазером. Идея есть, дело за реализацией.
Купил первый попавшийся детский пистолет(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

Не забываем про безопасность — на лазер, как и в телескоп на солнце, можно посмотреть только два раза…
В будущем хотелось бы установить в пистолет сервомашинку, которая будет дергать груз для симуляции отдачи. Ну и распечатать нормальную мишень.