Skip to main content

手动控制逻辑代码

智能小车控制逻辑入口

“main.py”是小车控制代码的主入口,定义了小车控制模式及对应模式的后续调用实现。

首先导入一系列与参数有关的模块,以及启动摄像头的关键模块。

import os
from argparse import ArgumentParser
from multiprocessing import Process, Queue
from src.scenes import Manual, scene_initiator
from src.utils import getkey, log, CameraBroadcaster, CAMERA_INFO

定义出可选参数更改小车的控制模式,可选命令行cmd控制,手动控制,声音控制(正在规划中)以及测试等。

def parse_args():
parser = ArgumentParser()
parser.add_argument('--mode', type=str, required=False, default='manual',
choices=['cmd', 'voice', 'manual', 'test'])
return parser.parse_args()

遵循简单性的原则,在程序的主入口需判断模式的设定后,进入到对应模式的实现中,包含摄像头端口的调用及行驶过程中各类消息队列的处理。

if __name__ == '__main__':
args = parse_args()
log.info('start')
msg_queue = Queue(maxsize=1)
camera = CameraBroadcaster(CAMERA_INFO)
shared_memory_name = camera.memory_name
camera_process = Process(target=camera.run)
camera_process.start()
if args.mode == 'manual':
task = Manual(shared_memory_name, CAMERA_INFO, msg_queue)
process = Process(target=task.loop)
process.start()
try:
while True:
key = getkey()
if key == 'esc':
process.join()
camera.stop_sign.value = True
camera_process.join()
break
else:
msg_queue.put(key)
except (KeyboardInterrupt, SystemExit):
camera.stop_sign.value = True
camera_process.join()
os.system('stty sane')
log.info('stopping.')
elif args.mode == 'cmd':
process_list = []
record_map = {}
try:
log.info(f'start reading cmd')
while True:
command = input().strip()
if command == 'stop':
for p in process_list:
p.kill()
log.info(f'start put stop sign')
camera.stop_sign.value = True
camera_process.join()
break
elif command == 'clear':
for p in process_list:
p.kill()
process_list.clear()
log.info(f'clear succ')
continue
elif command == 'Manual':
log.error(f'Does not support switching from cmd mode to manual mode')
continue
log.info(f'building scene {command}')
scene = scene_initiator(command)
log.info(f'{scene}')
if scene is not None:
scene_obj = scene(shared_memory_name, CAMERA_INFO, msg_queue)
process = Process(target=scene_obj.loop)
process.start()
process_list.append(process)

except (KeyboardInterrupt, SystemExit):
camera.stop_sign.value = True
camera_process.join()
for process in process_list:
process.kill()
log.info('stopping.')

elif args.mode == 'voice':
raise NotImplementedError('voice control is not currently supported.')
elif args.mode == 'test':
test_list = []
test1 = Manual(shared_memory_name, CAMERA_INFO, msg_queue)
test_list.append(Process(target=test1.loop))
test2 = scene_initiator('Tracking')(shared_memory_name, CAMERA_INFO, msg_queue)
test_list.append(Process(target=test2.loop))
test3 = scene_initiator('Tracking')(shared_memory_name, CAMERA_INFO, msg_queue)
test_list.append(Process(target=test3.loop))

for process in test_list:
process.start()
try:
while True:
key = getkey()
if key == 'esc':
for process in test_list:
process.kill()
camera.stop_sign.value = True
camera_process.join()
break
else:
msg_queue.put(key)
except (KeyboardInterrupt, SystemExit):
camera.stop_sign.value = True
camera_process.join()
os.system('stty sane')
log.info('stopping.')

父主题: 代码实现

智能小车运动控制代码

