Guillermo Perez Guillen
Published © MIT

UV-Sanitizing Autonomous Robot

Cost-effective robotic solution using OpenCV for surface sanitization in home

AdvancedFull instructions provided16 hours512

Things used in this project

Hardware components

Raspberry Pi 3 Model B+
Raspberry Pi 3 Model B+
×1
Arduino UNO
Arduino UNO
×1
ESP32S
Espressif ESP32S
×1
Arduino Pro Mini 328 - 5V/16MHz
SparkFun Arduino Pro Mini 328 - 5V/16MHz
×1
Camera Module V2
Raspberry Pi Camera Module V2
×1
Ultrasonic Sensor - HC-SR04
SparkFun Ultrasonic Sensor - HC-SR04
×1
Proximity Sensor
Proximity Sensor
×1
SparkFun Full-Bridge Motor Driver Breakout - L298N
SparkFun Full-Bridge Motor Driver Breakout - L298N
×1
Grove - Relay
Seeed Studio Grove - Relay
×1
SG90 Micro-servo motor
SG90 Micro-servo motor
×1
Rechargeable Battery, 7.2 V
Rechargeable Battery, 7.2 V
×1
Rechargeable Battery, 4.8 V
Rechargeable Battery, 4.8 V
×1
UV lamp 6W
×1
Mini DIY Tesla Coil Kit
×1

Software apps and online services

OpenCV
OpenCV
Python
Arduino IDE
Arduino IDE
Amazon Alexa App

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Mastech MS8217 Autorange Digital Multimeter
Digilent Mastech MS8217 Autorange Digital Multimeter
Smartphone Android
Soldering iron (generic)
Soldering iron (generic)
Hot glue gun (generic)
Hot glue gun (generic)

Story

Read more

Schematics

Schematic (step 4)

First version of the project

Schematic (step 9)

Second vresion of the project

uv-sanitizing-autonomous-robot

Here you will find the two versions of my project: OpenCV Cascade Classifier, Schematic Diagrams, Codes and STL files

Code

esp32_wroom_32.ino

Arduino
Code used in step 4
// AUTHOR: GUILLERMO PEREZ GUILLEN

#include <Arduino.h>
#include <NewPing.h> // SRFO4
#define ultrasonic_pin_1 4 // SRF04
#define ultrasonic_pin_2 25 // SRF05

const int UltrasonicPin = 2; // SRFO4 
const int MaxDistance = 200; // SRFO4

const unsigned int TRIG_PIN=27; //SRF05
const unsigned int ECHO_PIN=26; //SRF05


NewPing sonar(UltrasonicPin, UltrasonicPin, MaxDistance); // SRFO4

#ifdef ESP32
  #include <WiFi.h>
  #define RF_RECEIVER 13
  #define RELAY_PIN_1 12
  #define RELAY_PIN_2 14
#else
  #include <ESP8266WiFi.h>
  #define RF_RECEIVER 5
  #define RELAY_PIN_1 4
  #define RELAY_PIN_2 14
#endif
#include "fauxmoESP.h"

#include <RCSwitch.h>

#define SERIAL_BAUDRATE 115200

#define WIFI_SSID "XXXXXXXXXX"
#define WIFI_PASS "XXXXXXXXXX"

#define LAMP_1 "lamp"
#define LAMP_2 "car"

fauxmoESP fauxmo;

RCSwitch mySwitch = RCSwitch();

// Wi-Fi Connection
void wifiSetup() {
  // Set WIFI module to STA mode
  WiFi.mode(WIFI_STA);

  // Connect
  Serial.printf("[WIFI] Connecting to %s ", WIFI_SSID);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  // Wait
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(100);
  }
  Serial.println();

  // Connected!
  Serial.printf("[WIFI] STATION Mode, SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
}

