302 lines
13 KiB
Python
302 lines
13 KiB
Python
|
# -*- coding:utf-8 -*-
|
|||
|
# @Author len
|
|||
|
# @Create 2023/11/23 11:28
|
|||
|
|
|||
|
import math
|
|||
|
import sensor, image, time, lcd
|
|||
|
import binascii
|
|||
|
from Maix import GPIO
|
|||
|
from machine import Timer, PWM, UART, Timer
|
|||
|
from fpioa_manager import fm
|
|||
|
import KPU as kpu
|
|||
|
|
|||
|
|
|||
|
class Mainlen():
|
|||
|
def __init__(self):
|
|||
|
'''初始化摄像头和 LCD 显示屏'''
|
|||
|
lcd.init() # lcd初始化
|
|||
|
|
|||
|
self.canera_init() # 摄像头初始化
|
|||
|
# sensor.set_auto_gain(0, gain_db=17) # 设置摄像头的自动增益功能
|
|||
|
|
|||
|
# 映射串口引脚
|
|||
|
fm.register(6, fm.fpioa.UART1_RX, force=True)
|
|||
|
fm.register(7, fm.fpioa.UART1_TX, force=True)
|
|||
|
|
|||
|
# 初始化串口
|
|||
|
self.uart = UART(UART.UART1, 115200, read_buf_len=4096)
|
|||
|
|
|||
|
# 循迹
|
|||
|
# --------------感光芯片配置 START -------------------
|
|||
|
self.IMG_WIDTH = 240
|
|||
|
self.IMG_HEIGHT = 320
|
|||
|
# 直线灰度图颜色阈值
|
|||
|
self.LINE_COLOR_THRESHOLD = [(0, 60)] # 找黑色
|
|||
|
self.LINE_COLOR_BAISE = [(60, 255)] # 找白色
|
|||
|
self.ROIS = { # 找黑色
|
|||
|
# 'left': (0, 0, 320, 50), # 纵向取样-左侧
|
|||
|
# 'right': (0, 190, 320, 50), # 纵向取样-右侧
|
|||
|
'left': (0, 0, 180, 50), # 纵向取样-左侧
|
|||
|
'right': (0, 190, 180, 50), # 纵向取样-右侧
|
|||
|
'up': (240, 0, 80, 240), # 横向取样-上方
|
|||
|
'middle_up': (160, 0, 80, 240), # 横向取样-中上
|
|||
|
'middle_down': (80, 0, 80, 240), # 横向取样-中下
|
|||
|
'down': (0, 0, 80, 240), # 横向取样-下方
|
|||
|
}
|
|||
|
|
|||
|
self.is_need_send_data = False # 是否需要发送数据的信号标志
|
|||
|
|
|||
|
# 主函数 run
|
|||
|
def startMain(self):
|
|||
|
Flag_track = False # 循迹标识
|
|||
|
|
|||
|
while True:
|
|||
|
data = self.uart.read(8)
|
|||
|
|
|||
|
if data is not None:
|
|||
|
if (data[1] == 0x02) and (data[7] == 0xBB) and self.verify_checksum(data):
|
|||
|
# 巡线与控制舵机
|
|||
|
if data[2] == 0x91:
|
|||
|
if data[3] == 0x01: # 启动循迹
|
|||
|
sensor.set_pixformat(sensor.GRAYSCALE) # 设置像素格式为灰色
|
|||
|
Flag_track = True
|
|||
|
print("开始循迹")
|
|||
|
|
|||
|
elif data[3] == 0x02: # 停止循迹
|
|||
|
sensor.set_pixformat(sensor.RGB565) # 设置像素格式为彩色 RGB565
|
|||
|
Flag_track = False
|
|||
|
print("停止循迹")
|
|||
|
|
|||
|
|
|||
|
elif data[3] == 0x03: # 调整舵机
|
|||
|
print("调整舵机")
|
|||
|
self.Servo(data)
|
|||
|
else:
|
|||
|
pass
|
|||
|
|
|||
|
|
|||
|
img = sensor.snapshot() # 获取图像
|
|||
|
if Flag_track: # 循迹
|
|||
|
print("循迹")
|
|||
|
self.tracking(img)
|
|||
|
|
|||
|
lcd.display(img) # 在LCD显示
|
|||
|
|
|||
|
# 初始化摄像头阈值
|
|||
|
def canera_init(self):
|
|||
|
# 摄像头模块初始化
|
|||
|
sensor.reset() # 复位和初始化摄像头
|
|||
|
sensor.set_pixformat(sensor.RGB565) # 设置像素格式为彩色 RGB565
|
|||
|
# sensor.set_pixformat(sensor.GRAYSCALE) # 设置像素格式为灰色
|
|||
|
sensor.set_framesize(sensor.QVGA) # 设置帧大小为QVGA(320×240)
|
|||
|
sensor.set_vflip(1) # 后置模式
|
|||
|
sensor.skip_frames(30) # # 跳过前30帧
|
|||
|
|
|||
|
|
|||
|
# 循迹
|
|||
|
def tracking(self, img):
|
|||
|
print("循迹")
|
|||
|
roi_blobs_result = self.find_blobs_in_rois(img)
|
|||
|
down_center, state_crossing, deflection_angle = self.state_deflection_angle(roi_blobs_result)
|
|||
|
|
|||
|
dsd = self.data_format_wrapper(down_center, state_crossing, deflection_angle)
|
|||
|
self.UsartSend(dsd)
|
|||
|
print("下发指令:", dsd)
|
|||
|
|
|||
|
# 寻找色块 在ROIS中寻找色块,获取ROI中色块的中心区域与是否有色块的信息
|
|||
|
def find_blobs_in_rois(self, img):
|
|||
|
canvas = img.copy()
|
|||
|
roi_blobs_result = {} # 在各个ROI中寻找色块的结果记录
|
|||
|
for roi_direct in self.ROIS.keys():
|
|||
|
roi_blobs_result[roi_direct] = {
|
|||
|
'cx': 0,
|
|||
|
'cy': 0,
|
|||
|
'w': 0,
|
|||
|
'blob_flag': False
|
|||
|
}
|
|||
|
for roi_direct, roi in self.ROIS.items():
|
|||
|
blobs = canvas.find_blobs(self.LINE_COLOR_THRESHOLD, roi=roi, merge=True)
|
|||
|
if len(blobs) != 0:
|
|||
|
largest_blob = max(blobs, key=lambda b: b.pixels())
|
|||
|
if largest_blob.area() > 1000:
|
|||
|
roi_blobs_result[roi_direct]['cx'] = largest_blob.cy()
|
|||
|
roi_blobs_result[roi_direct]['cy'] = largest_blob.cx()
|
|||
|
roi_blobs_result[roi_direct]['w'] = largest_blob.h()
|
|||
|
roi_blobs_result[roi_direct]['blob_flag'] = True
|
|||
|
x, y, width, height = largest_blob[:4]
|
|||
|
img.draw_rectangle((x, y, width, height), color=(255))
|
|||
|
img.draw_string( x, (y+height)//2, roi_direct, color=(255, 255, 255), scale=1)
|
|||
|
|
|||
|
else:
|
|||
|
# blobs=canvas.find_blobs(LINE_COLOR_THRESHOLD, roi=roi, merge=True, pixels_area=10)
|
|||
|
continue
|
|||
|
blobs_baise = canvas.find_blobs(self.LINE_COLOR_BAISE, roi=(0, 0, 60, 240),
|
|||
|
merge=True) # 车载摄像头屏幕下部找白色#宽度200修改为240#用途寻卡,y示例中40修改为0
|
|||
|
blobs_dixing = canvas.find_blobs(self.LINE_COLOR_BAISE, roi=(125, 0, 60, 240),
|
|||
|
merge=True) # 车载摄像头屏幕中间找白色 #宽度200修改为240,y示例中40修改为0
|
|||
|
blobs_zuo = canvas.find_blobs(self.LINE_COLOR_THRESHOLD, roi=(0, 0, 180, 50), merge=True) # 车载摄像头屏幕左下部找黑色
|
|||
|
blobs_you = canvas.find_blobs(self.LINE_COLOR_THRESHOLD, roi=(0, 190, 180, 50), merge=True) # 车载摄像头屏幕右下部找黑色
|
|||
|
if len(blobs_baise) != 0:
|
|||
|
print("*********进入循环第1步*******")
|
|||
|
largest_baise = max(blobs_baise, key=lambda b: b.pixels())
|
|||
|
wx, wy, wwidth, wwheight = largest_baise[:4]
|
|||
|
img.draw_rectangle((wx, wy, wwidth, wwheight), color=(36, 116, 181))
|
|||
|
# img.draw_string(wx, (wy + wwheight) // 2, roi_direct, color=(255, 255, 0), scale=1)
|
|||
|
arc = wwidth * wwheight
|
|||
|
if arc >= 11000:
|
|||
|
print("*********进入循环第2步*******")
|
|||
|
if len(blobs_dixing) != 0:
|
|||
|
print("*********进入循环第3步*******")
|
|||
|
largest_dixing = max(blobs_dixing, key=lambda b: b.pixels())
|
|||
|
wx, wy, wwidth, wwheight = largest_dixing[:4]
|
|||
|
arc = wwidth * wwheight
|
|||
|
if arc >= 11000:
|
|||
|
print('kapian') # 首先中部区域识别到白色进入判断地形还是卡片,接着判断下部,如果为白色判断为卡片。
|
|||
|
self.UsartSend(self.data_format_wrapper(0, 1, 0)) # 地形停止命令
|
|||
|
if len(blobs_zuo) != 0 and len(blobs_you) != 0:
|
|||
|
print("ka十字路口")
|
|||
|
self.UsartSend(self.data_format_wrapper(1, 1, 0)) # 地形停止命令
|
|||
|
else:
|
|||
|
print('dixing')
|
|||
|
print(roi_blobs_result) # 返回的是黑色色块,各区域中心位置
|
|||
|
self.UsartSend(self.data_format_wrapper(0, 1, 0)) # 地形停止命令
|
|||
|
return roi_blobs_result # 返回的是黑色色块,各区域中心位置
|
|||
|
|
|||
|
# 计算偏转状态值
|
|||
|
def state_deflection_angle(self, roi_blobs_result):
|
|||
|
'''
|
|||
|
说明:偏转状态值返回
|
|||
|
'''
|
|||
|
# ROI区域权重值
|
|||
|
# ROIS_WEIGHT = [1, 1, 1, 1]
|
|||
|
ROIS_WEIGHT = [1, 0, 0, 1]
|
|||
|
state_crossing = False
|
|||
|
deflection_angle = 0 # 偏转角
|
|||
|
down_center = 0 # 中下值
|
|||
|
center_num = 0 # 中间值
|
|||
|
# print(roi_blobs_result)
|
|||
|
|
|||
|
# 偏转值计算,ROI中心区域X值
|
|||
|
centroid_sum = roi_blobs_result['up']['cx'] * ROIS_WEIGHT[0] + roi_blobs_result['middle_up']['cx'] * \
|
|||
|
ROIS_WEIGHT[1] \
|
|||
|
+ roi_blobs_result['middle_down']['cx'] * ROIS_WEIGHT[2] + roi_blobs_result['down']['cx'] * \
|
|||
|
ROIS_WEIGHT[3]
|
|||
|
if roi_blobs_result['up']['blob_flag']:
|
|||
|
center_num += ROIS_WEIGHT[0]
|
|||
|
if roi_blobs_result['middle_up']['blob_flag']:
|
|||
|
center_num += ROIS_WEIGHT[1]
|
|||
|
if roi_blobs_result['middle_down']['blob_flag']:
|
|||
|
center_num += ROIS_WEIGHT[2]
|
|||
|
if roi_blobs_result['down']['blob_flag']:
|
|||
|
center_num += ROIS_WEIGHT[3]
|
|||
|
|
|||
|
center_pos = centroid_sum / (ROIS_WEIGHT[0] + ROIS_WEIGHT[1] + ROIS_WEIGHT[2] + ROIS_WEIGHT[3])
|
|||
|
deflection_angle = (self.IMG_WIDTH / 2) - center_pos
|
|||
|
|
|||
|
# 判断两侧ROI区域检测到黑色线
|
|||
|
if roi_blobs_result['left']['blob_flag'] and roi_blobs_result['right']['blob_flag']:
|
|||
|
# 判断两侧ROI区域检测到黑色线处于图像下方1/3处
|
|||
|
if roi_blobs_result['left']['cy'] <= ((self.IMG_HEIGHT / 3)) or roi_blobs_result['right']['cy'] <= (
|
|||
|
(self.IMG_HEIGHT / 3)):
|
|||
|
# 当最下方ROI区域的黑线宽度大于140像素(检测到路口)
|
|||
|
if roi_blobs_result['down']['w'] > 140:
|
|||
|
down_center = 1 # 自行修改处 判断识别到十字路口
|
|||
|
print("输出了十字路口标识")
|
|||
|
|
|||
|
return down_center, state_crossing, deflection_angle
|
|||
|
|
|||
|
# 控制舵机
|
|||
|
def Tise_servo(self, angle):
|
|||
|
# 判断舵机控制方向
|
|||
|
if angle < 0:
|
|||
|
# 限制舵机角度,防止过大损坏舵机
|
|||
|
if angle > 80:
|
|||
|
angle = 80
|
|||
|
angle = -angle
|
|||
|
elif angle > 0:
|
|||
|
# 限制舵机角度,防止过大损坏舵机
|
|||
|
if angle > 35:
|
|||
|
angle = 35
|
|||
|
angle = angle
|
|||
|
# PWM通过定时器配置,接到IO17引脚
|
|||
|
tim_pwm = Timer(Timer.TIMER0, Timer.CHANNEL0, mode=Timer.MODE_PWM)
|
|||
|
S1 = PWM(tim_pwm, freq=50, duty=0, pin=17)
|
|||
|
S1.duty((angle + 90) / 180 * 10 + 2.5)
|
|||
|
|
|||
|
def Servo(self, data):
|
|||
|
'''
|
|||
|
功能:180度舵机:angle:-90至90 表示相应的角度
|
|||
|
360连续旋转度舵机:angle:-90至90 旋转方向和速度值。
|
|||
|
【duty】占空比值:0-100
|
|||
|
'''
|
|||
|
angle = data[5]
|
|||
|
# 判断舵机控制方向
|
|||
|
if data[4] == ord('-'):
|
|||
|
# 限制舵机角度,防止过大损坏舵机
|
|||
|
if angle > 80:
|
|||
|
angle = 80
|
|||
|
angle = -angle
|
|||
|
elif data[4] == ord('+'):
|
|||
|
# 限制舵机角度,防止过大损坏舵机
|
|||
|
if angle > 35:
|
|||
|
angle = 35
|
|||
|
angle = angle
|
|||
|
# PWM通过定时器配置,接到IO17引脚
|
|||
|
tim_pwm = Timer(Timer.TIMER0, Timer.CHANNEL0, mode=Timer.MODE_PWM)
|
|||
|
S1 = PWM(tim_pwm, freq=50, duty=0, pin=17)
|
|||
|
S1.duty((angle + 90) / 180 * 10 + 2.5)
|
|||
|
|
|||
|
# 检验校验和
|
|||
|
def verify_checksum(self, data):
|
|||
|
if len(data) != 8:
|
|||
|
return False
|
|||
|
# 计算校验和:data[2]、data[3]、data[4] 和 data[5] 的和,然后对256取模
|
|||
|
calculated_checksum = (data[2] + data[3] + data[4] + data[5]) % 256
|
|||
|
# 比较计算出的校验和与data[6]是否相等
|
|||
|
if calculated_checksum == data[6]:
|
|||
|
return True
|
|||
|
else:
|
|||
|
return False
|
|||
|
|
|||
|
# 串口发送
|
|||
|
def UsartSend(self, str_data):
|
|||
|
'''
|
|||
|
串口发送函数
|
|||
|
'''
|
|||
|
print(str_data)
|
|||
|
self.uart.write(str_data)
|
|||
|
|
|||
|
# 判断符号
|
|||
|
def get_symbol(self, num):
|
|||
|
'''
|
|||
|
根据数值正负,返回数值对应的符号
|
|||
|
正数: ‘+’, 负数‘-’ 主要为了方便C语言解析待符号的数值。
|
|||
|
'''
|
|||
|
print("num = ", num)
|
|||
|
if num >= 0:
|
|||
|
return ord('+')
|
|||
|
else:
|
|||
|
return ord('-')
|
|||
|
|
|||
|
# 封装数据
|
|||
|
def data_format_wrapper(self, down_center, state_crossing, deflection_angle):
|
|||
|
'''
|
|||
|
根据通信协议封装循迹数据
|
|||
|
TODO 重新编写通信协议 与配套C解析代码
|
|||
|
'''
|
|||
|
send_data = [
|
|||
|
0x55,
|
|||
|
0x02,
|
|||
|
0x91,
|
|||
|
down_center, # 底部色块中心是否在中点附近#底部色块十字路口
|
|||
|
1 if state_crossing else 0, # 是否越障 无用
|
|||
|
self.get_symbol(deflection_angle), # 偏航角符号 print输出是43 + 45- +向左转调整 -向右转调整
|
|||
|
abs(int(deflection_angle)), # 偏航角
|
|||
|
0xbb]
|
|||
|
return bytes(send_data)
|
|||
|
|
|||
|
|
|||
|
# 运行程序
|
|||
|
myMain = Mainlen()
|
|||
|
myMain.startMain()
|