“python/src/actions”文件夹中包含的是与烧录到ESP32控制端对应的小车基础和复杂运动的python代码文件以及其中的函数说明

  1. 小车动作函数定义。

    import time
    from abc import ABC, abstractmethod


    class BaseAction(ABC):
    """
    基础动作的基类,所有基本动作均继承于该类
    """

    def __init__(self, *args, **kwds) -> None:
    """
    基础动作类的初始化方法,通过args与kwds控制输入参数
    :param args:
    :param kwds:
    """
    # 抽象的速度信息
    self.speed = kwds.get('speed', -1)
    # 电机角度
    self.servo_angle = kwds.get('servo', [-1, -1])

    # 根据电机的实际情况修改下发到电机的速度
    self.motor_rating = [1.45, 1, 1, 1]

    # 确定是否需要在运行时根据前动作更新电机角度及电机速度
    self.update_speed = False
    self.update_servo = False

    if self.speed == -1:
    self.update_speed = True

    if self.servo_angle[0] == -1 and self.servo_angle[1] == -1:
    self.update_servo = True

    # 由速度生成方法将抽象的总体速度计算为4个电机的速度并输出为list
    self.speed_setting = self.generate_speed_setting(self.speed)
    self.fix_speed()

    def fix_speed(self):
    self.speed_setting = [int(speed * ratio) for speed, ratio in zip(self.speed_setting, self.motor_rating)]

    @staticmethod
    @abstractmethod
    def generate_speed_setting(speed, degree=0):
    """
    生成4个电机的速度,并输出为列表
    抽象类,需要根据具体情况进行设置
    :param speed: 抽象的速度。 当前动作初始化时设置 或 控制器根据前一动作速度进行设置
    :param degree: 如需转弯,速度计算需要的角度信息
    :return:
    """
    pass

    def __call__(self, speed, servo_angle):
    """
    call魔法函数,两个输入参数由控制器输入
    当init方法设置了相关信息,则忽略控制器输入的参数
    当init方法没有设置相关信息,相关信息的将由控制器输入的参数进行更新

    :param speed: 抽象速度
    :param servo_angle: 舵机的角度
    :return: 长度为6的列表,前4位为4个电机的速度,后2位为舵机的两个角度
    """
    if self.update_servo:
    self.servo_angle = servo_angle
    if self.update_speed:
    degree = 0
    if hasattr(self, 'degree'):
    degree = self.degree
    self.speed_setting = self.generate_speed_setting(speed, degree)
    self.fix_speed()

    return self.speed_setting + self.servo_angle


    class Advance(BaseAction):
    """
    小车前进
    """

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return [-speed, -speed, speed, speed]


    class BackUp(BaseAction):
    """
    小车后退
    """

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return [speed, speed, -speed, -speed]


    class CustomAction(BaseAction):
    """
    自定义动作
    """

    def __init__(self, *args, **kwds):
    super().__init__(*args, **kwds)
    self.speed_setting = kwds.get('motor_setting', [0, 0, 0, 0])
    self.update_controller_speed = False
    self.update_speed = False

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return [0, 0, 0, 0]


    class Stop(BaseAction):
    """
    小车停止
    """

    def __init__(self, *args, **kwds):
    super().__init__(*args, **kwds)
    self.speed = 0

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return [0, 0, 0, 0]


    class TurnLeft(BaseAction):
    """
    小车左转
    """

    def __init__(self, *args, **kwds):
    super().__init__(*args, **kwds)
    self.degree = kwds.get('degree', 0)
    self.speed_setting = self.generate_speed_setting(speed=self.speed, degree=self.degree)

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return [-speed, -speed, int(speed * (1 + degree)), int(speed * (1 + degree))]


    class TurnRight(BaseAction):
    """
    小车右转
    """

    def __init__(self, *args, **kwds):
    super().__init__(*args, **kwds)
    self.degree = kwds.get('degree', 0)
    self.speed_setting = self.generate_speed_setting(speed=self.speed, degree=self.degree)

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return [-int(speed * (1 + degree)), -int(speed * (1 + degree)), speed, speed]


    class ShiftLeft(BaseAction):
    """
    向左平移
    """

    def __init__(self, *args, **kwds):
    super().__init__(*args, **kwds)
    self.motor_rating = [1.5, 1.3, 1.25, 1.25]
    self.fix_speed()

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return [speed, -speed, -speed, speed]


    class ShiftRight(BaseAction):
    """
    向右平移
    """

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return [-speed, speed, speed, -speed]


    class LeftOblique(BaseAction):
    """
    斜向左前方
    """

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return [0, -speed, 0, speed]


    class RightOblique(BaseAction):
    """
    斜向右前方
    """

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return [-speed, 0, speed, 0]


    class SpinClockwise(BaseAction):
    """
    顺时针旋转
    """

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return [-speed] * 4


    class SpinAntiClockwise(BaseAction):
    """
    逆时针旋转
    """

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return [speed] * 4


    class SetServo(BaseAction):
    """
    舵机转动
    """

    def __init__(self, *args, **kwds):
    super().__init__(*args, **kwds)
    self.speed = 0

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return [0, 0, 0, 0]


    class Sleep(BaseAction):
    """
    Sleep(1)等同于time.sleep(1)
    可加入至动作序列进行使用
    """

    def __init__(self, *args, **kwds):
    super().__init__(*args, **kwds)
    self.sleep_time = args[0]

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return []

    def __call__(self, speed, servo_angle):
    time.sleep(self.sleep_time)
    return None
  2. 以逆时针旋转的操作为例,方法首先继承于运动的基类,实现运动动作的方式是修改不同位置电机的旋转方向和转速。

    class SpinAntiClockwise(BaseAction):
    """
    逆时针旋转
    """

    @staticmethod
    def generate_speed_setting(speed, degree=0):
    return [speed] * 4
  3. 复杂动作则以掉头行驶为例,是多种不同的简单动作的组合,首先要导入所有的简单动作。

    from abc import ABC

    from src.actions.base_action import Advance, Sleep, SpinAntiClockwise, Stop, SpinClockwise, CustomAction
  4. 再利用简单动作形成复杂行驶动作。

    class TurnAround(ComplexAction):
    def __init__(self):
    super().__init__()
    self.action_seq = [
    Stop(),
    Sleep(0.5),
    Advance(speed=30),
    Sleep(0.35),
    Stop(),
    Sleep(0.3),
    SpinAntiClockwise(speed=50),
    Sleep(0.55),
    Advance(speed=30),
    Sleep(1.2),
    SpinAntiClockwise(speed=50),
    Sleep(0.55),
    Stop(),
    # Advance(speed=35),
    ]