void setup() {
  pinMode(ultrasonic_pin_1, OUTPUT); // SRF04
  digitalWrite(ultrasonic_pin_1, LOW); // SRF04

  pinMode(ultrasonic_pin_2, OUTPUT); // SRF05
  digitalWrite(ultrasonic_pin_2, LOW); // SRF05    
  pinMode(TRIG_PIN, OUTPUT); // SRF05
  pinMode(ECHO_PIN, INPUT); // SRF05
  
  // Init serial port and clean garbage
  Serial.begin(SERIAL_BAUDRATE);
  Serial.println();

  // Wi-Fi connection
  wifiSetup();

  // LED
  pinMode(RELAY_PIN_1, OUTPUT);
  digitalWrite(RELAY_PIN_1, LOW);

  pinMode(RELAY_PIN_2, OUTPUT);
  digitalWrite(RELAY_PIN_2, LOW);
  
  mySwitch.enableReceive(RF_RECEIVER);  // Receiver on interrupt 0 => that is pin #2

  // By default, fauxmoESP creates it's own webserver on the defined port
  // The TCP port must be 80 for gen3 devices (default is 1901)
  // This has to be done before the call to enable()
  fauxmo.createServer(true); // not needed, this is the default value
  fauxmo.setPort(80); // This is required for gen3 devices

  // You have to call enable(true) once you have a WiFi connection
  // You can enable or disable the library at any moment
  // Disabling it will prevent the devices from being discovered and switched
  fauxmo.enable(true);
  // You can use different ways to invoke alexa to modify the devices state:
  // "Alexa, turn lamp two on"

  // Add virtual devices
  fauxmo.addDevice(LAMP_1);
  fauxmo.addDevice(LAMP_2);

  fauxmo.onSetState([](unsigned char device_id, const char * device_name, bool state, unsigned char value) {
    // Callback when a command from Alexa is received. 
    // You can use device_id or device_name to choose the element to perform an action onto (relay, LED,...)
    // State is a boolean (ON/OFF) and value a number from 0 to 255 (if you say "set kitchen light to 50%" you will receive a 128 here).
    // Just remember not to delay too much here, this is a callback, exit as soon as possible.
    // If you have to do something more involved here set a flag and process it in your main loop.
        
    Serial.printf("[MAIN] Device #%d (%s) state: %s value: %d\n", device_id, device_name, state ? "ON" : "OFF", value);
    if ( (strcmp(device_name, LAMP_1) == 0) ) {
      // this just sets a variable that the main loop() does something about
      Serial.println("RELAY 1 switched by Alexa");
      //digitalWrite(RELAY_PIN_1, !digitalRead(RELAY_PIN_1));
      if (state) {
        digitalWrite(RELAY_PIN_1, HIGH);
      } else {
        digitalWrite(RELAY_PIN_1, LOW);
      }
    }
    if ( (strcmp(device_name, LAMP_2) == 0) ) {
      // this just sets a variable that the main loop() does something about
      Serial.println("RELAY 2 switched by Alexa");
      if (state) {
        digitalWrite(RELAY_PIN_2, HIGH);
      } else {
        digitalWrite(RELAY_PIN_2, LOW);
      }
    }
  });

}

