Introduction: Realtime Gun Aiming

About: I am a robotics hobbyist and like circuit designing. I live in Stockholm and am keen to join groups / individuals who like to have fun with hardware.

This was a fun project I wanted to build since I was a child.

A toy gun that aims itself to moving object in front of it.

Ingredients:

  1. Plastic beads pixel art
  2. Aruino UNO
  3. Arduino motor driver board
  4. Two servo motors
  5. A web camera
  6. Arduino programming knowledge
  7. Servo programming knowledge
  8. openCV machie vision library knowledge

Steps:

  1. Build the gun.
  2. Build the mount for gun.
  3. Arduino programming for gun motion control.
  4. OpenCV programming for motion detection.

Step 1: Build the Gun

Buy a set of pixel beads and use your creativity to make a toy gun.

I have used combination of gray, white and black pixel beads to make my toy gun.

Once assembled on the board they should be ironed with a baking paper on top ( so to avoid sticking )

Once individual pieces are made they can be glued together with glue gun.

Any toy gun would work if you want to be quick with this step.

Step 2: Build the Mount for Gun

The mount is simple

I used mechanix game to make the mount for first servo.

Second servo was pasted to first with a glue gun.

The gun was mounted to the second servo with glue gun again.

Step 3: Arduino Programming for Gun Motion Control

Arduino code used:

It connects to serial port.

Uses arduino uno motor driver shield. Servo motors are connected to pin 9 and 10.

Command format it accepts: 50:70\n (x and y rotation angle in degrees)

<p>#include <br>#include </p><p>/* Serial control
 by aroramayank2002@gmail.com
 14-Apr-2017
 Gets angle of rotation from serial port in format 50:70\n
 Parses it and orients the servo x,y to that angle</p><p>  Gun pointing real hardware 
  xMin = top end
  xMax = bottom end
  yMin = right end
  yMax = left end
*/</p><p>#include 
#include </p><p>Servo myservo_10; 
Servo myservo_09;  </p><p>// variable to store the servo position
int posy = 90;    
int posx = 90;</p><p>int posxMax = 140;
int posxMin = 45;</p><p>int posyMax = 140;
int posyMin = 40;</p><p>void setup() {
  // Works parallely
  myservo_10.attach(10);  
  myservo_09.attach(9);</p><p>  Serial.begin(115200);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  servoTest();
  //establishContact();
}</p><p>void printHelp(){
  String xMsg = "x(min,max):(";
  String yMsg = "y(min,max):(";
  Serial.println("----------------------------------------");
  Serial.println("Eg. 12:45   horizontal(x):vertical(y) angle of orientation ");
  Serial.println(xMsg + posxMin + "," + posxMax + ")");
  Serial.println(yMsg + posyMin + "," + posyMax + ")");
  Serial.println("----------------------------------------");
}</p><p>void servoTest(){
  myservo_10.write(posy);              
  myservo_09.write(posx);
  delay(1000);
  myservo_10.detach();  
  myservo_09.detach();</p><p>  Serial.println("Ready!");
}</p><p>// Parse the serial command and execute
void parseCommand(String cmd){</p><p>   char separator = ':';
   int separatorAt = -1;
    int strIndex[] = { 0, -1 };
    int maxIndex = cmd.length() - 1;</p><p>    for (int i = 0; i <= maxIndex; i++) {
        if (cmd.charAt(i) == separator) {
            separatorAt = i;
        }
    }
   if(-1==separatorAt){
     Serial.println("Invalid option, check command '"+cmd+"'");
     printHelp();
     return;
   }
   String x = cmd.substring(0, separatorAt);
   String y = cmd.substring( (separatorAt+1), maxIndex+1);
   String msg1 = "x:" + x;
   String msg2 = "y:" + y;
   Serial.println(msg1); 
   Serial.println(msg2); </p><p>   posy = y.toInt();
   posx = x.toInt();
   
   if(posy<=posyMax && posy>=posyMin && posx<=posxMax && posx>=posxMin){</p><p>      myservo_10.attach(10);  
      myservo_09.attach(9);
  
      myservo_10.write(posy);  
      myservo_09.write(posx);            
  
      delay(50);
  
      myservo_10.detach();  
      myservo_09.detach();
    
    }else{
      Serial.println("Invalid option, check command '"+cmd+"'");
      printHelp();
    }
}</p><p>void establishContact() {
  while (Serial.available() <= 0) {
    Serial.print('.');   // send a .
    delay(300);
  }
}</p><p>String command;
char inByte;
void loop() {</p><p>   if (Serial.available() > 0) {
    inByte = Serial.read();
    if(inByte == '\n' ) {
      parseCommand(command);
      command="";
    }
    else{
      command+=inByte;
    }
  } 
}</p>