父主题: 代码实现

手动控制逻辑代码

“python/src/scenes/manual.py”为手动控制小车的核心部分。

  1. 首先导入所有必须模块和基础运动模块。

    import datetime
    import os
    import cv2
    import numpy as np
    from src.actions import Advance, Stop, SetServo, TurnLeft, TurnRight, SpinClockwise, SpinAntiClockwise, BackUp, \
    ShiftLeft, ShiftRight, CustomAction
    from src.actions.complex_actions import ComplexAction, TurnAround
    from src.scenes.base_scene import BaseScene
    from src.utils import log
  2. 基于场景的基类构建手动控制小车场景的手动类,初始化后,进入到loop循环的函数中,不断等待键盘输入的键值,再执行键值对应的指令

    def loop(self):
    ret = self.init_state() # 执行初始化
    if ret:
    log.error(f'{self.__class__.__name__} init failed.')
    return
    frame = np.ndarray((self.height, self.width, 3), dtype=np.uint8, buffer=self.broadcaster.buf) # 拉取共享内存中的图片
    log.info(f'{self.__class__.__name__} loop start')
    last_action = SetServo(servo=[90, 65]) # 设置舵机角度

    while True:
    try:
    if not self.msg_queue.empty():
    key = self.msg_queue.get()
    else:
    continue
    except KeyboardInterrupt:
    self.ctrl.execute(Stop()) #捕获SIGINT之后停止小车
    break

    degree = 0
    if key == 'up':
    self.speed = min(self.speed + 1, 60) #加速
    elif key == 'down':
    self.speed = max(self.speed - 1, 25) #减速
    elif key == 'left':
    last_action = ShiftLeft() #左平移
    elif key == 'right':
    last_action = ShiftRight() #右平移
    elif key == 'w':
    last_action = Advance() #前进
    elif key == 'a':
    last_action = TurnLeft() #左转
    degree = 1.1
    elif key == 's':
    last_action = BackUp() #后退
    elif key == 'd':
    last_action = TurnRight() #右转
    degree = 1.1
    elif key == 'q':
    last_action = SpinAntiClockwise() #逆时针旋转
    elif key == 'e':
    last_action = SpinClockwise() #顺时针旋转
    elif key == 'space':
    last_action = Stop() #停车
    elif key == 'esc':
    self.ctrl.execute(Stop()) #退出循环前停下小车
    break
    elif key == 'c':
    save_img = frame.copy()
    cv2.imwrite(os.path.join(self.save_dir, f'{datetime.datetime.now()}.jpg'), save_img) #保存当前摄像头中的画面
    log.info(f'image saved.')
    elif key == 't':
    last_action = CustomAction(motor_setting=[-62, 50, 50, -50])
    elif key == 'r':
    last_action = CustomAction(motor_setting=[55, -50, -50, 50])
    elif key == 'z':
    last_action = TurnAround() #掉头
    else:
    continue

    if not isinstance(last_action, ComplexAction) and not isinstance(last_action, CustomAction):
    last_action.update_speed = False
    last_action.speed_setting = last_action.generate_speed_setting(speed=self.speed, degree=degree)
    last_action.fix_speed()
    self.ctrl.execute(last_action)

