目录
个人感慨与感悟
本人是一名大二的二本学生,本篇文章记录一下我这一年多的学习历程(主要还是针对标题的内 容)。下面是我的一些经历和感悟,不感兴趣的可以直接跳过-——————————
高考完之后,在我收到录取通知书之后,父母让我提前学点大学的东西,比如高数英语之类的。后来,我就在b站上面逛逛,然后了解到了python这门语言。后来........被卖课的骗了,上了及几十节基础语法(上网上搜,随便一搜一大堆),花了三千多(服了,当时我还傻乎乎的分期付款);正如我前几篇文章。
开学之后,在新生群里,看到有机器人之类的社团招生。想着给自己的大学生活找点事情做做,进入社团之后,我被那些智能车给深深的迷住了。从此开启造车之路。
大一上没学啥。就学了Arduino的一些基础知识,如digitalRead(),analogWrite()函数之类的,后来在期末的时候勉强做出了一个小车。仅仅能控制正转反转哈哈哈。当时一直不理解寻迹小车是生么个逻辑,为什么检测到黑线能直走,为什么检测到十字路口就能转弯;(实际上,就是循迹传感器检测到黑线返回高电平(也有的是低电平),单片机检测到为高电平(低电平),我就控制电机,从而它才能走。而不是检测到高电平它自己就动,而是我写了代码让他动而已)
大一下学的还行,还是学arduino。现在看来进度挺慢的。就学了一些模块,如超声波,机械臂,esp8266,激光传感器,播放器之类的。因为口罩原因,所有吉林省电赛推迟举办了,然后我就碰巧让我遇上了,那年省赛是自主命题,也是小车循迹,不过那时候我还没有完全搞清楚循迹的逻辑,很菜,然后连循迹都没循好,(扇形那块卡住了),后来和学长合作,给学长了做了个人机互界面
升大二时候的暑假,打了电赛,也取得了一点成绩(大佬轻喷,手下留情),我做的是E题,激光追踪,主控还是用的Mega2560,搭配云台,K210。
2023电赛
大二上,也就是这个学期。提前一个月准备 吉林省人工智能创新大赛。打了四次板子,车身外壳用3d打印机打的,最后比赛前一天晚上摄像头被我搞坏了,哎。好了,正式开始正题吧
K210视觉循迹
K210基于MicroPython 语言,而“MicroPython 是 Python 3 编程语言的精简高效实现。说人话就是K210用python编程。
我是利用K210检测物体的方法来写的循迹,这个方法的话肯定是没有利用二值化那些方法寻线好,因为我选择的赛道是全地形赛道,需要识别不同颜色的障碍物,所以只能靠检测物体来做。
先附上源码(参考CSDN一些大佬文章写的,不是纯手搓哈哈哈,刚接触K210,还没那个实力
/(ㄒoㄒ)/~~)
# Untitled - By: ZWHSONDER - 周四 10月 26 2023 # 导入模块 import sensor, time, image ,lcd from machine import UART from fpioa_manager import fm # 配置串口引脚 fm.register(18, fm.fpioa.UART1_TX, force=True) uart_A = UART(UART.UART1, 115200, 8, 0, 1, timeout=1000, read_buf_len=4096) # 感光元件设置 sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.set_auto_exposure(1) sensor.set_auto_gain(False) sensor.set_auto_whitebal(False) sensor.set_vflip(1) sensor.set_hmirror(False) sensor.skip_frames(time = 2000) # 显示屏初始化 lcd.init(freq=15000000) # 创建时钟对象 clock = time.clock() # 寻找色块 # 定义类 class color_property(): cx = 0 cy = 0 flag = 0 color = 0 pixels_max = 0 color_threshold = (0, 0, 0, 0, 0, 0) color_roi = (0,0,320,240) color_x_stride = 1 color_y_stride = 1 color_pixels_threshold = 100 color_area_threshold = 100 color_merge = True color_margin = 1 # 定义黑色色块 black = color_property() black.color_threshold = (0, 20, -9, 4, -5, 6) black.color_roi = (0,120,320,240) black.color_x_stride = 1 black.color_y_stride = 1 black.color_pixels_threshold = 4000 black.color_area_threshold = 4000 black.color_merge = True black.color_margin = 1 #串口发送数据 def sending_data(x,y): FH = bytearray([0x2C,0x12,x,y,0x5B]) uart_A.write(FH); # 定义寻找色块函数 def opv_find_blobs(color,led_flag): color.pixels_max = 0 color.flag = 0 color.led_flag = 0 for blobs in img.find_blobs([color.color_threshold], roi = color.color_roi, x_stride = color.color_x_stride, y_stride = color.color_y_stride, pixels_threshold = color.color_pixels_threshold, area_threshold = color.color_area_threshold, merge = color.color_merge, margin = color.color_margin): img.draw_rectangle(blobs[0:4]) if color.pixels_max < blobs.pixels(): color.pixels_max = blobs.pixels() color.cx = blobs.cx() color.cy = blobs.cy() color.w = blobs.w() color.h = blobs.h() color.flag = 1 color.density = blobs.density() def zi_tai_jiao_zheng(): #只用于循普通黑线中的姿态矫正 opv_find_blobs(black,1) if black.flag == 1: # 标记画面中被找到的最大色块的中心坐标 if black.w<90: if black.cx>180: if black.cy>160: if black.cy<240: if black.cx<215: sending_data(1,1) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx<240: sending_data(1,2) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx<260: sending_data(1,3) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx<290: sending_data(1,4) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx<320: sending_data(1,5) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx<345: sending_data(1,6) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx<180: if black.cy>160: if black.cy<240: if black.cx>130: sending_data(2,1) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx>110: sending_data(2,2) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx>90: sending_data(2,3) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx>70: sending_data(2,4) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx>40: sending_data(2,5) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx>15: sending_data(2,6) print("x=%d,y=%d"%(black.cx,black.cy)) def shi_zi(): opv_find_blobs(black,1) if black.flag == 1: if black.w >300: if black.h >60:#80 if black.cx>140: if black.cx<220: if black.cy>160: if black.cy<200: sending_data(4,1) time.sleep(1) print("222222222w=%d,h=%d"%(black.w,black.h)) # 主函数 while(True): clock.tick() img = sensor.snapshot() opv_find_blobs(black,1) opv_find_blobs(red,3) zi_tai_jiao_zheng() shi_zi() lcd.display(img)
我用的固件是maixpy v0.6.2 84 g8fcd84a58.bin(固件不同,库也不同)
大概思路:首先,定义一个类函数(这样方便定义不同颜色)。定义黑色的色块。识别到黑色色块画矩形。然后对黑色色块加些限制条件,根据不同条件(这样我们就会知道小车处于什么状态),向C8T6发送不同的数据,从而32执行不同的代码,决定小车往哪走。
导入各类模块
# 导入模块 import sensor, time, image,lcd from machine import UART from fpioa_manager import fm
①import sensor,time,image,lcd
# 导入感光元件模块 sensor 跟踪运行时间模块 time 机器视觉模块 image
导入这些模块,才能使用里面的函数
②from machine import UART
#导入串口库函数(为了K210和stm32之间串口通信)
③from fpioa_manager import fm
#GPIO重定向函数(导入这个函数,我们就可以定义引脚了(K210得映射引脚,不然就用不了,就是把芯片上的引脚映射到具体K210板子上外拓的引脚上))
配置引脚
# 配置串口引脚 fm.register(18, fm.fpioa.UART1_TX, force=True) uart_A = UART(UART.UART1, 115200, 8, 0, 1, timeout=1000, read_buf_len=4096)
①fm.register(18, fm.fpioa.UART1_TX, force=True)
#映射串口引脚,我这里是将芯片上串口一的发送引脚映射到k210板子上的18号位引脚
fm.register(pin,function,force=False)
pin:芯片外部IO口,也就是K210板子上面外拓的引脚
function:芯片功能,例如这儿的 UART1_TX,它的功能就是发送数据
force=True:强制注册,清除之前的注册记录
②uart_A = UART(UART.UART1, 115200, 8, 0, 1, timeout=1000, read_buf_len=4096)
#设置串口引脚一系列参数;machine.UART(uart,baudrate,bits,parity,stop,timeout, read_buf_len)
uart:串口编号
baudrate:波特率
bits:数据位,默认8
parity:校验,默认None,0为偶校验,1为奇校验
stop:停止位,默认1
timeout:串口接收超时时间
read_buf_len:串口接收缓冲大小
感光元件设置
#感光元件设置 sensor.reset() sensor.set_pixformat(sensor.RGB565) sensor.set_framesize(sensor.QVGA) sensor.set_auto_exposure(1) sensor.set_auto_gain(False) sensor.set_auto_whitebal(False) sensor.set_vflip(1) sensor.set_hmirror(False) sensor.skip_frames(time = 2000)
①sensor.reset()
# 重置并初始化感光元件 默认设置为 摄像头频率 24M 不开启双缓冲模式(用人话说就是“初始化摄像头”)
②sensor.set_pixformat(sensor.RGB565)
#sensor.set_pixformat(pixformat);设置像素格式;pixformat有2个参数:
sensor.GRAYSCAL:灰度图像,每像素 8 位(1 字节)(处理速度快)
sensor.RGB565: 彩色图像,每像素为16位(2字节),5位用于红色,6位用于 绿色,5位用于蓝色(但是处理速度比灰度图像要慢)
③sensor.set_framesize(sensor.QVGA)
sensor.set_framesize(framesize),设置每帧大小,其实就是图像尺寸,有以下的参数:
sensor.VGA sensor.QVGA sensor.QQVGA sensor.QQQVGA 从左到右,帧数逐渐变小(尺寸变小),每秒钟拍下的照片变多,从而更精确
④sensor.set_auto_exposure(1)
#设置自动曝光
⑤sensor.set_auto_gain(False)
#关闭自动增益,自动增益:放大电路的增益自动随信号强度调整而自动控制方法,会影响对颜色的判断,所以建议关掉,也就是False
⑥set_auto_whitebal(False)
#关掉自动白平衡;白平衡:是描述显示器中红、绿、蓝三基色混合生成后白色精确度的一项指标,数值越高,色调越暖,数值越低,色调越冷!
⑦sensor.set_vflip(1)
#将画面垂直反转(我的K210有点奇怪,就是它的图像是左右反的(镜像),所以我得让他图像反转过来)
⑧sensor.set_hmirror(False)
#不水平翻转,如果是True则水平翻转,这主要是针对lcd屏内显示的图像(如果图像是倒着,则用该函数)
⑨sensor.skip_frames(time = 2000)
#sensor.skip_frames([n, time]):n:摄像头配置后跳过的帧数;time:等待时间
如果n和time均没指定,则默认跳过300毫秒的帧。(我这里是跳过2000毫秒的帧数)
显示屏和时钟
lcd.init(15000000) clock = time.clock()
①lcd.init(freq=15000000)
# 初始化lcd屏,lcd.init(type=1,freq=15000000,color=lcd.BLACK)
type:lcd类型
freq:通信频率
color:LCD初始化的颜色
而我这里传了一个参数叫freq(即-频率),是指驱动lcd的时钟频率,这里是15MHZ,其它参数默认
②clock = time.clock()
#创建一个时钟;用于计算 FPS(每秒帧数)
定义类(寻找色块)
class color_property(): cx = 0 cy = 0 flag = 0 color = 0 pixels_max = 0 color_threshold = (0, 0, 0, 0, 0, 0) color_roi = (0,0,320,240) color_x_stride = 1 color_y_stride = 1 color_pixels_threshold = 100 color_area_threshold = 100 color_merge = True color_margin = 1
①cx,cy:分别代表色块正中心的x轴值和y轴值
②flag:色块标志位,1代表找到色块,0代表没找到色块
③color:色块颜色标志位,下面在实例化类的时候,你可以设定值(比如:实例化黑色块,你设它为666,那么则666代表黑色)
④pixels_max:色块像素最大值(搞这个便于我们筛选掉那些误判的小像素,减少影响)
⑤color_threshold:色块颜色阈值,识别是哪种颜色就是靠的这个(阈值怎么调,网上一搜一大堆,这里就不赘述了哈)
⑥color_roi:色块寻找区域(感兴趣区域);
(x轴起始值,y轴起始值,x轴结束值,y轴结束值)选择感兴趣的区域,这四个值根据你想要检测的区域来设定,一旦设定了,它就只能检测你设定的区域里的色块,就算其他区域有咱想要的色块,他也不会检测到!!!
⑦color_x_stride,color_y_stride:色块的x轴y轴像素最小宽度(同样可以减小误差),假如我们想要的色块很大,那太小的肯定不符合,但我们又不想让它检测到,那就可以设置最小宽度,值小于这个宽度的就不检测。
⑧color_pixels_threshold,color_area_threshold:色块像素个数阈值;色块被框面积阈值 (这两用法和⑦简直一毛一样的作用,那既然一样,为什么还要写这个,因为有特殊情况,所以我们写⑧纯粹是多给它个限制条件而已,保险,防止误判!!!)
⑨color_merge,color_margin:
1.是否合并寻找到的色块 ,True 则合并 False 则不合并;
2.色块合并间距 例如调节此参数为1 若上面选择True合并色块 且被找到的色块有多个相距1像素 则会将这些色块合并;
实例化类
这里我就写一个黑色块,其他色块可以自行添加
# 实例化类 # 黑色 black = color_property() black.color_threshold = (0, 20, -9, 4, -5, 6) black.color_roi = (0,120,320,240) black.color_x_stride = 1 black.color_y_stride = 1 black.color_pixels_threshold = 4000 black.color_area_threshold = 4000 black.color_merge = True black.color_margin = 1
这些参数定义类上面都说过,所以这里就没必要再次阐述,我就说一下color_roi参数的设置
为什么我这里设置(0,120,320,240),而不是(0,0,320,240)。因为如果是从y轴0开始的话,识别范围大,而我这里设置从y轴120开始,则就意味着,在y轴我只检测从120-240内的颜色,它识别范围变小,所以误差也就变小,容错率变高。你也可以设置成其他数,看需求。
串口发送数据
我这里我就大概解释一下,不仔细说了
这一步很重要,是用于K210和STM32之间通信。两者通信的根本是在于发送“字符”或者“数字”;
#串口发送数据 def sending_data(x,y): FH = bytearray([0x2C,0x12,x,y,0x5B]) uart_A.write(FH);
UART.write(buf):串口发送数据函数,buf:需要发送的数据
那么这里为什么我要在这个数组里加上“0x2C,0x12,0x5B”呢?
答:串口有 起始位,数据位,校验位,停止位。0x2C,0x12为起始位。
①为什么要有起始位?
起始位是相当于告诉单片机,我从这里“开始”。所以,如果STM32串口检测到0x2C,则意味这串口接收到数据了。 ps:那么为什么不直接接收“数据位”呢?因为会有时候接收不到数据,比如说我发“123456”,而我只收到“23456”,那么请问这个数据还有用吗,答案是肯定没有用的!而有了“开始位”则不一样,比如说我接收,好,如果开头接收到的第一个数据不是0x2C,则我们就不要了呗,直到再次检测到0x2C !两个起始位保险一点,道理一样的!
②什么是数据位呢?
“数据位”就是你要发送的数据,例如单个字符,还是数字。就是我上面所说的“两者通信的根本是在于发送“字符”或者“数字”。
③什么停止位?
一般停止位为1,是告诉单片机,我们检测到1,数据接收完了(一整个数组),那么0x5B又是什么呢?他既是检测数据接收的完整性的一个东西,又是告诉单片机我接收完成,但是得等到检测停止位为1,才能停止接收,直下一次检测再检测到开始位。
定义寻找色块的函数
之前那是定义色块,这里是寻找色块!两者的区别就是字面上的意思,定义色块 or 寻找色块
def opv_find_blobs(color,led_flag): color.pixels_max = 0 color.flag = 0 color.led_flag = 0 for blobs in img.find_blobs([color.color_threshold], roi = color.color_roi, x_stride = color.color_x_stride, y_stride = color.color_y_stride, pixels_threshold = color.color_pixels_threshold, area_threshold = color.color_area_threshold, merge = color.color_merge, margin = color.color_margin): img.draw_rectangle(blobs[0:4]) if color.pixels_max < blobs.pixels(): color.pixels_max = blobs.pixels() color.cx = blobs.cx() color.cy = blobs.cy() color.w = blobs.w() color.h = blobs.h() color.flag = 1 color.density = blobs.density()
(写到这里,已是凌晨3:28 ; 累,但是生命的意义在于不停的创作!)
①:
color.pixels_max = 0 : 重置色块最大像素数量
color.flag = 0 : 重置色块标志位
color.led_flag = 0 : 重置LED标志位
每一次都重置,可以避免误判,之前检测到的色块影响;
②:
for blobs in img.find_blobs([color.color_threshold],
roi = color.color_roi,
x_stride = color.color_x_stride,
y_stride = color.color_y_stride,
pixels_threshold = color.color_pixels_threshold,
area_threshold = color.color_area_threshold,
merge = color.color_merge,
margin = color.color_margin):
这个是一个函数,寻找色块函数(原始函数):
img.find_blobs([thresholds], roi=my_roi, x_stride=0, y_stride=0, invert=False, area_threshold=0,pixels_threshold=0,merge=False)参数 | 用法 |
[thresholds] | 必须是元组列表,定义你想追踪的颜色范围。 |
roi | 色块寻找区域(感兴趣区域) |
x_stride | 色块x轴像素最小宽度 |
y_stride | 色块y轴像素最小宽度 |
pixels_threshold | 色块像素个数阈值 |
area_threshold | 色块被框面积阈值 |
merge | color.color_merge实际上就是我们在定义类的时候的True |
margin | 色块合并间距 |
③
③ img.draw_rectangle(blobs[0:4])
标记色块 | 参数 |
img.draw_rectangle(x, y, w, h[, color[, thickness=1[, fill=False]]]) 画矩形 | (x,y):起始坐标;w:宽度;h:长度;color:颜色;thickness:边框粗细;fill:是否填充。 |
img.draw_circl(x, y, radius[, color[, thickness=1[, fill=False]]]) 画圆 | (x,y):圆心; radius:半径; color:颜色;thickness:线条粗细; fill:是否填充。 |
img.draw_arrow(x0, y0, x1, y1[, color[, size,[thickness=1]]]) 画箭头 | (x0,y0):起始坐标;(x1,y1):终点坐标;color:颜色;size:箭头位置 大小。thickness:线粗细。 |
img.draw_cross(x, y[, color[, size=5[, thickness=1]]]) 画十字交叉 | (x,y): 交叉坐标;color:颜色;size:尺寸;thickness:线粗 细。 |
④if color.pixels_max < blobs.pixels():
这个就是判断,判断是否找到面积最大的色块
⑤
其实就是一系列赋值而已
color.pixels_max = blobs.pixels() # 将面积最大的色块的像素值赋值给color
color.cx = blobs.cx() # 将面积最大的色块的 x轴 中心坐标值 赋值给 color
color.cy = blobs.cy()
color.w = blobs.w() #将面积最大的色块的宽度赋值给color
color.h = blobs.h()
color.flag = 1 # 标志画面中有找到色块
color.density = blobs.density() # 将面积最大的色块的 色块密度比 赋值给 color
姿态矫正
其实就是在走普通直线过程中可能会走歪,然后纠正一下而已啦
ps:我这里纠正靠的是,K210识别到歪了之后,然后串口发送数据给C8T6,然后C8T6收到对应的数据,从而控制车子向左向右偏!(由此可见串口通信的重要性)
def zi_tai_jiao_zheng(): #只用于循普通黑线中的姿态矫正 opv_find_blobs(black,1) if black.flag == 1: # 标记画面中被找到的最大色块的中心坐标 if black.w<90: if black.cx>180: if black.cy>160: if black.cy<240: if black.cx<215: sending_data(1,1) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx<240: sending_data(1,2) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx<260: sending_data(1,3) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx<290: sending_data(1,4) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx<320: sending_data(1,5) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx<345: sending_data(1,6) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx<180: if black.cy>160: if black.cy<240: if black.cx>130: sending_data(2,1) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx>110: sending_data(2,2) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx>90: sending_data(2,3) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx>70: sending_data(2,4) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx>40: sending_data(2,5) print("x=%d,y=%d"%(black.cx,black.cy)) elif black.cx>15: sending_data(2,6) print("x=%d,y=%d"%(black.cx,black.cy))
原谅本人才疏学浅,只想出通过判断中心坐标和色块宽度和高度来确定车子所处的方向,还有许许多多的办法,欢迎各位大佬指教
①opv_find_blobs(black,1)先调用色块寻找函数,并定义参数(为黑色)
② if black.flag == 1 判断是否为最大色块,实际上这样做的目的就是检测最大色块
③然后就是层层判断,先判断宽度再判断中心坐标,因为K210摄像头是有x轴,y轴。所以我们通过色块的中心坐标与摄像头中心坐标对比,来确定车身处于哪个方向
其他:例如十字路口,左直角,右直角。同理!!!
主函数
必须有主函数
# 主函数 while(True): clock.tick() # 跟踪运行时间 img = sensor.snapshot() # 拍摄一张照片 opv_find_blobs(black,1) zi_tai_jiao_zheng() shi_zi() zuo_zhi_jiao() you_zhi_jiao() mei_you_hei_xian() lcd.display(img)
这个就是把我上面写的这些函数放里面就行了,不停循环检测;
这就是K210部分的全部内容,欢迎各位大佬指正!人非圣贤,孰能无过!
后期有空再更新STM32部分的知识,谢谢大家的阅读