void loop() {
  delay(25);
  int rf_sensor_left = sonar.ping_cm(); // SRFO4
  
  if (rf_sensor_left<30){digitalWrite(ultrasonic_pin_1, HIGH);} // SRFO4
  else {digitalWrite(ultrasonic_pin_1, LOW);} // SRFO4 

  digitalWrite(TRIG_PIN, LOW); // SRFO5
  delayMicroseconds(2); // SRFO5
  digitalWrite(TRIG_PIN, HIGH); // SRFO5
  delayMicroseconds(10); // SRFO5
  digitalWrite(TRIG_PIN, LOW); // SRFO5  

  const unsigned long duration= pulseIn(ECHO_PIN, HIGH); // SRFO5
  int rf_sensor_right = duration/29/2; // SRFO5

  if (rf_sensor_right<30){digitalWrite(ultrasonic_pin_2, HIGH);} // SRFO5
  else {digitalWrite(ultrasonic_pin_2, LOW);} // SRFO5 

  Serial.print("Distance1: ");
  Serial.println(rf_sensor_left);  
  Serial.print("Distance2: ");
  Serial.println(rf_sensor_right);  
  Serial.println("  ");
  
  // fauxmoESP uses an async TCP server but a sync UDP server
  // Therefore, we have to manually poll for UDP packets
  fauxmo.handle();

  static unsigned long last = millis();
  if (millis() - last > 5000) {
    last = millis();
    Serial.printf("[MAIN] Free heap: %d bytes\n", ESP.getFreeHeap());
  }
    
  if (mySwitch.available()) {    
    if (mySwitch.getReceivedValue()==6819768) {
      digitalWrite(RELAY_PIN_1, !digitalRead(RELAY_PIN_1));
    }
    if (mySwitch.getReceivedValue()==9463928) {
      digitalWrite(RELAY_PIN_2, !digitalRead(RELAY_PIN_2));     
    }
    delay(600);
    mySwitch.resetAvailable();
  }
}

generate_arduino_code.py

Python
Code used in step 5
import numpy as np

# We create the class 
class NeuralNetwork:

    def __init__(self, layers, activation='tanh'):
        if activation == 'sigmoid':
            self.activation = sigmoid
            self.activation_prime = sigmoid_derivada
        elif activation == 'tanh':
            self.activation = tanh
            self.activation_prime = tanh_derivada

        # Initialize the weights
        self.weights = []
        self.deltas = []
        # Assign random values to input layer and hidden layer
        for i in range(1, len(layers) - 1):
            r = 2*np.random.random((layers[i-1] + 1, layers[i] + 1)) -1
            self.weights.append(r)
        # Assigned random to output layer
        r = 2*np.random.random( (layers[i] + 1, layers[i+1])) - 1
        self.weights.append(r)

    def fit(self, X, y, learning_rate=0.2, epochs=100000):
        # I add column of ones to the X inputs. With this we add the Bias unit to the input layer
        ones = np.atleast_2d(np.ones(X.shape[0]))
        X = np.concatenate((ones.T, X), axis=1)
        
        for k in range(epochs):
            i = np.random.randint(X.shape[0])
            a = [X[i]]

            for l in range(len(self.weights)):
                    dot_value = np.dot(a[l], self.weights[l])
                    activation = self.activation(dot_value)
                    a.append(activation)
            #Calculate the difference in the output layer and the value obtained
            error = y[i] - a[-1]
            deltas = [error * self.activation_prime(a[-1])]
            
            # We start in the second layer until the last one (A layer before the output one)
            for l in range(len(a) - 2, 0, -1): 
                deltas.append(deltas[-1].dot(self.weights[l].T)*self.activation_prime(a[l]))
            self.deltas.append(deltas)

            # Reverse
            deltas.reverse()

            # Backpropagation
            # 1. Multiply the output delta with the input activations to obtain the weight gradient.             
            # 2. Updated the weight by subtracting a percentage of the gradient
            for i in range(len(self.weights)):
                layer = np.atleast_2d(a[i])
                delta = np.atleast_2d(deltas[i])
                self.weights[i] += learning_rate * layer.T.dot(delta)

            if k % 10000 == 0: print('epochs:', k)

    def predict(self, x): 
        ones = np.atleast_2d(np.ones(x.shape[0]))
        a = np.concatenate((np.ones(1).T, np.array(x)), axis=0)
        for l in range(0, len(self.weights)):
            a = self.activation(np.dot(a, self.weights[l]))
        return a

    def print_weights(self):
        print("LIST OF CONNECTION WEIGHTS")
        for i in range(len(self.weights)):
            print(self.weights[i])

    def get_weights(self):
        return self.weights
    
    def get_deltas(self):
        return self.deltas

# When creating the network, we can choose between using the sigmoid or tanh function
def sigmoid(x):
    return 1.0/(1.0 + np.exp(-x))

def sigmoid_derivada(x):
    return sigmoid(x)*(1.0-sigmoid(x))