父主题: 代码实现

目标检测模型代码

“python/src/models/yolov5.py”为yolov5模型的定义代码,为小车的基础运行提供核心的智能目标识别与检测功能。

  1. 示例代码定义了如何重塑图片的尺寸,并计算需要零值填充大小的功能。

    def letterbox(img, new_shape=(640, 640), color=(114, 114, 114), auto=False, scaleFill=False, scaleup=True):
    # Resize image to a 32-pixel-multiple rectangle https://github.com/ultralytics/yolov3/issues/232
    shape = img.shape[:2] # current shape [height, width]
    if isinstance(new_shape, int):
    new_shape = (new_shape, new_shape)

    # Scale ratio (new / old)
    r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
    if not scaleup: # only scale down, do not scale up (for better test mAP)
    r = min(r, 1.0)

    # Compute padding
    ratio = r, r # width, height ratios
    new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
    dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
    if auto: # minimum rectangle
    dw, dh = np.mod(dw, 64), np.mod(dh, 64) # wh padding
    elif scaleFill: # stretch
    dw, dh = 0.0, 0.0
    new_unpad = (new_shape[1], new_shape[0])
    ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios

    dw /= 2 # divide padding into 2 sides
    dh /= 2

    if shape[::-1] != new_unpad: # resize
    img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR)
    top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
    left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
    img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
    return img, ratio, (dw, dh)
  2. Yolov5的模型定义,以及推理的实现过程,形成最终的推理结果目标框和对应的类别名称。

    class YoloV5(Model):
    def __init__(self, model_path, acl_init=True):
    super().__init__(model_path, acl_init)
    self.neth = 640
    self.netw = 640
    self.conf_threshold = 0.1
    dic = {0: 'left',
    1: 'right',
    2: 'stop',
    3: 'turnaround'}
    self.names = ['person', 'sports_ball', 'bicycle', 'motorcycle', 'car', 'bus', 'truck'] * 12
    self.object_list = ['person', 'sports_ball', 'bicycle', 'motorcycle', 'car', 'bus', 'truck']
    self.names = list(dic.values())
    self.object_list = list(dic.values())

    def infer(self, img_bgr):
    imgh, imgw = img_bgr.shape[0], img_bgr.shape[1]
    imginfo = np.array([self.neth, self.netw, imgh, imgw], dtype=np.float16)
    img_padding = letterbox(img_bgr, new_shape=(self.neth, self.netw))[0] # padding resize bgr

    img = []

    img.append(img_padding)
    img = np.stack(img, axis=0)
    img = img[..., ::-1].transpose(0, 3, 1, 2) # BGR tp RGB
    image_np = np.array(img, dtype=np.float32)
    image_np_expanded = image_np / 255.0
    img = np.ascontiguousarray(image_np_expanded).astype(np.float16) #将tensor的内存连续排列
    result = self.execute([img, imginfo]) #调用推理接口
    batch_boxout, boxnum = result

    pred_boxes = []
    idx = 0
    num_det = int(boxnum[idx][0])
    bbox = batch_boxout[idx][:num_det * 6].reshape(6, -1).transpose().astype(np.float32) # 6xN -> Nx6

    for idx, class_id in enumerate(bbox[:, 5]):
    obj_name = self.names[int(bbox[idx][5])]
    if not obj_name in self.object_list:
    continue
    confidence = bbox[idx][4]
    if float(confidence) < self.conf_threshold:
    continue
    x1 = int(bbox[idx][0])
    y1 = int(bbox[idx][1])
    x2 = int(bbox[idx][2])
    y2 = int(bbox[idx][3])

    pred_boxes.append([x1, y1, x2, y2, obj_name, confidence]) #获取推理结果

    return pred_boxes

父主题: 代码实现

目标追踪逻辑代码

