2021电赛F题(智能送药小车)参赛总结【视觉部分】


前言

在2021年全国大学生电子设计竞赛中,我们小组做的是F题(智能送药小车)。我在小组中主要负责小车视觉功能的实现,所以在本篇参赛总结中只会涉及小车视觉功能实现的思路和过程。


提示:下文中所提出的实现方法不一定是最优解,如果有其他更好的建议和想法,我衷心欢迎大家与我交流,一起学习进步!

一、题目解读

完整规则太多了,这里仅引入一些与视觉部分相关的内容。

通读题目后,不难发现,能用到视觉的有两方面:

  • 小车寻迹
  • 数字(病房号)识别

二、实现思路

1. 小车寻迹

小车寻迹任务主要是寻找红色的道路中线,保证小车不偏不倚地行进且不会压黑线。我们可将整个寻迹分为两个部分:直道和路口。任务为实时告知主控板当前红色中线和小车的相对位置,以及是否到达路口。

我们决定使用openmv作为视觉传感器,openmv寻找红色中线主要是使用find_blobs()根据设定好的LAB阈值进行色块查找。但是在实际使用过程中会出现反光(赛道红线使用红色电工胶布制成),为了解决这个问题,我们将摄像头小车底盘(透明亚克力板)摄像头区域进行遮挡使之不透光。车前和车尾遮挡不完全,防止反光影响色块查找划定上图中绿色区域作为ROI(0,30,160,60),摄像头帧尺寸采用QQVGA([w:160 h:120])。如下图所示:

直道寻迹

openmv置于小车底盘中央位置,镜头方向向下,使摄像头画面中红线竖直在画面中央,宽度占比1:5左右,如下图所示。


我们将上图左边图像视为小车理想位置,当出现右边情况时,此时小车实际位置与理想位置会产生一个距离偏移角度偏移。所以,我们需要实时通过openmv得到这两个偏移量,通过串口发送给主控板以便其控制电机完成偏移量的减少。之于如何计算这两个量值,可以参看micropython官方文档Blob 类 – 色块对象中的cx(),cy(),rotation()方法。

路口寻迹

在地图上会出现的路口情况如下图所示(除去右上角那张),分别为十字路口和T形路口。

那么如何判断小车行至路口了呢?这里我们使用如下方法进行判断。

在find_blobs查找色块后,会得到返回一个包括每个色块的色块对象的列表。为了防止干扰,我们可以取最大的色块作为目标色块。得到目标色块blob包围矩形,上两图分别是直道和路口的包围矩形(绿色边框)。可以看到,直道和路口的矩形框在形状上有所区别,直道矩形h>w,路口矩形h<w,所以可以通过两个blob的角度(blob[7])进行路口和直道的判别。通过blob[7]*53-90得到我们平时认知中的红线偏离竖直中心轴的角度值,当小车在直道上行进时,这个值应当在大约在(-45,45)之间波动,遇到路口时,这个值会突变到到(-90,-70)和(70,90)之间,因此通过角度偏移量的突变感知到路口的出现。

至于是否到达路口这一点的判断逻辑是在主控板上,openmv的功能就是一直发送距离和角度的偏移量。

主要micropython函数:find_blobs()

Openmv寻迹代码

import sensor, image, time, pyb
import math
from pyb import UART# LAB threshold for red line
road_threshold = (0, 39, 21, 127, -128, 127)sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA)      # QQVGA:[w:160 h:120]
sensor.skip_frames(n = 800)
sensor.set_auto_gain(False)
sensor.set_auto_whitebal(False)
uart = UART(3, 115200)
clock = time.clock()def find_max(blobs):max_size=0for blob in blobs:if blob.pixels() > max_size:max_blob=blobmax_size = blob.pixels()return max_blobwhile (True):clock.tick()img = sensor.snapshot()blobs = img.find_blobs([road_threshold], roi = (0, 30, 160, 60), x_stride = 20, y_stride = 20, pixels_threshold = 100)if blobs:b = find_max(blobs)img.draw_rectangle(b[0:4])img.draw_line((b[5], b[6], b[5] + int(50 * math.cos(b[7])), b[6] + int(50 * math.sin(b[7]))), color = (0, 255, 0))uart.write('D%03dA%03dE' % (b[5] - 80, b[7] * 53 - 90+7)) # 这里的角度偏移值"+7"是实际测试中出现的误差的补偿量print('D%03dA%03dE' % (b[5] - 80, b[7] * 53 - 90+7))else:uart.write('D%03dA%03dE' % (999, 999))    # 没有找到中线(中线不在摄像头视野内)print('D%03dA%03dE' % (999, 999))print(clock.fps())

