산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

아두이노 나노 & OpenCV로 만드는 자율 주행 안면 추적 로봇 카

부품 및 소모품

부품수량
Creator Ci20 보드1
SparkFun 듀얼 H-브리지 모터 드라이버 L2981
리튬이온 배터리 1000mAh1
서보 모터 (Tower Pro MG996R)2
카메라 (범용, 구형 노트북 카메라 재활용)1
Dexter Industries GoPiGo 로봇 베이스 키트1
Arduino Nano R31
초음파 거리 센서 HC-SR041

앱 및 온라인 서비스

프로젝트 개요

내 얼굴을 추적하는 자동차입니다. 내가 멀어지면 따라옵니다!

Ci20(임베디드 리눅스 보드) 필수 설치 사항

데비안 8 배포판 사용. 설치 명령어:

시스템 업데이트

sudo apt-get update
sudo apt-get upgrade
sudo apt-get dist-upgrade

OpenCV 설치 (버전 2.4.9.1, 구버전이지만 잘 작동)

sudo apt-get install libopencv-dev python-opencv

소스에서 컴파일하려면 이 튜토리얼 참조.

PySerial 설치

sudo apt-get install python python-serial

시리얼 포트 권한 변경 (USB 또는 내장)

sudo chown ci20:ci20 /dev/ttyUSB0  # 아두이노 USB 시리얼
sudo chown ci20:ci20 /dev/ttyS1    # Ci20 내장 시리얼

파이썬 스크립트 실행 (비디오 소스 인덱스 0부터 시작)

sudo python facetrackingcar.py 0

성능 향상 팁

캡처 이미지 미표시로 약간의 성능 향상. 보려면 코드에서 다음 라인 주석 해제:

#cv.ShowImage("result", img)
#cv.NamedWindow("result", 1)
#cv.DestroyWindow("result")

haarcascade_frontalface_alt.xml 파일을 스크립트와 같은 디렉토리에 둬야 합니다.

코드

faceser.py (Python - OpenCV 안면 추적 및 시리얼 전송)

#!/usr/bin/python
""" haar-like 특징으로 안면/객체 검출 데모.
카메라 이미지/비디오 스트림에서 얼굴 찾아 빨간 박스 표시,
두 서보로 웹캠 중앙에 얼굴 오도록 제어.
OpenCV 샘플 facedetect.py 기반 """
import sys
from optparse import OptionParser
import serial
import cv2.cv as cv
import time

# haar 검출 파라미터
min_size = (20, 20)
image_scale = 2
haar_scale = 1.2
min_neighbors = 2
haar_flags = cv.CV_HAAR_DO_CANNY_PRUNING

max_pwm = 180
min_pwm = 1
midScreenWindow = 20      # 화면 중심 허용 오차
panStepSize = 1           # 팬 스텝 (도)
tiltStepSize = 1          # 틸트 스텝 (도)
servoPanPosition = 90     # 초기 팬 위치 (x)
servoTiltPosition = 45    # 초기 틸트 위치 (y)
panGpioPin = 2            # 아두이노 팬 서보 ID (핀 번호 아님)
tiltGpioPin = 1           # 아두이노 틸트 서보 ID
var = 0

def detect_and_draw(img, cascade):
    gray = cv.CreateImage((img.width, img.height), 8, 1)
    small_img = cv.CreateImage((cv.Round(img.width / image_scale),
                                 cv.Round(img.height / image_scale)), 8, 1)
    cv.CvtColor(img, gray, cv.CV_BGR2GRAY)
    cv.Resize(gray, small_img, cv.CV_INTER_LINEAR)
    cv.EqualizeHist(small_img, small_img)
    
    midFace = None
    if cascade:
        t = cv.GetTickCount()
        faces = cv.HaarDetectObjects(small_img, cascade, cv.CreateMemStorage(0),
                                     haar_scale, min_neighbors, haar_flags, min_size)
        t = cv.GetTickCount() - t
        
        if faces:
            for ((x, y, w, h), n) in faces:
                pt1 = (int(x * image_scale), int(y * image_scale))
                pt2 = (int((x + w) * image_scale), int((y + h) * image_scale))
                cv.Rectangle(img, pt1, pt2, cv.RGB(255, 0, 0), 3, 8, 0)
                
                # 얼굴 중심 좌표 계산
                x1, x2 = pt1[0], pt2[0]
                y1, y2 = pt1[1], pt2[1]
                midFaceX = x1 + ((x2 - x1) / 2)
                midFaceY = y1 + ((y2 - y1) / 2)
                midFace = (midFaceX, midFaceY)
    return midFace

