├── .gitignore └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2021电赛F题视觉分析+代码免费开源 2 | 3 | > 最近好多小宝宝们要电赛题的源码,其他csdn营销号下载都需要会员或者钱,哥们先把视觉分析+代码开源,饿死营销号 4 | 5 | 视觉的一个任务是视觉上位机模块识别数字并进行滤波和判断处理,传指令给下位机;另一个任务是红线循迹。 6 | 7 | 8 | 9 | 不多bb,先上效果图: 10 | 11 | K210数字识别效果: 12 | 13 | ![k210结果2](https://github.com/zhaohaobingSUI/ZHB_project_diansai/blob/master/images/k210%E7%BB%93%E6%9E%9C2.png) 14 | 15 | (我媛哥的手真好看) 16 | 17 | openmv红线循迹效果: 18 | 19 | ![openmv结果1](https://github.com/zhaohaobingSUI/ZHB_project_diansai/blob/master/images/openmv%E7%BB%93%E6%9E%9C1.png) 20 | 21 | 22 | 23 | 硬件工具:K210、openmv 24 | 25 | 软件工具:Maixpy IDE、OpenMV IDE 26 | 27 | IDE可到官网下载: 28 | 29 | https://www.sipeed.com/index.html 30 | 31 | https://singtown.com/openmv/ 32 | 33 | ![IDE](https://github.com/zhaohaobingSUI/ZHB_project_diansai/blob/master/images/IDE.png) 34 | 35 | ## 1、K210数字识别、滤噪、判断 36 | 37 | 正常操作是上位机是识别,给下位机传数据,处理放到下位机;假如你有个坑比队友,处理还是放上位机干吧; 38 | 39 | 经过算法优化,现在几乎不存在掉帧。 40 | 41 | ### 1.1 功能难点及对应函数实现分析 42 | 43 | 1、在小车行驶过程中,会存在数字不能完全进入视野中情况等存在有很多帧误识情况,要进行滤波处理 44 | 45 | 2、每一帧要做处理的同时每个目标值的矩形框进行处理,每个目标值的矩形框还要进行滤波 46 | 47 | 3、在上位机上不断的优化算法,减少掉帧 48 | 49 | 4、在Maixpy IDE中python 的numpy、pandas巴拉巴拉包都调不了 50 | 51 | 5、图中最多出现四个,但这里设了读六个矩形框,留两个容错,不然有误识别的直接死机 52 | 53 | 6、给下位机传送指令: 54 | 55 | 一开始初始化识别传送对应的数字 56 | 57 | 若为12,则后面不在传送指令 58 | 59 | 若为34,则后面路口处传"l"or"r" 60 | 61 | 若为5678,则后面两个路口传送两次"l"or“r” 62 | 63 | 7、设计思路 64 | 65 | 通过初始化函数识别一开始给入的数字并保存下来; 66 | 67 | 根据识别的情况进入不同的程序; 68 | 69 | 12号病房,识别完了以后是不用在路上在识别的,直接关掉程序; 70 | 71 | 34号病房,初始化识别完了,加两秒延迟,避免在开始的时候连续识别传指令,要在路上识别一次,要保证返回来的时候不识别不然又给32传指令了,还要保证初始化识别完了以后不会紧接着识别误认为是路上的标签给32传指令; 72 | 73 | 5678也一样,但是路上得识别两次穿两次指令; 74 | 75 | 76 | 77 | ### 1.2 YOLOV5神经网络模型训练 78 | 79 | 训练集: 80 | 81 | ![数据集1](https://github.com/zhaohaobingSUI/ZHB_project_diansai/blob/master/images/%E6%95%B0%E6%8D%AE%E9%9B%861.png) 82 | 83 | 测试集: 84 | 85 | ![数据集2](https://github.com/zhaohaobingSUI/ZHB_project_diansai/blob/master/images/%E6%95%B0%E6%8D%AE%E9%9B%862.png) 86 | 87 | 训练输出结果: 88 | 89 | ![输出结果](https://github.com/zhaohaobingSUI/ZHB_project_diansai/blob/master/images/%E8%BE%93%E5%87%BA%E7%BB%93%E6%9E%9C.png) 90 | 91 | 92 | 93 | 将通过拍摄3403张赛道数字照片,使用labellmg进行标签标定,制作数据集,yolov5训练 94 | 95 | 详细过程可看下面文章: 96 | 97 | https://blog.csdn.net/zhaohaobingniu/article/details/120397185?spm=1001.2014.3001.5502 98 | 99 | https://blog.csdn.net/zhaohaobingniu/article/details/120255571?spm=1001.2014.3001.5502 100 | 101 | 注意:这里yolov5得到的是.pt文件,需要进行模型转换,转化成可以加载在K210板上的.kmodel模型(后面细讲) 102 | 103 | 104 | 105 | ### 1.3 K210操作步骤 106 | 107 | #### 1.3.1 下载K210的IDE 108 | 109 | https://www.sipeed.com/index.html 110 | 111 | #### 1.3.2 烧入最新固件库 112 | 113 | 官方教程: 114 | 115 | https://wiki.sipeed.com/soft/maixpy/zh/get_started/upgrade_maixpy_firmware.html 116 | 117 | 更新固件库: 118 | 119 | ![固件库](https://github.com/zhaohaobingSUI/ZHB_project_diansai/blob/master/images/%E5%9B%BA%E4%BB%B6%E5%BA%93.png) 120 | 121 | #### 1.3.3 将文件放入TF卡中 122 | 123 | 文件目录: 124 | 125 | ![文件目录](https://github.com/zhaohaobingSUI/ZHB_project_diansai/blob/master/images/%E6%96%87%E4%BB%B6%E7%9B%AE%E5%BD%95.png) 126 | 127 | 将上述文件全复制到tf卡中,tf卡插入k210 128 | 129 | 注意:tf卡先格式成FAT32格式,部分牌子的tf卡格式了也不能挂载到k210上,建议多换几张卡试试 130 | 131 | #### 1.3.4 IED中查看效果 132 | 133 | IDE中运行效果: 134 | 135 | ![ide效果图](https://github.com/zhaohaobingSUI/ZHB_project_diansai/blob/master/images/ide%E6%95%88%E6%9E%9C%E5%9B%BE.png) 136 | 137 | #### 1.3.5 通过串口调试助手测试指令通信 138 | 139 | 引脚9是TX,引脚10是RX,波特率为115200 140 | 141 | 通过usb转ttl连接到电脑上 142 | 143 | 初始识别数字为6,两次转向是向左 144 | 145 | ![串口发送数据图](https://github.com/zhaohaobingSUI/ZHB_project_diansai/blob/master/images/%E4%B8%B2%E5%8F%A3%E5%8F%91%E9%80%81%E6%95%B0%E6%8D%AE%E5%9B%BE.png) 146 | 147 | 148 | 149 | ### 1.4 上代码含详细注释 150 | 151 | 152 | 153 | 第一版: 154 | 155 | ```python 156 | import sensor,image,lcd,time 157 | import KPU as kpu 158 | from machine import UART 159 | from fpioa_manager import fm 160 | 161 | lcd.init(freq=15000000) 162 | sensor.reset() 163 | sensor.set_pixformat(sensor.RGB565) 164 | sensor.set_framesize(sensor.QVGA) 165 | sensor.set_hmirror(0) 166 | sensor.run(1) 167 | #加载yolov5模型 168 | task = kpu.load("/sd/yolov5.kmodel") 169 | f=open("anchors.txt","r") 170 | anchor_txt=f.read() 171 | L=[] 172 | for i in anchor_txt.split(","): 173 | L.append(float(i)) 174 | anchor=tuple(L) 175 | f.close() 176 | f=open("lable.txt","r") 177 | lable_txt=f.read() 178 | lable = lable_txt.split(",") 179 | f.close() 180 | #设置RX、RT引脚 181 | fm.register(9, fm.fpioa.UART1_TX, force=True) 182 | fm.register(10, fm.fpioa.UART1_RX, force=True) 183 | #设置串口通信 184 | uart_A = UART(UART.UART1, 115200, 8, 1, 0, timeout=1000, read_buf_len=4096) 185 | anchor = (0.1766, 0.1793, 0.4409, 0.3797, 0.6773, 0.5954, 1.0218, 0.9527, 2.158, 1.6841) 186 | sensor.set_windowing((224, 224)) 187 | a = kpu.init_yolo2(task, 0.5, 0.3, 5, anchor) 188 | classes=["9","1","4","2","3","8","5","6","7" ] 189 | 190 | #全局变量,保存初始化识别的数字 191 | intnum = 0 192 | #初始化识别函数 193 | def begin(intnum): 194 | TF = 1 195 | #得分序列,放1-8识别的次数,每一帧识别成哪个,对应的位置加一,1-8哪个先到10即最终识别为哪个 196 | List_score01 = [0]*8 197 | while(TF): 198 | img = sensor.snapshot() 199 | #code是yolov5返回的每个矩形框的参数 200 | #例图中出现两个目标区域的时候:[{"x":9, "y":99, "w":55, "h":82, "value":0.697979, "classid":8, "index":0, "objnum":2}, {"x":137, "y":105, "w":56, "h":67, "value":0.939132, "classid":4, "index":1, "objnum":2}] 201 | code = kpu.run_yolo2(task, img) 202 | #print(code) 203 | if code: 204 | for i in code: 205 | #画目标区域矩形框 206 | a = img.draw_rectangle(i.rect()) 207 | a = lcd. display(img) 208 | list1=list(i.rect()) 209 | #print(classes[i.classid()]) 210 | #识别到的加1 211 | List_score01[int(classes[i.classid()])-1] += 1 212 | #print(List_score01) 213 | if(List_score01[0] >= 10): 214 | intnum = 1 215 | #给下位机通信传指令 216 | uart_A.write('1') 217 | #print(1) 218 | #退出初始化循环 219 | TF = 0 220 | if(List_score01[1] >= 10): 221 | intnum = 2 222 | uart_A.write('2') 223 | #print(2) 224 | TF = 0 225 | if(List_score01[2] >= 10): 226 | intnum = 3 227 | uart_A.write('3') 228 | #print(3) 229 | TF = 0 230 | if(List_score01[3] >= 10): 231 | intnum = 4 232 | uart_A.write('4') 233 | #print(4) 234 | TF = 0 235 | if(List_score01[4] >= 10): 236 | intnum = 5 237 | uart_A.write('5') 238 | #print(5) 239 | TF = 0 240 | if(List_score01[5] >= 10): 241 | intnum = 6 242 | uart_A.write('6') 243 | #print(6) 244 | TF = 0 245 | if(List_score01[6] >= 10): 246 | intnum = 7 247 | uart_A.write('7') 248 | #print(7) 249 | TF = 0 250 | if(List_score01[7] >= 10): 251 | intnum = 8 252 | uart_A.write('8') 253 | print(8) 254 | TF = 0 255 | else: 256 | a = lcd.display(img) 257 | return intnum 258 | 259 | #34道路识别函数 260 | def threefour(intnum): 261 | #设置二维矩阵,存放每一个矩形框中不同数字识别的次数 262 | List_score02 = [[0]*8] * 6 263 | intnum02 = [0]*6 264 | TF = 1 265 | while(TF): 266 | #加载一帧图像 267 | img = sensor.snapshot() 268 | code = kpu.run_yolo2(task, img) 269 | if code: 270 | int_i = 0 271 | for i in code: 272 | int_i +=1 273 | a=img.draw_rectangle(i.rect()) 274 | a = lcd. display(img) 275 | list1=list(i.rect()) 276 | b=(list1[0]+list1[2])/2 277 | #对应的矩阵值加一 278 | List_score02[int_i][int(classes[i.classid()])-1] += 1 279 | print(List_score02[int_i]) 280 | #该目标框逐个数字分析是否出现次数到达10,即为该目标区域中的数字 281 | for ii in range(8): 282 | if(List_score02[int_i][ii] >= 10): 283 | intnum02[int_i] = ii+1 284 | #该目标区域中的数字是否是初始化识别的数字 285 | if(intnum == intnum02[int_i]): 286 | #判断位置,从而判断先做向右转 287 | if(b < 75): 288 | uart_A.write("l") 289 | print("l") 290 | TF = 0 291 | if(b > 75): 292 | uart_A.write("r") 293 | print("r") 294 | TF = 0 295 | else: 296 | a = lcd.display(img) 297 | return 0 298 | 299 | #5678道路识别函数 300 | def fivesixseveneight(intnum): 301 | #设置二维矩阵,存放每一个矩形框中不同数字识别的次数,一帧中最多有四个目标区域,但这里设置六个,防止有误识别的造成有很多矩形框程序暴死 302 | List_score02 = [[0]*8] * 6 303 | intnum02 = [0]*6 304 | TF = 2 305 | #5678道路识别要识别两次,传两次指令,两次直接通过延时函数断开 306 | while(TF): 307 | img = sensor.snapshot() 308 | code = kpu.run_yolo2(task, img) 309 | if code: 310 | int_i = 0 311 | for i in code: 312 | int_i +=1 313 | a=img.draw_rectangle(i.rect()) 314 | a = lcd. display(img) 315 | list1=list(i.rect()) 316 | b=(list1[0]+list1[2])/2 317 | List_score02[int_i][int(classes[i.classid()]) -1] += 1 318 | print(List_score02[int_i]) 319 | for ii in range(8): 320 | if(List_score02[int_i][ii] >= 10): 321 | intnum02[int_i] = ii+1 322 | if(intnum == intnum02[int_i]): 323 | if(b < 75): 324 | uart_A.write("l") 325 | print("l") 326 | TF -= 1 327 | List_score02 = [[0]*8] * 6 328 | intnum02 = [0]*6 329 | time.sleep(3) 330 | if(b > 75): 331 | uart_A.write("r") 332 | print("r") 333 | TF -= 1 334 | List_score02 = [[0]*8] * 6 335 | intnum02 = [0]*6 336 | time.sleep(3) 337 | else: 338 | a = lcd.display(img) 339 | return 0 340 | 341 | #执行程序 342 | intnum = begin(intnum) 343 | time.sleep(3) 344 | if(intnum == 3 or intnum == 4): 345 | threefour(intnum) 346 | if(intnum == 5 or intnum == 6 or intnum ==7 or intnum == 8): 347 | fivesixseveneight(intnum) 348 | uart_A.deinit() 349 | del uart_A 350 | a = kpu.deinit(task) 351 | 352 | 353 | ``` 354 | 355 | 356 | 357 | 第二版: 358 | 359 | ```python 360 | import sensor,image,lcd,time 361 | import KPU as kpu 362 | from machine import UART 363 | from fpioa_manager import fm 364 | 365 | lcd.init(freq=15000000) 366 | sensor.reset() 367 | sensor.set_pixformat(sensor.RGB565) 368 | sensor.set_framesize(sensor.QVGA) 369 | sensor.set_hmirror(0) 370 | sensor.run(1) 371 | #加载yolov5模型 372 | task = kpu.load("/sd/yolov5.kmodel") 373 | f=open("anchors.txt","r") 374 | anchor_txt=f.read() 375 | L=[] 376 | for i in anchor_txt.split(","): 377 | L.append(float(i)) 378 | anchor=tuple(L) 379 | f.close() 380 | f=open("lable.txt","r") 381 | lable_txt=f.read() 382 | lable = lable_txt.split(",") 383 | f.close() 384 | #设置RX、RT引脚 385 | fm.register(9, fm.fpioa.UART1_TX, force=True) 386 | fm.register(10, fm.fpioa.UART1_RX, force=True) 387 | #设置串口通信 388 | uart_A = UART(UART.UART1, 9600, 8, 1, 0, timeout=1000, read_buf_len=4096) 389 | anchor = (0.1766, 0.1793, 0.4409, 0.3797, 0.6773, 0.5954, 1.0218, 0.9527, 2.158, 1.6841) 390 | sensor.set_windowing((224, 224)) 391 | a = kpu.init_yolo2(task, 0.5, 0.3, 5, anchor) 392 | classes=["9","1","4","2","3","8","5","6","7" ] 393 | 394 | #全局变量,保存初始化识别的数字 395 | intnum = 0 396 | #初始化识别函数 397 | def begin(intnum): 398 | TF = 1 399 | #得分序列,放1-8识别的次数,每一帧识别成哪个,对应的位置加一,1-8哪个先到10即最终识别为哪个 400 | List_score01 = [0]*8 401 | while(TF): 402 | img = sensor.snapshot() 403 | #code是yolov5返回的每个矩形框的参数 404 | #例图中出现两个目标区域的时候:[{"x":9, "y":99, "w":55, "h":82, "value":0.697979, "classid":8, "index":0, "objnum":2}, {"x":137, "y":105, "w":56, "h":67, "value":0.939132, "classid":4, "index":1, "objnum":2}] 405 | code = kpu.run_yolo2(task, img) 406 | #print(code) 407 | if code: 408 | for i in code: 409 | #画目标区域矩形框 410 | a = img.draw_rectangle(i.rect()) 411 | a = lcd. display(img) 412 | list1=list(i.rect()) 413 | #print(classes[i.classid()]) 414 | #识别到的加1 415 | List_score01[int(classes[i.classid()])-1] += 1 416 | #print(List_score01) 417 | if(List_score01[0] >= 10): 418 | intnum = 1 419 | #给下位机通信传指令 420 | uart_A.write('A') 421 | print(1) 422 | #退出初始化循环 423 | TF = 0 424 | if(List_score01[1] >= 10): 425 | intnum = 2 426 | uart_A.write('B') 427 | print(2) 428 | TF = 0 429 | if(List_score01[2] >= 10): 430 | intnum = 3 431 | uart_A.write('C') 432 | print(3) 433 | TF = 0 434 | if(List_score01[3] >= 10): 435 | intnum = 4 436 | uart_A.write('D') 437 | print(4) 438 | TF = 0 439 | if(List_score01[4] >= 10): 440 | intnum = 5 441 | uart_A.write('E') 442 | print(5) 443 | TF = 0 444 | if(List_score01[5] >= 10): 445 | intnum = 6 446 | uart_A.write('F') 447 | print(6) 448 | TF = 0 449 | if(List_score01[6] >= 10): 450 | intnum = 7 451 | uart_A.write('G') 452 | #print(7) 453 | TF = 0 454 | if(List_score01[7] >= 10): 455 | intnum = 8 456 | uart_A.write('H') 457 | print(8) 458 | TF = 0 459 | else: 460 | a = lcd.display(img) 461 | return intnum 462 | 463 | 464 | #道路识别函数 465 | def then(intnum): 466 | while(1): 467 | #设置二维矩阵,存放每一个矩形框中不同数字识别的次数,一帧中最多有四个目标区域,但这里设置六个,防止有误识别的造成有很多矩形框程序暴死 468 | List_score02 = [[0]*8] * 6 469 | intnum02 = [0]*6 470 | TF = 1 471 | while(TF): 472 | img = sensor.snapshot() 473 | code = kpu.run_yolo2(task, img) 474 | if code: 475 | int_i = -1 476 | for i in code: 477 | int_i +=1 478 | a=img.draw_rectangle(i.rect()) 479 | a = lcd. display(img) 480 | list1=list(i.rect()) 481 | b=(list1[0]+list1[2])/2 482 | List_score02[int_i][int(classes[i.classid()]) -1] += 1 483 | #print(int_i) 484 | print(str(List_score02[int_i]) + str(int_i)) 485 | for ii in range(8): 486 | #int_i是第几个框,ii是第几个框中的每个数字 487 | if(List_score02[int_i][ii] == 10 and intnum == ii+1): 488 | if(b < 75): 489 | uart_A.write("L") 490 | print("L") 491 | TF = 0 492 | List_score02 = [[0]*8] * 6 493 | intnum02 = [0]*6 494 | if(b > 75): 495 | uart_A.write("R") 496 | print("R") 497 | TF = 0 498 | List_score02 = [[0]*8] * 6 499 | intnum02 = [0]*6 500 | else: 501 | a = lcd.display(img) 502 | TF = 0 503 | List_score02 = [[0]*8] * 6 504 | intnum02 = [0]*6 505 | return 0 506 | 507 | #执行程序 508 | time.sleep(3) 509 | uart_A.write("E") 510 | intnum = begin(intnum) 511 | time.sleep(3) 512 | then(intnum) 513 | uart_A.deinit() 514 | del uart_A 515 | a = kpu.deinit(task) 516 | 517 | ``` 518 | 519 | 520 | 521 | ## 2、OPENMV红线循迹 522 | 523 | 循迹测试: 524 | 525 | ![openmv结果1](https://github.com/zhaohaobingSUI/ZHB_project_diansai/blob/master/images/openmv%E7%BB%93%E6%9E%9C1.png) 526 | 527 | 测试结果: 528 | 529 | ![openmv结果2](https://github.com/zhaohaobingSUI/ZHB_project_diansai/blob/master/images/openmv%E7%BB%93%E6%9E%9C2.png) 530 | 531 | 用openmv红线循迹,这部分比较简单,就不多说了吧 532 | 533 | 用灰度传感器循迹也可以,不过调的不好容易走s形 534 | 535 | --------------------------------------------------------------------------------