Tips

  • road_threshold 这个阈值是通过openmv官方IDE上的阈值选择器确定的。
  • 上面代码的相关偏移量计算是要配合主控板的判断逻辑的,不同的主控判断逻辑对openmv发送的内可能会有所差异。
  • 当然,寻迹功能也不一定要使用摄像头。很多队伍也采用灰度传感器进行寻迹,听说不仅简单,而且效果还是不错的。我们之前也是考虑做的,但是今年物资太少,现买灰度传感器时间上来不及,所以就用手头的openmv做寻迹了。

2. 数字(病房号)识别

第一次看到数字识别,想到的是K210跑yolo模型。果不其然,开赛不久,网上一老哥就做出来了。看了结果后,发现K210的缺点还是有的。首先就是摄像头视角问题,手上买的官方maix-bit套件中的摄像头视角太小,不能同时看完远端病房路口处的四个数字,得换一个广角镜头才行。然后就是误识别,容易把1看做7,5看做6,这可能是数据集太少的原因(不过后来又跑过某个兄弟的每类1000+训练图片量的目标检测模型,发现5和6还是分不清,麻了)。我这个人本来就对采集数据集和标注发怵的,而且时间缺失,K210的方案果断放弃了。
摒弃K210后,掏出了之前管学长借的一块树莓派4b,开搞!(今年组委会规则开放使用树莓派)经一番思考后,我们决定放弃训练目标检测模型,使用传统图像处理+机器学习的方案解决。摄像头我们采用一个USB免驱150度广角摄像头,树莓派系统为官方桌面版系统(2021.5.7),开发环境为python+opencv,还外接了一块7英尺显示屏(用来显示信息)。
同样,我们可将整个数字识别过程分为两个部分: 数字图像提取和分类
树莓派上位机任务如下:

  1. 获取目标病房号
  2. 发送转向指令

流程图大致如下:

数字提取

下图为手机拍摄的输入源图像,实际摄像头输入图像左右视角应当更宽。

思路为:根据颜色找到图像中的数字、标签边框和道路边界,形态学操作去掉噪点和标签边框。查找轮廓,得到轮廓包围矩形。根据矩形位置阈值滤去道路边界,最后根据数字的矩形框剪裁得到数字二值化图像。

HSV颜色阈值确定

通过下面这个脚本确定黑色数字的HSV阈值。
hsv_choose.py

import cv2
import numpy as npcap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1080)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 960)def nothing(x):passcv2.namedWindow("Tracking")
cv2.createTrackbar("LH", "Tracking", 0, 255, nothing)
cv2.createTrackbar("LS", "Tracking", 0, 255, nothing)
cv2.createTrackbar("LV", "Tracking", 0, 255, nothing)
cv2.createTrackbar("UH", "Tracking", 0, 180, nothing)
cv2.createTrackbar("US", "Tracking", 0, 255, nothing)
cv2.createTrackbar("UV", "Tracking", 0, 255, nothing)while True:# 读取图片_, frame = cap.read()# 压缩图像frame = cv2.resize(frame, (400, 400 * frame.shape[0] // frame.shape[1]))hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)l_h = cv2.getTrackbarPos("LH", "Tracking")l_s = cv2.getTrackbarPos("LS", "Tracking")l_v = cv2.getTrackbarPos("LV", "Tracking")u_h = cv2.getTrackbarPos("UH", "Tracking")u_s = cv2.getTrackbarPos("US", "Tracking")u_v = cv2.getTrackbarPos("UV", "Tracking")l_g = np.array([l_h, l_s, l_v])  # lower green valueu_g = np.array([u_h, u_s, u_v])mask = cv2.inRange(hsv, l_g, u_g)res = cv2.bitwise_and(frame, frame, mask = mask)  # src1,src2cv2.imshow("frame", frame)cv2.imshow("mask", mask)key = cv2.waitKey(1)if key == 27:  # Escbreakcv2.destroyAllWindows()

