1. 程式人生 > >樹莓派python OpenCV捕獲顏色塊並通過串列埠返回座標資訊

樹莓派python OpenCV捕獲顏色塊並通過串列埠返回座標資訊

樹莓派python OpenCV捕獲顏色塊並通過串列埠返回座標資訊

樹莓派python OpenCV捕獲顏色塊並通過串列埠返回座標資訊

介紹

該程式碼起初是用在無人機尋找彩色物體定位上面,在無人機上面掛載樹莓派,藉助樹莓派的高運算能力和可拓展性來彌補飛控的不足。在攝像頭通過USB介面掛載到樹莓派上,藉助OpenCV來進行影象處理,並通過串列埠傳送彩色物體位置資訊給飛控,以實現無人機的定位。實則程式碼可以用在其他任何場景。

開發環境

在Windows下通過pycharm pro通過SSH連線樹莓派進行傳輸資料和除錯程式。這樣的好處是具有程式碼補全和提示功能,提高效率。
pycharm pro工程裡的setting找到project Interpreter選擇add如圖:

在這裡插入圖片描述
然後選擇SSH Interpreter後輸入Host 和 Username 點選下一步按照提示即可連線上樹莓派。
在連線上樹莓派後,它會從樹莓派上下載python虛擬環境,需要一定的時間,下載完成後就可以像在樹莓派上編寫程式了。pyCharm很強大,在同步過後有很多實用 的功能值得去探索。包括程式碼同步,程式碼對比等等。
遠端除錯樹莓派(PyCharm實現)

思路

首先從攝像頭獲取影象>擇攝像頭中心為ROI>縮小影象降低處理時間>轉換為hsv顏色空間>提取出指定顏色(這裡為藍色)>灰度處理>二值化>膨脹消除內部干擾>找出重心>串列埠編碼>傳送座標。

程式碼

串列埠部分

# -*- coding:utf-8 -*-

import serial
import time

# 串列埠配置
def serialConfig(port, buat):
    ser = serial.Serial(port, buat)
    ser.flushInput()
    ser.
flushOutput() if ser.isOpen() is False: ser.open() return True, ser return True, ser if __name__ == "__main__": isOpen, Ser = serialConfig("/dev/ttyAMA0", 9600) if isOpen is True: try: while True: size = Ser.inWaiting() # 獲得緩衝區字元 if size != 0: response = Ser.read(size) # 讀取內容並顯示 print response Ser.write(bytes(response)) Ser.flushOutput() Ser.flushInput() # 清空接收快取區 time.sleep(0.2) # 軟體延時 except KeyboardInterrupt: Ser.close() else: print("port false")

影象處理部分

# -*- coding:utf-8 -*-

import cv2
import tkinter
import numpy as np
import time
import SerialPort as sp

win = tkinter.Tk(':0.0')  # 指定輸出裝置,否則報錯 必須加這一句 不然找不到顯示裝置將報錯
cv2.setUseOptimized(True)  # 優化opencv

# 藍色閾值
lower_blue = np.array([50, 50, 50])
upper_blue = np.array([130, 255, 255])
# 可以這樣知道想要查詢顏色對應的hsv空間
# green = np.uint8([[[0, 255, 0]]])
# hsv_green = cv2.cvtColor(green, cv2.COLOR_BGR2HSV)

lastCX = 0
lastCY = 0
cx = 0
cy = 0

def image_process(frame):
    global cx, cy
    global lastCY, lastCX
    times = cv2.getTickCount()

    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

    mask = cv2.inRange(hsv, lower_blue, upper_blue)  # 得到藍色掩模
    blue = cv2.bitwise_and(frame, frame, mask=mask)  # 位與
    gray = cv2.cvtColor(blue, cv2.COLOR_BGR2GRAY)  # 灰度處理

    #  cv2.medianBlur(gray, 3)  # 中值濾波
    #  bin_blue = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
    temp, bin_blue = cv2.threshold(gray, 50, 255, cv2.THRESH_BINARY)  # 簡單二值化
    kernel = np.ones((10, 10), np.uint8)  # 膨脹將要用到的核心
    bin_blue = cv2.erode(bin_blue, kernel, iterations=1)  # 膨脹以消除內部干擾
    # 獲取影象輪廓
    dst_image, contours, hierarchy = cv2.findContours(bin_blue, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    frame = cv2.drawContours(frame, contours, -1, (0, 255, 0), 2)  # 原始影象上畫出輪廓

    m = cv2.moments(dst_image)  # 尋找輪廓的距

    lastCX = cx
    lastCY = cy

    try: #防止除零發生異常
        cx = int(m['m10'] / m['m00'])  # 計算重心
        cy = int(m['m01'] / m['m00'])

        cv2.circle(frame, (cx, cy), 5, (0, 0, 255), -1)  # 標記重心

        cx = cx - 120
        cy = cy - 120

    except ZeroDivisionError:

        cy = lastCY
        cx = lastCX
        cv2.circle(frame, (cx+120, cy+120), 5, (0, 0, 255), -1)  # 標記重心

    if cx > 120:
        cx = 120
    if cy >120:
        cy = 120
    if cx < -120:
        cx = -120
    if cy < -120:
        cy = -120

    timed = cv2.getTickCount()  # 計算處理一幀的時間
    time = (timed - times) / cv2.getTickFrequency()
    font = cv2.FONT_HERSHEY_SIMPLEX
    cv2.putText(frame, str(time)[: 5], (50, 50), font, 1, (255, 0, 0), 2)

    # print time
    return cx, cy, frame, dst_image


def serialSend(SerPort, data, flag = 0):

    SerPort.write(chr(int( 0xaa ))) # 幀頭
    for i in data:
        SerPort.write(chr(int(i)))

    SerPort.write(chr(int(flag)))
    SerPort.write(chr(int( 0xab ))) # 幀尾


if __name__ == "__main__":
    isOpened, ser = sp.serialConfig('/dev/ttyAMA0', 115200)  # 配置串列埠
    cap = cv2.VideoCapture(0)
    while cap.isOpened():
        ts = cv2.getTickCount()

        ret, video = cap.read()
        videoROI = video[ :480, 80:560]  # 取影象中心為ROI

        ROI = cv2.resize(videoROI, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_CUBIC)  # 縮小影象以提高運算速率
        x, y, image, dstImage = image_process(ROI)  # 影象處理

        finalX = x / 2 + 60# s縮小到<256 方便串列埠傳送
        finalY = y / 2 + 60 # 由於串列埠不能傳送負數 所以這樣處理後資料範圍被限定在0-255 串口才能正常傳送 ,主要是python運用不是很熟練
        data=[finalX,finalY]
        serialSend(ser, data)

        # print("imageX:{0} imageY:{1} finalX:{2} finalY:{3}".format(x, y, finalX, finalY))
        #
        # 放大以方便檢視
        # image = cv2.resize(image, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
        # dstImage = cv2.resize(dstImage, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
        #
        # cv2.imshow('image', image)
        # cv2.imshow('dst_image', dstImage)
        #
        # k = cv2.waitKey(1) & 0xff
        # if k == 27:
        #     break
        # if k == ord('s'):
        #     print(video.shape)

        te = cv2.getTickCount()
        tt = (te - ts) / cv2.getTickFrequency()
        print(tt)

    ser.close()
    cap.release()
    cv2.destroyAllWindows()

後續處理

飛機接收到座標資訊後就可以進行定位了。