在实现目标检测的前提下,结合小车的基础控制部分,将小车的速度调整依赖到目标检测的推理结果上,就能实现目标追踪。

  1. “python/src/scenes/tracking.py”为目标追踪的核心代码,示例代码定义追踪的运行逻辑。

    class Tracking(BaseScene):
    def __init__(self, memory_name, camera_info, msg_queue):
    super().__init__(memory_name, camera_info, msg_queue)
    self.model = None

    def init_state(self):
    log.info(f'start init &#123;self.__class__.__name__&#125;')
    model_path = os.path.join(os.getcwd(), 'weights', 'tracking.om')
    if not os.path.exists(model_path):
    log.error(f'Cannot find the offline inference model(.om) file needed for &#123;self.__class__.__name__&#125; scene.')
    return True
    self.model = YoloV5(model_path) #加载模型
    log.info(f'&#123;self.__class__.__name__&#125; model init succ.')
    self.ctrl.execute(SetServo(servo=[90, 65])) #设置舵机角度
    return False

    def loop(self):
    ret = self.init_state() #执行初始化
    if ret:
    log.error(f'&#123;self.__class__.__name__&#125; init failed.')
    return
    frame = np.ndarray((self.height, self.width, 3), dtype=np.uint8, buffer=self.broadcaster.buf) #获取共享内存中的图片
    log.info(f'&#123;self.__class__.__name__&#125; loop start')
    last_action = None
    last_not_seen = True
    forward_speed_slow = 30
    forward_speed_fast = 40
    while True:
    action = None
    if self.stop_sign.value:
    break
    if self.pause_sign.value:
    continue

    img_bgr = frame.copy()
    bboxes = self.model.infer(img_bgr)
    log.info(f'&#123;bboxes&#125;')
    if not bboxes:
    if last_not_seen:
    action = Stop()
    else:
    last_not_seen = True
    continue
    else:
    if len(bboxes) > 1:
    ori_box = sorted(bboxes, key=lambda x: x[-1], reverse=True)[0][:4]
    else:
    ori_box = bboxes[0][:4]
    x1, y1, x2, y2 = ori_box
    x, y = (x1 + x2) // 2, (y1 + y2) // 2 #计算目标中心点的x与y坐标
    h, w = y2 - y1, x2 - x1 #计算目标的宽高

    if h * w < 141 * 128 or y < 110: #进行距离判断,如果过远就加速,否则减速
    speed = forward_speed_fast
    else:
    speed = forward_speed_slow

    if x < 400:
    action = TurnLeft(degree=1.1, speed=speed) #左转
    elif x > 1000:
    action = TurnRight(degree=1.1, speed=speed) #右转
    else:
    action = Advance(speed=speed) #直行

    if h * w > 800 * 500 or y > 390: #如果距离过近则停车
    action = Stop()

    if action is None or action == last_action:
    continue
    self.ctrl.execute(action)
    last_action = action
  2. 初始化并导入Yolov5目标检测模型。

    def __init__(self, memory_name, camera_info, msg_queue):
    super().__init__(memory_name, camera_info, msg_queue)
    self.model = None

    def init_state(self):
    log.info(f'start init &#123;self.__class__.__name__&#125;')
    model_path = os.path.join(os.getcwd(), 'weights', 'tracking.om')
    if not os.path.exists(model_path):
    log.error(f'Cannot find the offline inference model(.om) file needed for &#123;self.__class__.__name__&#125; scene.')
    return True
    self.model = YoloV5(model_path)
    log.info(f'&#123;self.__class__.__name__&#125; model init succ.')
    self.ctrl.execute(SetServo(servo=[90, 65]))
    return False
  3. 在得到正确导入结果后,开启循环,不断获取推理结果,并根据结果估算智能小车和追踪目标之间的距离,再根据计算出的结果下达不同的运动指令,设置慢速和快速跟进的两个速度。

    def loop(self):
    ret = self.init_state()
    if ret:
    log.error(f'&#123;self.__class__.__name__&#125; init failed.')
    return
    frame = np.ndarray((self.height, self.width, 3), dtype=np.uint8, buffer=self.broadcaster.buf)
    log.info(f'&#123;self.__class__.__name__&#125; loop start')
    last_action = None
    last_not_seen = True
    forward_speed_slow = 30
    forward_speed_fast = 40
  4. 获取推理结果的外接框。

    bboxes = self.model.infer(img_bgr)
  5. 计算出目标框的中心点的位置和目标框的宽高大小。

    log.info(f'&#123;bboxes&#125;')
    if not bboxes:
    if last_not_seen:
    action = Stop()
    else:
    last_not_seen = True
    continue
    else:
    if len(bboxes) > 1:
    ori_box = sorted(bboxes, key=lambda x: x[-1], reverse=True)[0][:4]
    else:
    ori_box = bboxes[0][:4]
    x1, y1, x2, y2 = ori_box
    x, y = (x1 + x2) // 2, (y1 + y2) // 2
    h, w = y2 - y1, x2 - x1

    根据计算出的目标框的大小来判断小车和目标之间的距离,再调整小车的行进速度。根据目标近大远小的简单规则,存在两个判断条件,如果目标框的面积小于一定值,就说明小车与目标距离较远,需要快速接近目标,另外如果识别框的中心点的纵坐标大于0,也就是在摄像头视角里的上半部分,也说明小车距离目标较远,也需快速接近目标,反之亦然。

    if h * w < 141 * 128 or y < 110:
    speed = forward_speed_fast
    else:
    speed = forward_speed_slow
  6. 另外如果前方目标在小车的偏左或偏右的位置,也可以采用同样的判断方法,即判断目标框的中心点的横坐标落在小车摄像头视角画面中的左侧还是右侧,进而下发对应的微调转向的命令,实现跟踪目标的方向调整。

    if x < 400:
    action = TurnLeft(degree=1.1, speed=speed)
    elif x > 1000:
    action = TurnRight(degree=1.1, speed=speed)
    else:
    action = Advance(speed=speed)

    if h * w > 800 * 500 or y > 390:
    action = Stop()