数字识别

数字识别采用SVM分类器分类方法。首先采集各类数字图片,再进行训练得到svm.dat。在主程序中只需加载SVM分类器对提取得到的二值化数字图像进行分类即可。

SVM训练部分这里就不详述了,大家可以自行百度。或者后面有时间的话,我会单独写一篇关于SVM训练的博客。

预测部分代码片段:

def predict(label_img):model = cv2.ml.SVM_load("svm.dat")  # 切记,如果设置树莓派自启动,此处需填绝对路径label_img = cv2.resize(label_img, (20, 20), interpolation = cv2.INTER_AREA)label_img = preprocess_hog([label_img])  # 方向梯度直方图# 识别resp = model.predict(label_img)[1].ravel()number = chr(resp[0])predict_result = numberreturn predict_result

树莓派Python代码

F_rasp.py

import cv2
import numpy as np
from numpy.linalg import norm
import serial
import time
import threading"""全局调试参数"""
# 摄像头
CAMERA_PATH = -1
# FRAME_WIDTH = 3000
# FRAME_HEIGHT = 960
# 模式选择
COLLECT_MODE = False  # 图片采集
DEBUG_MODE = False  # 调试
RESULT_MODE = False  # 显示结果
SERIAL_MODE = True  # 串口通信
MES_MODE = False  # GUI提示
# 串口
# SERIAL_WIN_PORT = "COM10"
SERIAL_LIN_PORT = "/dev/ttyUSB0"
# 图像阈值
LOWER_HSV = [0, 0, 0]
UPPER_HSV = [180, 255, 75]
MORPH_KERNEL_SIZE = 3  # 开闭操作核大小
MORPH_OPEN_NUM = 0  # 开操作次数
MORPH_CLOSE_NUM = 1  # 闭操作次数
CONTOUR_AREA_MAX = 3000  # 轮廓面积阈值
CONTOUR_AREA_MIN = 500
ROI_SPACES = (100, 10, 2, 2)  # 识别ROI相对画面位置:上,下,左,右# 通信参数初始化
global ser
target_number = 0  # 目标病房号码,0:无病房,1~8:1~8病房
turn_flag = 4  # 转向标志位,1:直行,2:左转,3:右转 4:无效
detect_flag = 0  # 检测标志位,0:停止检测,1:开启检测
controlFrame = "T%01d%01dE"  # %01d 对应turn_flag或者target_number
wait_target_list = []
WAIT_FRAME_NUM = 60
loop_send_flag = 1
if MES_MODE:message_text = ""
if COLLECT_MODE:count = 0# 来自opencv的sample,用于svm训练  返回直方图
def preprocess_hog(digits):samples = []for img in digits:gx = cv2.Sobel(img, cv2.CV_32F, 1, 0)gy = cv2.Sobel(img, cv2.CV_32F, 0, 1)mag, ang = cv2.cartToPolar(gx, gy)bin_n = 16_bin = np.int32(bin_n * ang / (2 * np.pi))bin_cells = _bin[:10, :10], _bin[10:, :10], _bin[:10, 10:], _bin[10:, 10:]mag_cells = mag[:10, :10], mag[10:, :10], mag[:10, 10:], mag[10:, 10:]hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]hist = np.hstack(hists)# transform to Hellinger kerneleps = 1e-7hist /= hist.sum() + epshist = np.sqrt(hist)hist /= norm(hist) + epssamples.append(hist)return np.float32(samples)def detect(srcImg):# print(srcImg.shape)if COLLECT_MODE:global countdst = srcImg.copy()if DEBUG_MODE or RESULT_MODE:w, h = dst.shape[1], dst.shape[0]cv2.rectangle(dst, (ROI_SPACES[2], ROI_SPACES[0]), (w - ROI_SPACES[3], h - ROI_SPACES[1]), (255, 0, 0))num_mask = np.zeros((srcImg.shape[0], srcImg.shape[1]), np.uint8)# if DEBUG_MODE:#     cv2.imshow("srcImg", srcImg)# 把 BGR 转为 HSVhsv = cv2.cvtColor(srcImg, cv2.COLOR_BGR2HSV)# HSV中黑色范围lower_hsv = np.array(LOWER_HSV)upper_hsv = np.array(UPPER_HSV)# 获得黑色区域的maskmask = cv2.inRange(hsv, lower_hsv, upper_hsv)mask = cv2.medianBlur(mask, 5)if DEBUG_MODE:cv2.imshow("hsv mask img", mask)# 形态学变换element = cv2.getStructuringElement(cv2.MORPH_RECT, (MORPH_KERNEL_SIZE, MORPH_KERNEL_SIZE))  # 获取核for i in range(MORPH_OPEN_NUM):mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, element)for i in range(MORPH_CLOSE_NUM):mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, element)if DEBUG_MODE:cv2.imshow("morph img", mask)# 轮廓查找contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)if DEBUG_MODE:cv2.drawContours(dst, contours, -1, (0, 0, 255))labels = []for index, contour in enumerate(contours):# print(cv2.contourArea(contour))if CONTOUR_AREA_MAX > cv2.contourArea(contour) > CONTOUR_AREA_MIN:  # 轮廓包围大小阈值if DEBUG_MODE:cv2.drawContours(dst, contours, index, (0, 255, 0))contour_poly = cv2.approxPolyDP(contour, 3, True)x, y, w, h = cv2.boundingRect(contour_poly)if w / h > 1.5:continueif srcImg.shape[1] - ROI_SPACES[3] < x + w or x < ROI_SPACES[2] or srcImg.shape[0] - ROI_SPACES[1] < y + h or y < ROI_SPACES[0]:  # 过滤道路continuenum_mask = cv2.drawContours(num_mask, contours, index, 255, -1)_mask = cv2.add(mask, np.zeros(np.shape(mask), dtype = np.uint8), mask = num_mask)if DEBUG_MODE:cv2.imshow("number mask", _mask)# 将标号向四周扩充pre_img = _mask[y:y + h, x:x + w]if h > w:pre_img = cv2.copyMakeBorder(pre_img, 5, 5, int((h + 10 - w) / 2), int((h + 10 - w) / 2),cv2.BORDER_CONSTANT,value = [0, 0, 0])else:pre_img = cv2.copyMakeBorder(pre_img, int((w + 10 - h) / 2), int((w + 10 - h) / 2), 5, 5,cv2.BORDER_CONSTANT, value = [0, 0, 0])if COLLECT_MODE:# 录制采集global countcv2.imshow("collect image", cv2.resize(pre_img, (100, 100)))cv2.waitKey(30)print("keep or abandon? enter q to abandon,enter Enter to keep.")if cv2.waitKey(0) & 0xff is not ord('q'):cv2.imwrite("./images/" + str(count) + ".jpg", pre_img)count += 1print("keep")else:print("abandon")cv2.destroyWindow("collect image")result = predict(pre_img)labels.append({"image": _mask[y:y + h, x:x + w], "rect": (x, y, w, h), "result": result})if 0 < len(labels) < 5:# 对目标进行排序,从左到右依次为0,1,2,3sorted_labels = sorted(labels, key = lambda keys: keys["rect"][0])for index, l in enumerate(sorted_labels):cv2.rectangle(dst, (l["rect"][0], l["rect"][1]), (l["rect"][0] + l["rect"][2], l["rect"][1] + l["rect"][3]),(0, 255, 0), 2)cv2.putText(dst, str(l["result"]), (l["rect"][0], l["rect"][1]), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0),4, 8)if RESULT_MODE or DEBUG_MODE:cv2.putText(dst, "TA: " + str(target_number) + "  TU: " + str(turn_flag), (50, 50),cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)cv2.imshow("detect result", cv2.resize(dst, (0, 0), fx = 1, fy = 1))if MES_MODE:cv2.moveWindow("detect result", 0, 0)return sorted_labelselse:if RESULT_MODE or DEBUG_MODE:cv2.putText(dst, "TA: " + str(target_number) + "  TU: " + str(turn_flag), (50, 50),cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)cv2.imshow("detect result", cv2.resize(dst, (0, 0), fx = 1, fy = 1))if MES_MODE:cv2.moveWindow("detect result", 0, 0)return Nonedef predict(label_img):model = cv2.ml.SVM_load("svm.dat")  # 切记,如果设置树莓派自启动,此处需填绝对路径label_img = cv2.resize(label_img, (20, 20), interpolation = cv2.INTER_AREA)label_img = preprocess_hog([label_img])  # 方向梯度直方图# 识别resp = model.predict(label_img)[1].ravel()number = chr(resp[0])predict_result = numberreturn predict_resultdef open_detect(_frame):global target_numberglobal loop_send_flagglobal message_textglobal turn_flagif target_number != 0:print("target number is: " + str(target_number))if MES_MODE:message_text += "target number is: " + str(target_number) + "\n"else:print("not get target number!")if MES_MODE:message_text += "not get target number!" + "\n"# 识别数字标签ls = detect(_frame)if ls is not None:ls_number = [_l["result"] for _l in ls]# print(ls_number)if 1 < len(ls_number) < 5:loop_send_flag = 0if len(ls_number) == 1:  # 一张标签的情况global WAIT_FRAME_NUMif len(wait_target_list) < WAIT_FRAME_NUM:print("Target_number setting...[" + str(len(wait_target_list)) + "]")if MES_MODE:message_text += "Target_number setting...[" + str(len(wait_target_list)) + "]" + "\n"wait_target_list.append(ls_number[0])else:result = {}for i in set(wait_target_list):result[i] = wait_target_list.count(i)target_number = sorted(result.items(), key = lambda item: item[1], reverse = True)[0][0]print("Target_number set finish!")if MES_MODE:message_text += "Target_number set finish!" + "\n"loop_send_flag = 1wait_target_list.clear()turn_flag = 4elif len(ls_number) == 2:  # 2张标签的情况for index, i in enumerate(ls_number):if int(i) == int(target_number) and index == 0:  # 目标病房在左边,左转turn_flag = 2breakelif int(i) == int(target_number) and index == 1:  # 目标病房在右边,右转turn_flag = 3breakelse:  # 无目标病房,直行turn_flag = 1send(turn_flag, target_number)elif len(ls_number) == 4:  # 4张标签的情况for index, i in enumerate(ls_number):if int(i) == int(target_number) and (index == 0 or index == 1):  # 目标病房在左边,左转turn_flag = 2breakelif int(i) == int(target_number) and (index == 2 or index == 3):  # 目标病房在右边,右转turn_flag = 3breakelse:  # 无目标病房,直行turn_flag = 1send(turn_flag, target_number)else:turn_flag = 4print("the number of labels is incorrect!")else:wait_target_list.clear()turn_flag = 4print("not find number label!")# 串口发送
def send(t_flag, t_number):global message_textcontent = controlFrame % (int(t_flag), int(t_number))if SERIAL_MODE:ser.write(content.encode())print("Send: " + content)if MES_MODE:message_text += "Send: " + content + "\n"def message(text):bg = np.zeros((100, 600, 3), np.uint8)  # 生成一个空灰度图像fontFace = cv2.FONT_HERSHEY_SIMPLEXfontScale = 1fontColor = (0, 255, 0)  # BGRthickness = 1lineType = 2y0, dy = 50, 25for i, txt in enumerate(text.split('\n')):y = y0 + i * dycv2.putText(bg, txt, (50, y), fontFace, fontScale, fontColor, thickness, lineType)cv2.namedWindow("F_CAR_GUI")cv2.moveWindow("F_CAR_GUI", 0, 500)cv2.imshow("F_CAR_GUI", bg)def main():# 摄像头参数初始化cap = cv2.VideoCapture(CAMERA_PATH)assert cap is not None, "未能打开目标路径下的摄像头,可尝试修改CAMERA_PATH"# cap.set(cv2.CAP_PROP_FRAME_WIDTH, FRAME_WIDTH)# cap.set(cv2.CAP_PROP_FRAME_HEIGHT, FRAME_HEIGHT)if MES_MODE:cv2.namedWindow("F_CAR_GUI")while True:global message_textmessage_text = ""print("**************FRAME_BEGIN**************")# 读取图片_, frame = cap.read()if frame is None:break# 压缩图像frame = cv2.resize(frame, (400, 400 * frame.shape[0] // frame.shape[1]))open_detect(frame)if target_number and loop_send_flag:if SERIAL_MODE:ser.write((controlFrame % (4, int(target_number))).encode())print("send: " + controlFrame % (4, int(target_number)))if MES_MODE:message_text += "send: " + controlFrame % (4, int(target_number)) + "\n"print("***************FRAME_END***************\n\n\n")if MES_MODE:message(message_text)if cv2.waitKey(30) & 0xFF == 27:breakcap.release()cv2.destroyAllWindows()if __name__ == "__main__":print("程序开始运行...")if SERIAL_MODE:ser = serial.Serial(SERIAL_LIN_PORT, 115200, timeout = 0.5)# import platform# if platform.system() == 'Windows':#     ser = serial.Serial(SERIAL_WIN_PORT, 115200, timeout = 0.5)# elif platform.system() == 'Linux':#     ser = serial.Serial(SERIAL_LIN_PORT, 115200, timeout = 0.5)main()