def tanh(x):
    return np.tanh(x)

def tanh_derivada(x):
    return 1.0 - x**2

########## CAR NETWORK

nn = NeuralNetwork([6,3,4],activation ='tanh')
X = np.array([[0,0,0,0,0,0],   
              [0,0,0,0,0,1],   
              [0,0,0,0,1,0],   
              [0,0,0,0,1,1],   
              [0,0,0,1,0,0],   
              [0,0,0,1,0,1],   
              [0,0,0,1,1,0],   
              [0,0,0,1,1,1],   
              [0,0,1,0,0,0],  
              [0,0,1,0,0,1],   
              [0,0,1,0,1,1],   
              [0,0,1,1,0,0],   
              [0,0,1,1,0,1],   
              [0,0,1,1,1,1],   
              [0,1,0,0,0,0],   
              [0,1,0,0,0,1],   
              [0,1,0,0,1,0],   
              [0,1,0,1,0,0],   
              [0,1,0,1,0,1],   
              [0,1,0,1,1,0],   
              [0,1,1,0,0,0],   
              [0,1,1,0,1,0],   
              [0,1,1,1,0,0],   
              [0,1,1,1,1,0],   
              [1,0,0,0,0,0],   
              [1,0,0,0,0,1],   
              [1,0,0,0,1,0],   
              [1,0,0,0,1,1],   
              [1,0,0,1,0,0],   
              [1,0,0,1,0,1],   
              [1,0,0,1,1,0],   
              [1,0,0,1,1,1],   
              [1,0,1,0,0,0],   
              [1,0,1,0,0,1],   
              [1,0,1,0,1,1],   
              [1,0,1,1,0,0],   
              [1,0,1,1,0,1],   
              [1,0,1,1,1,1],   
              [1,1,0,0,0,0],   
              [1,1,0,0,0,1],   
              [1,1,0,0,1,0],   
              [1,1,0,1,0,0],   
              [1,1,0,1,0,1],   
              [1,1,0,1,1,0],   
              [1,1,1,0,0,0],   
              [1,1,1,0,1,0],   
              [1,1,1,1,0,0],   
              [1,1,1,1,1,0],   
             ])
# the outputs correspond to starting (or not) the motors
y = np.array([[0,0,0,0], # stop
              [0,0,0,0], # stop 
              [0,0,0,0], # stop
              [0,0,0,0], # stop
              [0,0,0,0], # stop 
              [0,0,0,0], # stop
              [0,0,0,0], # stop 
              [0,0,0,0], # stop 
              [0,0,0,0], # stop 
              [0,0,0,0], # stop 
              [0,0,0,0], # stop 
              [0,0,0,0], # stop
              [0,0,0,0], # stop 
              [0,0,0,0], # stop              
              [0,0,0,0], # stop 
              [0,0,0,0], # stop 
              [0,0,0,0], # stop
              [0,0,0,0], # stop
              [0,0,0,0], # stop 
              [0,0,0,0], # stop 
              [0,0,0,0], # stop 
              [0,0,0,0], # stop 
              [0,0,0,0], # stop 
              [0,0,0,0], # stop                
              [1,0,1,0], # forward 
              [1,0,1,0], # forward 
              [0,1,1,0], # turn-left
              [0,1,1,0], # turn-left 
              [0,1,0,1], # back
              [0,1,1,0], # turn-left 
              [0,1,1,0], # turn-left 
              [0,1,1,0], # turn-left 
              [1,0,0,1], # turn-right 
              [0,1,1,0], # turn-left
              [0,1,1,0], # turn-left
              [1,0,0,1], # turn-right
              [0,1,1,0], # turn-left              
              [1,0,0,1], # turn-right 
              [1,0,1,0], # forward 
              [1,0,1,0], # forward
              [1,0,0,1], # turn-right 
              [1,0,0,1], # turn-right 
              [0,1,0,1], # back 
              [1,0,0,1], # turn-right 
              [1,0,0,1], # turn-right
              [1,0,0,1], # turn-right
              [1,0,0,1], # turn-right              
              [1,0,0,1], # turn-right            
             ])