def move(servo, angle):
    """지정된 서보를 각도로 이동. servo: 1-4, angle: 0-180"""
    if min_pwm <= angle <= max_pwm:
        ser.write(chr(255))
        ser.write(chr(servo))
        ser.write(chr(angle))
    else:
        print "Servo angle must be an integer between 0 and 180.\n"

if __name__ == '__main__':
    ser = serial.Serial(port='/dev/ttyUSB0', baudrate=115200, timeout=1)
    # ser = serial.Serial(port='/dev/ttyS0', baudrate=115200, timeout=1)  # 내장 시리얼
    # ser = serial.Serial(port='COM14', baudrate=115200, timeout=1)       # 윈도우
    
    parser = OptionParser(usage="usage: %prog [options] [camera_index]")
    parser.add_option("-c", "--cascade", action="store", dest="cascade",
                      type="str", help="Haar cascade file, default %default",
                      default="./haarcascade_frontalface_alt.xml")
    (options, args) = parser.parse_args()
    
    cascade = cv.Load(options.cascade)
    if len(args) != 1:
        parser.print_help()
        sys.exit(1)
    
    input_name = args[0]
    if input_name.isdigit():
        capture = cv.CreateCameraCapture(int(input_name))
        cv.SetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH, 320)
        cv.SetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT, 240)
    else:
        print "We need a camera input! Specify camera index e.g. 0"
        sys.exit(0)
    
    # cv.NamedWindow("result", 1)
    if capture:
        frame_copy = None
        move(panGpioPin, servoPanPosition)
        move(tiltGpioPin, servoTiltPosition)
        
        while True:
            start = time.time()
            frame = cv.QueryFrame(capture)
            if not frame:
                cv.WaitKey(0)
                break
            
            if not frame_copy:
                frame_copy = cv.CreateImage((frame.width, frame.height),
                                            cv.IPL_DEPTH_8U, frame.nChannels)
            
            if frame.origin == cv.IPL_ORIGIN_TL:
                cv.Copy(frame, frame_copy)
            else:
                cv.Flip(frame, frame_copy, 0)
            
            midScreenX = frame.width / 2
            midScreenY = frame.height / 2
            midFace = detect_and_draw(frame_copy, cascade)
            
            if midFace is not None:
                midFaceX, midFaceY = midFace[0], midFace[1]
                
                # 팬 제어 (좌우)
                if midFaceX < (midScreenX - midScreenWindow):
                    servoPanPosition += panStepSize
                    print str(midFaceX) + " > " + str(midScreenX) + " : Pan Right : " + str(servoPanPosition)
                elif midFaceX > (midScreenX + midScreenWindow):
                    servoPanPosition -= panStepSize
                    print str(midFaceX) + " < " + str(midScreenX) + " : Pan Left : " + str(servoPanPosition)
                else:
                    print str(midFaceX) + " ~ " + str(midScreenX) + " : " + str(servoPanPosition)
                
                servoPanPosition = min(max(servoPanPosition, min_pwm), max_pwm)
                move(panGpioPin, servoPanPosition)
                
                # 틸트 제어 (상하)
                if midFaceY < (midScreenY - midScreenWindow):
                    if servoTiltPosition <= 90:
                        servoTiltPosition -= tiltStepSize
                        print str(midFaceY) + " > " + str(midScreenY) + " : Tilt Down : " + str(servoTiltPosition)
                elif midFaceY > (midScreenY + midScreenWindow):
                    if servoTiltPosition >= 1:
                        servoTiltPosition += tiltStepSize
                        print str(midFaceY) + " < " + str(midScreenY) + " : Tilt Up : " + str(servoTiltPosition)
                else:
                    print str(midFaceY) + " ~ " + str(midScreenY) + " : " + str(servoTiltPosition)
                
                servoTiltPosition = min(max(servoTiltPosition, min_pwm), max_pwm)
                move(tiltGpioPin, servoTiltPosition)
            else:
                end = time.time() + 0.1
                var += 0.1
                time_elapsed = int(end - start + var)
                print 'time elapsed:\t{}'.format(time_elapsed)
                print 'var:\t{}'.format(var)
                
                if time_elapsed == 20:
                    move(3, servoTiltPosition)
                    servoPanPosition = 90
                    servoTiltPosition = 45
                    var = 0
            
            if cv.WaitKey(1) >= 0:
                break
        # cv.DestroyWindow("result")

