树莓派摄像头-追踪

今天实现了对物体检测并控制舵机运动的应用。
舵机使用了pca9685控制,因此备份一下pac9685舵机控制板的驱动:

PCA9685.py

import time
import math

class PWM:
    _mode_adr      = 0x00
    _base_adr_low  = 0x08
    _base_adr_high = 0x09
    _prescale_adr  = 0xFE

    def __init__(self, bus, address = 0x40):
        '''
        Creates an instance of the PWM chip at given i2c address.
        @param bus: the SMBus instance to access the i2c port(0 or 1).
        @param address: the address of the i2c chip (default: 0x40).
        '''
        self.bus = bus
        self.address = address
        self._writeByte(self._mode_adr, 0x00)

    def setFreq(self, freq):
        '''
        Sets the PWM frequency. The value is stored in the device.
        @param freq: the frequency in Hz (approx.)
        '''
        prescaleValue = 25000000.0  # 25MHz
        prescaleValue /= 4096.0     # 12-bit
        prescaleValue /= float(freq)
        prescaleValue -= 1.0  
        prescale = math.floor(prescaleValue + 0.5)
        oldmode = self._readByte(self._mode_adr)
        if oldmode == None:
            return
        newmode = (oldmode & 0x7F) | 0x10
        self._writeByte(self._mode_adr, newmode)
        self._writeByte(self._prescale_adr, int(math.floor(prescale)))
        self._writeByte(self._mode_adr, oldmode)
        time.sleep(0.005)
        self._writeByte(self._mode_adr, oldmode | 0x80)

    def setDuty(self, channel, duty):
        '''
        Sets a single PWM channel. The value is stored in the device.
        @param channel: one of the channels 0..15
        @param duty: the duty cycle 0..100
        '''
        data = int(duty * 4096 /100)  # 0..4096 (included)
        self._writeByte(self._base_adr_low + 4 * channel, data & 0xFF)
        self._writeByte(self._base_adr_high + 4 * channel, data >> 8)

    def _writeByte(self, reg, value):
        try:
            self.bus.write_byte_data(self.address, reg, value)
        except:
            print("Error while writing to I2C device")

    def _readByte(self, reg):
        try:
            result = self.bus.read_byte_data(self.address, reg)
            return result
        except:
            print("Error while Reading from I2C device")
            return None

调用方法简单:

import time
from smbus2 import SMBus
from pca9685 import PWM

freq = 50
addr = 0x40
channels = [0, 1, 2] 
a = 12.5
b = 2

bus = SMBus(1)
pwm = PWM(bus, addr)
pwm.setFreq(freq)

def setPos(channel, pos):
    duty = a / 180 * pos + b 
    pwm.setDuty(channel, duty)
    time.sleep(0.1)

while True:
    try:
        for pos in range(60, 120, 2):
            setPos(channels[0], pos)
            time.sleep(0.1)

        for pos in range(60, 120, 3):
            setPos(channels[1], pos)
            time.sleep(0.5)

        for pos in range(0, 90, 3):
            setPos(channels[2], pos)
            time.sleep(0.01)

    except KeyboardInterrupt:
        for i in channels:
            setPos(i, 0)
        break

OpenCV 通过颜色检测并控制:

import cv2
import time
import numpy as np
from pca9685 import PWM
from smbus2 import SMBus

freq = 50
addr = 0x40
ch1  = 0
ch2  = 1

bus = SMBus(1)
pwm = PWM(bus, addr)
pwm.setFreq(freq)

def setPos(channel, position):
    pwm.setDuty(channel, position)
    time.sleep(0.01)

def posMap(x, in_min, in_max, out_min, out_max):
    return int((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)

cap = cv2.VideoCapture(0)
cap.set(3, 320)
cap.set(4, 160)
ret, frame = cap.read()

cx = int(frame.shape[1] / 2)
center = int(frame.shape[1] / 2)
pos = 90

while True:
    ret, frame = cap.read()
    frame = cv2.flip(frame, 1)
    hsv_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    ly = np.array([51, 0, 0])
    hy = np.array([180, 255, 255])

    mask = cv2.inRange(hsv_frame, ly, hy)
    contours, hir = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    contours = sorted(contours, key=lambda x: cv2.contourArea(x), reverse=True)
    # cv2.drawContours(frame, contours[1], -1, (0, 0, 255), 2)
    for cnt in contours[1:]:
        (x, y, w, h) = cv2.boundingRect(cnt)
        cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 255,0), 2)
        cx = int((x + x + w)/ 2)
        # cv2.drawContours(frame, cnt, -1, (0, 0, 255), 2)
        cv2.line(frame, (cx, 0), (cx, frame.shape[0]), (0, 0, 255), 1)
        if cx < center:
            pos -= 1.5
        elif cx > center:
            pos += 1.5  
        # pos = posMap(pos, 1, 480, 60, 120)
        setPos(ch1, pos)

        break
    # mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
    # hstack = np.hstack([mask, frame])
    cv2.imshow('frame', frame)
    if cv2.waitKey(1) & 0xFF == 27:
        break

cap.release()
cv2.destroyAllWindows()
setPos(ch1, 0)

总结

就是控制dutyCycle,频率肯定要50hz,一般的多级可能需要a, b函数作为微调。根据自己的舵机微调a和b的值。

可以尝试 a 设置8.5, b设置2