nn.fit(X, y, learning_rate=0.03,epochs=550001)
 
def valNN(x):
    return (int)(abs(round(x)))
 
index=0
for e in X:
    prediccion = nn.predict(e)
    print("X:",e,"expected:",y[index],"obtained:", valNN(prediccion[0]),valNN(prediccion[1]),valNN(prediccion[2]),valNN(prediccion[3]))
    index=index+1


########## WE GENERATE THE ARDUINO CODE
def to_str(name, W):
    s = str(W.tolist()).replace('[', '{').replace(']', '}')
    return 'float '+name+'['+str(W.shape[0])+']['+str(W.shape[1])+'] = ' + s + ';'

# We get the weights trained to be able to use them in the arduino code
pesos = nn.get_weights();

print('// Replace these lines in your arduino code:')
print('// float HiddenWeights ...')
print('// float OutputWeights ...')
print('// With trained weights.')
print('\n')
print(to_str('HiddenWeights', pesos[0]))
print(to_str('OutputWeights', pesos[1]))

autonomous_robot.ino

Arduino
Code used in step 5
// AUTHOR: GUILLERMO PEREZ GUILLEN

#define ENA 3
#define ENB 5
#define IN1 8
#define IN2 9
#define IN3 10
#define IN4 11

/******************************************************************
   NETWORK CONFIGURATION
******************************************************************/
const int ESP32_pin_1= 6; // ESP32 input pin 1 - starting
const int ESP32_pin_2 = 7; // ESP32 input pin 2 - SRF04
const int ESP32_pin_3 = 12; // ESP32 input pin 3 - SRF05
const int InputNodes = 7; // includes BIAS neuron
const int HiddenNodes = 4; //includes BIAS neuron
const int OutputNodes = 4;
int i, j;
double Accum;
double Hidden[HiddenNodes];
double Output[OutputNodes];
float HiddenWeights[7][4] = {{-4.618963658666277, 4.3001137618883325, 7.338055706191847, 2.7355309007172375}, {2.599633307446623, -7.649705724376986, -14.69443684121685, -3.65366992422193}, {-0.7777191662679982, 1.9860139431844053, 5.914809078303235, 0.03170277380327093}, {-2.309653145069323, 6.8379997039119775, 8.892299055796917, 0.6046238076393062}, {1.3276547120093833, 5.085574619860947, 2.384944264717347, 0.05753178068519734}, {-2.7696264005599858, 6.797226565794283, 3.5374247269984713, 0.5475825968169957}, {0.8118152131237218, -1.9324229493484606, -5.264294920291424, -0.036800281071245555}};
float OutputWeights[4][4] = {{-1.6342640637903814, 0.006920937706630823, -5.179205882976105, -0.40268984302793936}, {-1.0162353344988182, 1.3405072244655225, -4.241619375014734, 0.6682851389512594}, {1.3692632942485174, -1.3884291338648505, -0.9245235380688354, 2.246128813012694}, {-1.9802299382328057, 0.06512857708456388, -0.030302930346753857, -3.314024844617794}};

int error=0;
int dif,difAnt=0;
const float Kp=0.1;
const float Kd=0.1;

void setup() {	
	Serial.begin(9600);
	pinMode(A0, INPUT); //left sensor
	pinMode(A1, INPUT); //center sensor
	pinMode(A3, INPUT); //right sensor 
	pinMode(IN1, OUTPUT);
	pinMode(IN2, OUTPUT);
	pinMode(IN3, OUTPUT);
	pinMode(IN4, OUTPUT);
	pinMode(ENA, OUTPUT);
	pinMode(ENB, OUTPUT);
  pinMode(ESP32_pin_1, INPUT); 
  pinMode(ESP32_pin_2, INPUT);
  pinMode(ESP32_pin_3, INPUT);  
} 