Car.ino (아두이노 나노 - 모터/서보 제어)


#include 

#define MA_1 2
#define MA_2 3
#define MB_1 4
#define MB_2 5
#define MC_1 6
#define MC_2 7
#define MD_1 8
#define MD_2 9
#define SERVOX_PIN 11
#define SERVOY_PIN 10
#define trigPin 13
#define echoPin 12

Servo servoy;
Servo servox;
int x = 90, prevX;
int y = 45, prevY;
int userInput[3];
int startbyte, servo, pos, i;
int State = LOW;
unsigned long previousMillis = 0, val = 100;
const long interval = 100;
bool scana = false;
unsigned long currentMillis;

void setup() {
  inicializate();
  currentMillis = millis();
}

void loop() {
  if (Serial.available() > 2) {
    startbyte = Serial.read();
    if (startbyte == 255) {
      for (i = 0; i < 2; i++) {
        userInput[i] = Serial.read();
      }
      servo = userInput[0];
      pos = userInput[1];
      
      if (pos == 255) servo = 255; // 패킷 에러 복구
      
      switch (servo) {
        case 1: servoy.write(pos); break;           // 틸트 서보
        case 2: servox.write(pos); range(pos); break; // 팬 서보 + 거리 기반 주행
        case 3: scan(45); scan(65); break;          // 스캔 모드
        default: break;
      }
    }
  }
}

void scan(int val) {
  while (Serial.available() == 0) {
    servoy.write(val);
    currentMillis = millis();
    if (currentMillis - previousMillis >= interval) {
      previousMillis = currentMillis;
      servox.write(pos);
      if (State == LOW) {
        pos += 1;
        if (pos == 130) State = HIGH;
      } else {
        pos -= 1;
        if (pos == 40) { State = LOW; break; }
      }
    }
  }
}

long distancia() {
  long duration, distance;
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  duration = pulseIn(echoPin, HIGH);
  distance = (duration / 2) / 29.1;
  return distance;
}

void range(int pos) {
  if (pos >= 80 && pos <= 100) moves();           // 정면 → 전진/후진
  else if (pos >= 100 && pos <= 180) { left(); delay(10); Stop(); } // 우측 → 좌회전
  else if (pos >= 1 && pos <= 80) { right(); delay(10); Stop(); }  // 좌측 → 우회전
}

void moves() {
  long distance = distancia();
  if (val <= 80) { reverse(); delay(50); Stop(); }
  else if (val >= 140) { forward(); delay(50); Stop(); }
  
  static long previusdistance = 0;
  if (distance >= previusdistance) val = distance - previusdistance;
  else val = previusdistance - distance;
  previusdistance = distance;
}

void inicializate() {
  Serial.begin(115200);
  pinMode(MA_1, OUTPUT); pinMode(MA_2, OUTPUT);
  pinMode(MB_1, OUTPUT); pinMode(MB_2, OUTPUT);
  pinMode(MC_1, OUTPUT); pinMode(MC_2, OUTPUT);
  pinMode(MD_1, OUTPUT); pinMode(MD_2, OUTPUT);
  pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT);
  servox.attach(SERVOX_PIN);
  servoy.attach(SERVOY_PIN);
}