父主题: 代码实现

训练目标追踪功能模型

当前目标追踪功能,需要单独使用模型适配工具训练(模型适配工具的安装与使用请参见《[使用模型适配工具生成推理应用](https://www.hiascend.com/document/detail/zh/Atlas200IDKA2DeveloperKit/23.0.RC2/Getting Started with Application Development/iaqd/iaqd_0001.html)》),用户可参见本节,进行模型的训练获得对应模型文件。

  1. 收集待标记的png、jpg、JPEG、bmp、webp格式图片数据,推荐使用jpg格式。图片分辨率不高于1080P,单张图片不小于1MB,推荐使用小车上的摄像头进行图片收集,数量为200张以上且各角度均包含,并放置在全英文路径下。

    注:图片名称不要带字符"."。

  2. 为模型迁移准备数据集,进行图像标注,在模型适配工具界面选择“检测模型”。

    1. 单击“打开目录”选择1收集的数据集目录进行标注。

    2. 单击

      按钮,使用矩形框包围目标后单击鼠标左键,弹出添加标签界面,如图1所示。填写对应目标分类标签与Group ID号,当一个图片中有多个目标时需填写不同的ID号,单击“确定”完成标注。

      图1 添加标签

      图2 标注结果

    3. 若标记错误可单击

      按钮,按住左键可以移动标记框,移动鼠标至矩形框并单击“鼠标右键”,对矩形标签进行修改。

      图3 修改标签

    4. 当前图片标注完成后,单击图片上方菜单栏中

      图标或在左侧文件列表选择下一张图片进行标记,直到完成所有图片的标注任务。

    :::note 说明

    • 标注时输入标签仅支持数字、字母、下划线。
    • 数据集图片要从实际模型部署使用的环境获得。
    • 需将图片中的所有待检测目标都标注出来,漏标注将影响模型精度。
    • 边框需要紧密框住每个目标,且类别正确,标注无误。 :::

模型迁移

  1. 在工具界面单击下方“一键迁移”按钮,进入配置界面,输入迁移信息,单击“一键迁移”开始迁移。

    图4 模型一键迁移配置界面

    • 数据集路径:2输出的自定义数据集输出路径。
    • 数据集拆分:将图片划分成训练、验证以及测试集的比例,推荐值:0.3。默认拆分0.1的测试集用于边缘推理,训练集与验证集按输入拆分比例再次进行拆分。
    • 迭代次数:训练轮次,推荐值:100。
    • 每批图片数:参与每个批次训练的图片张数,推荐值:12。
    • 预训练模型:可选yolov5s,yolov5n,yolov5l,yolov5x,默认yolov5s。
    • 输出目录:模型输出路径。
    • 使用早停策略:勾选后,可根据设置的mAP值(均值平均精度,一般指图片内所有类别的AP的平均值)和持续迭代不上升次数,提前停止训练。
      • mAP达到(值):该训练模型精度已达标,可停止训练的阈值,默认值:0.99。
      • mAP连续迭代不上升次数:mAP值达到某一水平,多次迭代后并无提升的次数,默认值:10。
  2. 迁移完成后会出现提示框,提示已生成打包好的文件,如图5所示。在训练输出目录会生成以下文件与目录,如图6所示。

    • train_output:训练输出的权重文件、onnx文件以及训练数据信息json文件。
    • trans_output:经过数据转换,根据数据集拆分设置生成的测试集、验证集、训练集。
    • infer_project.tar.gz:打包好的推理相关模型文件与脚本。

    图5 迁移完成

    图6 输出文件

在线提单