void loop()
{

double TestInput[] = {0, 0, 0};
double input1=0,input2=0,input3=0,input4=0,input5=0,input6=0;
    
float volts0 =  analogRead(A0)*0.0048828125;  // value from sensor * (5/1024) 
float volts1 =  analogRead(A1)*0.0048828125;  // value from sensor * (5/1024) 
float volts2 =  analogRead(A3)*0.0048828125;  // value from sensor * (5/1024)

dif = analogRead(A3) - analogRead(A0);	// PID CONTROLLER
error = floor(Kp*(dif)+Kd*(difAnt-dif));	// PID CONTROLLER
difAnt=dif;	// PID CONTROLLER
int d0 = constrain(150 - error, 0, 150);//left speed - PID CONTROLLER
int d1 = constrain(150 + error, 0, 150);//right speed - PID CONTROLLER

float ir_sensor_left =  6*pow(volts0, -1); // worked out from datasheet graph //GP2Y0A51SK0F - 2 a 15 cm
float ir_sensor_center = 12.4*pow(volts1, -1); // worked out from datasheet graph //GP2Y0A41SK0F - 4 a 30 cm
float ir_sensor_right = 5.2*pow(volts2, -1); // worked out from datasheet graph //GP2Y0A51SK0F - 2 a 15 cm

if(digitalRead(ESP32_pin_1) == HIGH){input1=1;} // START TO MOVE
else {input1=0;} // STOP

if (ir_sensor_left<15){input2=1;} // IR SENSOR LEFT
else {input2=0;}

if(digitalRead(ESP32_pin_2) == HIGH){input3=1;} // RF SENSOR LEFT
else {input3=0;}
	
if (ir_sensor_center<30){input4=1;}  // IR SENSOR CENTER
else {input4=0;}

if(digitalRead(ESP32_pin_3) == HIGH){input5=1;} // RF SENSOR RIGHT
else {input5=0;}

if (ir_sensor_right<15){input6=1;} // IR SENSOR RIGHT
else {input6=0;}

/******************************************************************
    WE CALL THE FEEDFORWARD NETWORK WITH THE INPUTS
******************************************************************/

  Serial.print("Input1:");
  Serial.println(input1);
  Serial.print("Input2:");
  Serial.println(input2);
  Serial.print("Input3:");
  Serial.println(input3);  
  Serial.print("Input4:");
  Serial.println(input4);
  Serial.print("Input5:");
  Serial.println(input5);
  Serial.print("Input6:");
  Serial.println(input6); 
  Serial.println("   ");   
  
//THESE ARE THE THREE INPUTS WITH VALUES OF 0 TO 1 ********************
  TestInput[0] = 1.0;//BIAS UNIT
  TestInput[1] = input1;
  TestInput[2] = input2;
  TestInput[3] = input3;  
  TestInput[4] = input4;
  TestInput[5] = input5;
  TestInput[6] = input6;  

// THIS FUNCTION IS TO GET THE OUTPUTS **********************************
  InputToOutput(TestInput[0], TestInput[1], TestInput[2], TestInput[3], TestInput[4], TestInput[5], TestInput[6]); //INPUT to ANN to obtain OUTPUT

  int out1 = round(abs(Output[0]));
  int out2 = round(abs(Output[1]));
  int out3 = round(abs(Output[2]));
  int out4 = round(abs(Output[3]));
  Serial.print("Output1:");
  Serial.println(out1);
  Serial.print("Output2:");
  Serial.println(out2);
  Serial.print("Output3:");
  Serial.println(out3);
  Serial.print("Output4:");
  Serial.println(out4);  
  Serial.println("   ");

/******************************************************************
    DRIVE MOTORS WITH THE NETWORK OUTPUT
******************************************************************/
  analogWrite(ENA, d0);
  analogWrite(ENB, d1);
  digitalWrite(IN1, out1 * HIGH); 
  digitalWrite(IN2, out2 * HIGH); 
  digitalWrite(IN3, out3 * HIGH);
  digitalWrite(IN4, out4 * HIGH);
  delay(20);
}