void forward() {
  digitalWrite(MA_1, HIGH); digitalWrite(MA_2, LOW);
  digitalWrite(MB_1, HIGH); digitalWrite(MB_2, LOW);
  digitalWrite(MC_1, HIGH); digitalWrite(MC_2, LOW);
  digitalWrite(MD_1, HIGH); digitalWrite(MD_2, LOW);
}

void reverse() {
  digitalWrite(MA_1, LOW); digitalWrite(MA_2, HIGH);
  digitalWrite(MB_1, LOW); digitalWrite(MB_2, HIGH);
  digitalWrite(MC_1, LOW); digitalWrite(MC_2, HIGH);
  digitalWrite(MD_1, LOW); digitalWrite(MD_2, HIGH);
}

void right() {
  digitalWrite(MA_1, LOW); digitalWrite(MA_2, HIGH);
  digitalWrite(MB_1, LOW); digitalWrite(MB_2, HIGH);
  digitalWrite(MC_1, HIGH); digitalWrite(MC_2, LOW);
  digitalWrite(MD_1, HIGH); digitalWrite(MD_2, LOW);
}

void left() {
  digitalWrite(MA_1, HIGH); digitalWrite(MA_2, LOW);
  digitalWrite(MB_1, HIGH); digitalWrite(MB_2, LOW);
  digitalWrite(MC_1, LOW); digitalWrite(MC_2, HIGH);
  digitalWrite(MD_1, LOW); digitalWrite(MD_2, HIGH);
}

void Stop() {
  digitalWrite(MA_1, LOW); digitalWrite(MA_2, LOW);
  digitalWrite(MB_1, LOW); digitalWrite(MB_2, LOW);
  digitalWrite(MC_1, LOW); digitalWrite(MC_2, LOW);
  digitalWrite(MD_1, LOW); digitalWrite(MD_2, LOW);
}

void StopH() {
  digitalWrite(MA_1, HIGH); digitalWrite(MA_2, HIGH);
  digitalWrite(MB_1, HIGH); digitalWrite(MB_2, HIGH);
  digitalWrite(MC_1, HIGH); digitalWrite(MC_2, HIGH);
  digitalWrite(MD_1, HIGH); digitalWrite(MD_2, HIGH);
}

scanlinux.py (리눅스 시리얼 포트 스캔 유틸리티)


#!/usr/bin/env python
""" 리눅스 시리얼 포트 스캔. USB/시리얼 어댑터 포함.
pySerial 일부 (https://pyserial.sf.net) """
import serial
import glob

def scan():
    return (glob.glob('/dev/ttyS*') + glob.glob('/dev/ttyUSB*') +
            glob.glob('/dev/ttyACM*') + glob.glob('/dev/serial/by-id/*'))

if __name__ == '__main__':
    print "Found ports:"
    for name in scan():
        print name

커스텀 파트 및 인클로저

파이썬 OpenCV 안면 추적 스크립트와 아두이노 코드 압축 파일: FaceTrakingCar.rar

회로도

아두이노 나노 & OpenCV로 만드는 자율 주행 안면 추적 로봇 카

아두이노로 모터와 서보 제어. 핀아웃은 하드웨어에 따라 다를 수 있음.

제조공정

  1. 아두이노 디지털 주사위: LCD 기반 랜덤 넘버 제너레이터 만들기
  2. 아두이노 나노로 37개 LED 룰렛 게임 만들기
  3. ATtiny85 미니 아케이드: 스네이크 게임 - 휴대용 OLED 게임기 만들기
  4. 휴대용 거리 감지기: HC-SR04, 18650 배터리, 3D 프린트 케이스의 아두이노 나노 DIY 키트
  5. MobBob: 아두이노 로봇 직접 만들기, 안드로이드 스마트폰으로 매끄럽게 제어
  6. 피봇(PiBot): 아두이노 기반 88건반 피아노 연주 로봇
  7. 구리 도금 프로젝트: 아두이노 우노로 만드는 스마트 전기분해 시스템
  8. 아두이노 나노와 네오픽셀 매트릭스로 만드는 네온 퐁 게임
  9. 전방향 옴니 휠 로봇: 안전한 사람 추적 및 인사하기
  10. 아두이노 시프트 레지스터로 화려한 라이트 시퀀스 만들기