本方案与下位机通信逻辑如下:
树莓派开机自动运行本脚本,此时下位机并未复位。将目标病房号标签放置于摄像头下进行识别设置目标数字值,此过程可根据程序所提供的的简单UI进行。识别成功后拿走标签,上位机会持续发送无效转向值和目标数字值(因为如果目标值是1或2,需要在第一个无标签路口转向)。下位机复位后,小车开始行进,到达一定距离后(小车电机闭环控制)急刹(此时下位机接收累加器清零,开始累加接收到的转向值)开始缓慢向前行驶一小段距离,此时摄像头正好看到路口标签,开始进行标签识别,并发送有效转向值和目标数字值。小车缓慢行驶一段距离后,对接收累加器的值进行取平均,平均值作为本路口的转向参考。向前行驶到达路口,进行转向(直行也属于转向)。如果没有发送任何转向信息,小车会认为到达病房,停止行进。校车转向记录会在下位机进行压栈,返回时直接出栈就行。

Tips

  • 树莓派系统安装细节

    最好不要设置用户名和密码,防止后面设置自启动失败。

  • 树莓派开机自启动

    之前看了很多篇解决方法都不行,被坑了好久,最后成功解决的方法如下:树莓派4b——开机后自动打开终端Terminal并执行指定代码或python代码

  • 树莓派opencv_python安装

    参考博文如下:树莓派python3使用pip3安装opencv3.4
    我是使用文中第三种方法安装的,并且安装的是opencv_python-3.4.3.18!!!之前不指定版本,直接pip默认下载最新的4.x,总是安装不上。但是安装opencv_python-3.4.3.18后,上面的代码需要进行修改,contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)需要更改为_, contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE),这是opencv版本差异所致的。

