Introduction: Realtime Gun Aiming
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:
- Plastic beads pixel art
- Aruino UNO
- Arduino motor driver board
- Two servo motors
- A web camera
- Arduino programming knowledge
- Servo programming knowledge
- openCV machie vision library knowledge
Steps:
- Build the gun.
- Build the mount for gun.
- Arduino programming for gun motion control.
- 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>