void InputToOutput(double In1, double In2, double In3, double In4, double In5, double In6, double In7)
{
  double TestInput[] = {0, 0, 0, 0, 0, 0, 0};
  TestInput[0] = In1;
  TestInput[1] = In2;
  TestInput[2] = In3;
  TestInput[3] = In4;
  TestInput[4] = In5;
  TestInput[5] = In6;
  TestInput[6] = In7;    

/******************************************************************
    CALCULATE ACTIVITIES IN HIDDEN LAYERS
******************************************************************/

  for ( i = 0 ; i < HiddenNodes ; i++ ) {	// We go through the four columns of the hidden weights
    Accum = 0;
    for ( j = 0 ; j < InputNodes ; j++ ) {	// Three values of the entry line and each column of hidden weights
      Accum += TestInput[j] * HiddenWeights[j][i] ;
    }
    Hidden[i] = tanh(Accum) ; // We obtain a matrix of a line with four values
  }

/******************************************************************
    CALCULATE ACTIVATION AND ERROR IN THE OUTPUT LAYER
******************************************************************/

  for ( i = 0 ; i < OutputNodes ; i++ ) {
    Accum = 0;
    for ( j = 0 ; j < HiddenNodes ; j++ ) {
        Accum += Hidden[j] * OutputWeights[j][i] ;
    }
    Output[i] = tanh(Accum) ;//tanh
  }
}

uv_autonomous_robot.py

Python
Code used in step 9
# import the necessary packages
from picamera.array import PiRGBArray
from picamera import PiCamera
import time
import cv2
import serial
import struct
a=0
b=0
x1=0
y1=0
ser = serial.Serial('/dev/ttyUSB0',9600)

# initialize the camera and grab a reference to the raw camera capture
camera = PiCamera()
camera.resolution = (640, 480)
camera.framerate = 32
rawCapture = PiRGBArray(camera, size=(640, 480))

#Load a cascade file for detecting faces
backless_stool_cascade = cv2.CascadeClassifier('backless_stool.xml')

# allow the camera to warmup
time.sleep(0.1)
count = 0

# capture frames from the camera
for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True):
        image = frame.array
        gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        backless_stool = backless_stool_cascade.detectMultiScale(gray, 1.3, 5)
        for (x,y,w,h) in backless_stool:
                a=int((2*x+w)/2)
                b=int((2*y+h)/2)
                x1=int(a/3.66)
                y1=int(b/2.55)
                ser.write(struct.pack('>BB', x1,y1))
                cv2.rectangle(image, (x,y), (x+w,y+h), (255,0,0), 2)
                count += 1
        # show the frame
        cv2.imshow("Frame", image)
        key = cv2.waitKey(1) & 0xFF

        # clear the stream in preparation for the next frame
        rawCapture.truncate(0)

        # if the `q` key was pressed, break from the loop
        if key == ord("q"):
                break

arduino_pro_mini.ino

Arduino
Code used in step 9
#include <Servo.h>
int data_x = 0;
int data_y = 0;
int data[1];
Servo myservo_x;
Servo myservo_y;// create servo object to control a servo

void setup() {
  Serial.begin(9600);
  myservo_x.attach(9);  // attaches the servo on pin 9 to the servo object
  myservo_y.attach(10);
  myservo_x.write(90);
  myservo_y.write(90);
}

void loop() {
  while (Serial.available() >= 2) {
    for (int i = 0; i < 2; i++) {
      data[i] = Serial.read();
    }

    myservo_x.write(data[0]);
    myservo_y.write(data[1]);

    Serial.println(data[0]);
    Serial.println(data[1]);
  }  
}

Credits

Guillermo Perez Guillen

Guillermo Perez Guillen

57 projects • 63 followers
Electronics and Communications Engineer (ECE) & Renewable Energy: 14 prizes in Hackster / Hackaday Prize Finalist 2021-22-23

Comments