总结

这次电赛最终没能取得十分理想的成绩,主要是我背锅。电赛四天三夜里我个人有效参赛时间可能就两天,因为比赛和专业结课考试冲突了,导致来回在相隔十多公里的新老区之间往返(比赛场地在新区,考试在老区。)也耽误了不少时间。主要还是没熬夜,不敢熬。就挺对不起队友的,唉~总之,就是很遗憾,明明可以做得更好。
最后的最后,如果上面文章中有任何叙述不详和不妥之处,欢迎大家和我交流,一起进步~

2021电赛F题(智能送药小车)参赛总结【视觉部分】相关推荐

  1. 2021电赛F题-智能送药小车-国一

    2021电赛F题-智能送药小车-国一 B站视频链接:https://www.bilibili.com/video/BV1u44y1e7qk/ (这大概是b站第一个双车视频吧,嘿嘿

  2. 2021电赛F题智能送药小车方案分析(openMV数字识别,红线循迹,STM32HAL库freeRTOS,串级PID快速学习,小车自动返回)

    2021全国大学生电子设计竞赛F题智能送药小车 前提:本篇文章重在分享自己的心得与感悟,我们把最重要的部分,摄像头循迹,摄像头数字识别问题都解决了,有两种方案一种是openARTmini摄像头进行数字 ...

  3. 2021电赛国一智能送药小车(F题)设计报告

    2021电赛国一智能送药小车(F题)设计报告 [写在前面的话] 电赛是一个很奇妙的过程,可能有些人觉得电赛的门槛太高,那便意味着,当你决定要参加电赛的那一刻起,这一段路.这些日子就注定不会太轻松: 我 ...

  4. 2021年全国大学生电子设计大赛F题——智能送药小车,全方位解决方案+程序代码(详细注释)山东赛区国奖

    目录 1.赛题及硬件方案分析: 2.用到的主要器件清单: 3.各部分思路及代码实现 (1).小车舵机.马达驱动 (2).蓝牙通信 (3).单片机与OpenMV的串口通信 (4).单片机与OpenMV的 ...

  5. 2021电赛F题视觉教程+代码免费开源

    2021电赛F题视觉教程+代码免费开源 最近好多要电赛题的源码,其他csdn营销号下载都需要会员或钱,正好最近课设又要做一遍电赛小车题,哥们先把代码开源了,饿死营销号 电赛宝藏链接: 四天三夜,那布满 ...

  6. 2021电赛F题之openmv巡线(附代码)

    效果展示: 出错解决方法 openmv数字识别源代码–gitee 通过使用不同阈值的方法可以得到当前区域中什么区域有红线,对于电控而言作用类似于红外对管,之后电控通过逻辑判断如何运动,这就是我们队伍目 ...

  7. 2021电赛F题之openmv数字识别--更新(附带视频与代码)

    成果展示 常见出错解决方法 openmv数字识别源代码-gitee 效果如上 openmv的u盘里需要 思路: 模板匹配很简单,只不过使用起来需要自己拍摄大量的模板,同时如果模板数量较多那么就会造成严 ...

  8. (不眠者①队)国电-F题:智能送药小车,广东赛区一等奖,推国赛,开源(代码+设计方案)

    国电 -- F题: 智能送药小车,所有源码暂时开源 目录 国电 -- F题: 智能送药小车,所有源码暂时开源 前言 一.题目 二.硬件部分 材料清单: 材料实物图: 设计方案: 三.代码部分 数字识别 ...

  9. 做个全国一等奖的小车,其实不难(F题:智能送药小车方案分享)

    01  前 言 大家好,我是张巧龙,今天给大家带来关于21年F题的分享:智能送药小车,出了这个题目之后,咋一看,好像比较简单. 不过大家慢慢做,越往后做越发现,坑越来越多. 第一个问题:数字识别率不高 ...