Step 4: OpenCV Programming for Motion Detection

OpenCV is leading library for machine vision applications.

Some basic understanding of c / c++ languages are required to understand the program.

Also you would need to use serial communication library to communicate between openCV to the Arduino board.

Refer code for more information:

<p>// MotionDetectionOpenCVVisualStudio.cpp : Defines the entry point for the console application.<br>// Author: aroramayank2002@gmail.com
// Description: Traking moving object on the web camera and sending signal to hardware board to orient the gun.</p><p>// Use VideoCapture capture(videoFilename); to VideoCapture capture(0); if web cam is connected
#include "stdafx.h" // This should be the first line
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/videoio.hpp"
#include 
#include </p><p>//C
#include 
//C++
#include 
#include </p><p>////// Serial related
#include 
#include 
#include 
#include </p><p>// OS Specific sleep
#ifdef _WIN32
#include 
#else
#include 
#endif</p><p>#include "serial/serial.h"</p><p>using namespace cv;
using namespace std;
using namespace serial;</p><p>Mat frame3; //current frame3
Mat fgMaskMOG3; //fg mask fg mask generated by MOG2 method
Ptr pMOG3; //MOG2 Background subtractor
int keyboard3; //input from keyboard3
double frameNumber = 0;
bool debug3 = false;</p><p>// Serial port parameters, the board should be connected before this program is launched.
serial::Serial my_serial("COM8", 115200, serial::Timeout::simpleTimeout(1000));</p><p>void processVideo3();</p><p>int testMovingObjects3(int argc, char* argv[]) {
	cout << "Close this window to exit..." << endl;
	namedWindow("Frame");
	pMOG3 = createBackgroundSubtractorMOG2(2000, 16.0, false); //MOG2 approach
	processVideo3();
	//destroy GUI windows
	destroyAllWindows();
	return EXIT_SUCCESS;
}</p><p>// Draws a line on the video frame
void MyLine3(Mat img, Point start, Point end) {
	int thickness = 5;  // 1
	int lineType = LINE_8;
	line(img,
		start,
		end,
		Scalar(255, 255, 255),  //Scalar(0, 0, 255),
		thickness,
		lineType);
}</p><p>Mat crossHair3(Point center, Mat image) {
	MyLine3(image, Point(center.x, center.y - 10), Point(center.x, center.y + 10));
	MyLine3(image, Point(center.x - 10, center.y), Point(center.x + 10, center.y));
	return image;
}</p><p>Point matchingMethod3(int, void*, Mat img)
{
	Mat templ(100, 100, CV_8UC1, Scalar(155)); //b,g,r white image, 1 channel
	Mat result;
	Mat img_display;
	img.copyTo(img_display);
	int result_cols = img.cols - templ.cols + 1;
	int result_rows = img.rows - templ.rows + 1;
	result.create(result_rows, result_cols, CV_32FC1);
	//bool method_accepts_mask = (CV_TM_SQDIFF == match_method || match_method == CV_TM_CCORR_NORMED);
	int match_method = CV_TM_SQDIFF;
	matchTemplate(img, templ, result, match_method);</p><p>	normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());
	double minVal; double maxVal; Point minLoc; Point maxLoc;
	Point matchLoc;
	minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());
	if (match_method == TM_SQDIFF || match_method == TM_SQDIFF_NORMED)
	{
		matchLoc = minLoc;
	}
	else
	{
		matchLoc = maxLoc;
	}
	Point p(matchLoc.x + templ.cols / 2, matchLoc.y + templ.rows / 2);
	rectangle(result, matchLoc, p, Scalar::all(0), 2, 8, 0);
	if (debug3) {
		const char* image_window = "Orginal";
		const char* result_window = "Matched";
		namedWindow(image_window);
		namedWindow(result_window);
		imshow(image_window, img_display);
		imshow(result_window, result);
	}</p><p>	return p;
}</p><p>Mat updateCoordinatesOnFrame(Mat frame, Point p) {
	//get the frame3 number and write it on the current frame3
	stringstream ss;
	rectangle(frame, cv::Point(10, 2), cv::Point(100, 20),
		cv::Scalar(255, 255, 255), -1);
	ss << p.x << ", " << p.y;
	string frameNumberString = ss.str();
	putText(frame, frameNumberString.c_str(), cv::Point(15, 15),
		FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0));
	return frame;
}</p><p>int framesForAvg3 = 10;
int currentFrame3 = 0;
Point pArray3[10];
Point stablePoint3 = Point(100, 100);
Point getStablePoint(Point p) {
	int xSum = 0;
	int ySum = 0;
	if (currentFrame3 < framesForAvg3) {
		pArray3[currentFrame3++] = p;
	}
	else {
		currentFrame3 = 0;</p><p>		for (int i = 0; i < framesForAvg3; i++) {
			xSum += pArray3[i].x;
			ySum += pArray3[i].y;
			stablePoint3 = Point(int(xSum / framesForAvg3), int(ySum / framesForAvg3));
		}</p><p>	}</p><p>	return stablePoint3;
}</p><p>//Returns translates frame to degree.
int minMaxHorizontalAngle3[2] = { 45, 90 }; //vertical in hardware code
int minMaxVerticalAngle3[2] = { 65, 95 }; // horizontal in hardware code</p><p>Point getServoTranslatedCoordinates3(Point p, Mat frame) {
	int rows = frame.rows;
	int cols = frame.cols;</p><p>	double xPercent = (double)p.x / cols;
	double yPercent = (double)p.y / rows;</p><p>	int newX = minMaxHorizontalAngle3[0] + (int)((minMaxHorizontalAngle3[1] - minMaxHorizontalAngle3[0]) * xPercent);
	int newY = minMaxVerticalAngle3[0] + (int)((minMaxVerticalAngle3[1] - minMaxVerticalAngle3[0]) * yPercent);
	Point degrees = Point(newX, newY);
	return degrees;
}</p><p>int frequency3 = 3;
int frequencyCounter3 = 0;
void writeToSerial(Point p) {
	if (frequencyCounter3++ < frequency3) {
		//Do nothing		
	}
	else {
		frequencyCounter3 = 0;</p><p>		stringstream ss;
		ss << p.y;
		ss << ":";
		ss << (minMaxHorizontalAngle3[0] + minMaxHorizontalAngle3[1] - p.x);
		//ss << "65";
		cout << ss.str() << "\n";
		size_t bytes_wrote = my_serial.write(ss.str() + "\n");
	}
}</p><p>void findMovement3(Mat fgMaskMOG3, Mat frame) {
	Point p = matchingMethod3(0, 0, fgMaskMOG3);
	frame = crossHair3(p, frame);
	Point p3 = getServoTranslatedCoordinates3(p, frame);
	writeToSerial(p3);
	imshow("Frame", frame);
}</p><p>void processVideo3() {
	VideoCapture capture(1);
	if (!capture.isOpened()) {
		//error in opening the video input
		cerr << "Unable to open camera file: " << endl;
		exit(EXIT_FAILURE);
	}
	//read input data. ESC or 'q' for quitting
	while ((char)keyboard3 != 'q' && (char)keyboard3 != 27) {
		//read the current frame
		if (!capture.read(frame3)) {
			cerr << "Unable to read next frame3." << endl;
			cerr << "Exiting..." << endl;
			exit(EXIT_FAILURE);
		}
		//update the background model
		pMOG3->apply(frame3, fgMaskMOG3);</p><p>		findMovement3(fgMaskMOG3, frame3);</p><p>		keyboard3 = waitKey(30);
		if (255 != keyboard3) {
			cout << keyboard3 << endl;
		}
	}
	//delete capture object
	capture.release();
}</p><p>// To be used when testing serial program.
void testSerial() {
	int c;
	do {
	cout << "Enter char: ";
	c = _getch();
	size_t bytes_wrote = my_serial.write("50:80\n");
	cout << bytes_wrote << endl;
	//bytes_wrote = my_serial.write(string(1, '\n'));
	//cout << bytes_wrote << endl;
	} while (c != '.');	
}</p><p>int main(int argc, char* argv[])
{	
	return testMovingObjects3(argc, argv);
}</p>