最新文章

  1. 【万级并发】电商库存扣减如何设计?不超卖!
  2. XCode编译运行出错解决思路,以及再次推荐AppCode
  3. JPEG压缩原理与DCT离散余弦变换 量化
  4. Android控件系列之RadioButtonRadioGroup
  5. python目录大纲
  6. 共识机制-权益证明 PoS
  7. android控件属性文档,1.Android控件属性收集
  8. 怎么判断当前的os类型,手机类型
  9. IIS内部服务错误aspx与asp
  10. ajax提交输入内容,当输入用于提交时,AJAX表单提交
  11. 对象序列化Java中的序列化
  12. 【软工3】迭代二 心得体会及感想
  13. MYSQL select ....outfile.....from.....
  14. 【技术教程】如何使用OBS推流到EasyDSS平台实现同屏播放?
  15. idea 关闭检查更新_intellij idea怎么关闭自动更新
  16. ONOS 南向抽象层分析
  17. DBSCAN 对点云障碍物聚类
  18. GDB 的几个用法(until, finish, tui)
  19. 如何获取天猫/淘宝商品历史价格信息
  20. 洛谷 P1129 矩阵游戏

热门文章

  1. 世界通用认证-CB体系问题合集
  2. 2023 极术通讯- ChatGPT掀AI热潮,AI芯片需求暴增
  3. 基于SadTalker的AI主播,Stable Diffusion也可用
  4. 根据批量地址名称在高德地图进行定位踩过的坑
  5. lopatkin俄大神精简中文系统Windows 10 Enterprise 2016 LTSB 14393.729 x64 ZH-CN PIPvm v2
  6. 《炬丰科技-半导体工艺》臭氧的新型光刻胶剥离技术
  7. 老师和学生案例,加入抽烟的额外功能
  8. 美元霸权的潜在风险——无锚货币,为什么都要刺激消费
  9. 【Python爬虫实战案例】采集城市桌游商家数据信息,做可视化演示
  10. 随时间反向传播算法(BPTT)笔记