dirty

个性签名:

保持热爱

  • 2025-03-23
  • 发表了主题帖: 【嘉楠K230开发板】目标跟踪

    本帖最后由 dirty 于 2025-3-23 12:35 编辑       本篇讲述K230实现目标跟踪功能。 一.原理流程       通过CanMV K230 AI视觉框架开发,使用CanMV K230的文件系统里的目标跟踪模型,实现采集特定目标并追踪该物体功能。使用到的模型有跟踪模板模型cropped_test127.kmodel、跟踪实时模型nanotrack_backbone_sim.kmodel、跟踪模型nanotracker_head_calib_k230.kmodel。编程思路流程如下: 二.代码实现与测验 1.自定义跟踪模版任务类,里面定义了类初始化、配置预处理操作、重写预处理函数、自定义后处理、计算padding和crop参数、重写deinit # 自定义跟踪模版任务类 class TrackCropApp(AIBase): def __init__(self,kmodel_path,model_input_size,ratio_src_crop,center_xy_wh,rgb888p_size=[1280,720],display_size=[1920,1080],debug_mode=0): super().__init__(kmodel_path,model_input_size,rgb888p_size,debug_mode) # kmodel路径 self.kmodel_path=kmodel_path # 跟踪模板输入分辨率 self.model_input_size=model_input_size # sensor给到AI的图像分辨率,宽16字节对齐 self.rgb888p_size=[ALIGN_UP(rgb888p_size[0],16),rgb888p_size[1]] # 视频输出VO分辨率,宽16字节对齐 self.display_size=[ALIGN_UP(display_size[0],16),display_size[1]] # debug模式 self.debug_mode=debug_mode # 跟踪框宽、高调整系数 self.CONTEXT_AMOUNT = 0.5 #src模型和crop模型输入比值 self.ratio_src_crop = ratio_src_crop self.center_xy_wh=center_xy_wh # padding和crop参数 self.pad_crop_params=[] # 注意:ai2d设置多个预处理时执行的顺序为:crop->shift->resize/affine->pad,如果不符合该顺序,需要配置多个ai2d对象; # 如下模型预处理要先做resize+padding再做resize+crop,因此要配置两个Ai2d对象 self.ai2d_pad=Ai2d(debug_mode) self.ai2d_pad.set_ai2d_dtype(nn.ai2d_format.NCHW_FMT,nn.ai2d_format.NCHW_FMT,np.uint8, np.uint8) self.ai2d_crop=Ai2d(debug_mode) self.ai2d_crop.set_ai2d_dtype(nn.ai2d_format.NCHW_FMT,nn.ai2d_format.NCHW_FMT,np.uint8, np.uint8) self.need_pad=False # 配置预处理操作,这里使用了crop、pad和resize,Ai2d支持crop/shift/pad/resize/affine,具体代码请打开/sdcard/libs/AI2D.py查看 def config_preprocess(self,input_image_size=None): with ScopedTiming("set preprocess config",self.debug_mode > 0): # 初始化ai2d预处理配置,默认为sensor给到AI的尺寸,可以通过设置input_image_size自行修改输入尺寸 ai2d_input_size = input_image_size if input_image_size else self.rgb888p_size # 计算padding参数并应用pad操作,以确保输入图像尺寸与模型输入尺寸匹配 self.pad_crop_params= self.get_padding_crop_param() # 如果需要padding,配置padding部分,否则只走crop if (self.pad_crop_params[0] != 0 or self.pad_crop_params[1] != 0 or self.pad_crop_params[2] != 0 or self.pad_crop_params[3] != 0): self.need_pad=True self.ai2d_pad.resize(nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel) self.ai2d_pad.pad([0, 0, 0, 0, self.pad_crop_params[0], self.pad_crop_params[1], self.pad_crop_params[2], self.pad_crop_params[3]], 0, [114, 114, 114]) output_size=[self.rgb888p_size[0]+self.pad_crop_params[2]+self.pad_crop_params[3],self.rgb888p_size[1]+self.pad_crop_params[0]+self.pad_crop_params[1]] self.ai2d_pad.build([1,3,ai2d_input_size[1],ai2d_input_size[0]],[1,3,output_size[1],output_size[0]]) self.ai2d_crop.resize(nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel) self.ai2d_crop.crop(int(self.pad_crop_params[4]),int(self.pad_crop_params[6]),int(self.pad_crop_params[5]-self.pad_crop_params[4]+1),int(self.pad_crop_params[7]-self.pad_crop_params[6]+1)) self.ai2d_crop.build([1,3,output_size[1],output_size[0]],[1,3,self.model_input_size[1],self.model_input_size[0]]) else: self.need_pad=False self.ai2d_crop.resize(nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel) self.ai2d_crop.crop(int(self.center_xy_wh[0]-self.pad_crop_params[8]/2.0),int(self.center_xy_wh[1]-self.pad_crop_params[8]/2.0),int(self.pad_crop_params[8]),int(self.pad_crop_params[8])) self.ai2d_crop.build([1,3,ai2d_input_size[1],ai2d_input_size[0]],[1,3,self.model_input_size[1],self.model_input_size[0]]) # 重写预处理函数preprocess,因为该部分不是单纯的走一个ai2d做预处理,所以该函数需要重写 def preprocess(self,input_np): if self.need_pad: pad_output=self.ai2d_pad.run(input_np).to_numpy() return [self.ai2d_crop.run(pad_output)] else: return [self.ai2d_crop.run(input_np)] # 自定义后处理,results是模型输出array的列表 def postprocess(self,results): with ScopedTiming("postprocess",self.debug_mode > 0): return results[0] # 计算padding和crop参数 def get_padding_crop_param(self): s_z = round(np.sqrt((self.center_xy_wh[2] + self.CONTEXT_AMOUNT * (self.center_xy_wh[2] + self.center_xy_wh[3])) * (self.center_xy_wh[3] + self.CONTEXT_AMOUNT * (self.center_xy_wh[2] + self.center_xy_wh[3])))) c = (s_z + 1) / 2 context_xmin = np.floor(self.center_xy_wh[0] - c + 0.5) context_xmax = int(context_xmin + s_z - 1) context_ymin = np.floor(self.center_xy_wh[1] - c + 0.5) context_ymax = int(context_ymin + s_z - 1) left_pad = int(max(0, -context_xmin)) top_pad = int(max(0, -context_ymin)) right_pad = int(max(0, int(context_xmax - self.rgb888p_size[0] + 1))) bottom_pad = int(max(0, int(context_ymax - self.rgb888p_size[1] + 1))) context_xmin = context_xmin + left_pad context_xmax = context_xmax + left_pad context_ymin = context_ymin + top_pad context_ymax = context_ymax + top_pad return [top_pad,bottom_pad,left_pad,right_pad,context_xmin,context_xmax,context_ymin,context_ymax,s_z] #重写deinit def deinit(self): with ScopedTiming("deinit",self.debug_mode > 0): del self.ai2d_pad del self.ai2d_crop super().deinit() 2.自定义跟踪实时任务类。其自定义函数类别与上类似。 class TrackSrcApp(AIBase): def __init__(self,kmodel_path,model_input_size,ratio_src_crop,rgb888p_size=[1280,720],display_size=[1920,1080],debug_mode=0): super().__init__(kmodel_path,model_input_size,rgb888p_size,debug_mode) # kmodel路径 self.kmodel_path=kmodel_path # 检测模型输入分辨率 self.model_input_size=model_input_size # sensor给到AI的图像分辨率,宽16字节对齐 self.rgb888p_size=[ALIGN_UP(rgb888p_size[0],16),rgb888p_size[1]] # 视频输出VO分辨率,宽16字节对齐 self.display_size=[ALIGN_UP(display_size[0],16),display_size[1]] # padding和crop参数列表 self.pad_crop_params=[] # 跟踪框宽、高调整系数 self.CONTEXT_AMOUNT = 0.5 # src和crop模型的输入尺寸比例 self.ratio_src_crop = ratio_src_crop # debug模式 self.debug_mode=debug_mode # 注意:ai2d设置多个预处理时执行的顺序为:crop->shift->resize/affine->pad,如果不符合该顺序,需要配置多个ai2d对象; # 如下模型预处理要先做resize+padding再做resize+crop,因此要配置两个Ai2d对象 self.ai2d_pad=Ai2d(debug_mode) self.ai2d_pad.set_ai2d_dtype(nn.ai2d_format.NCHW_FMT,nn.ai2d_format.NCHW_FMT,np.uint8, np.uint8) self.ai2d_crop=Ai2d(debug_mode) self.ai2d_crop.set_ai2d_dtype(nn.ai2d_format.NCHW_FMT,nn.ai2d_format.NCHW_FMT,np.uint8, np.uint8) self.need_pad=False # 配置预处理操作,这里使用了crop、pad和resize,Ai2d支持crop/shift/pad/resize/affine,具体代码请打开/sdcard/libs/AI2D.py查看 def config_preprocess(self,center_xy_wh,input_image_size=None): with ScopedTiming("set preprocess config",self.debug_mode > 0): # 初始化ai2d预处理配置,默认为sensor给到AI的尺寸,可以通过设置input_image_size自行修改输入尺寸 ai2d_input_size=input_image_size if input_image_size else self.rgb888p_size # 计算padding参数并应用pad操作,以确保输入图像尺寸与模型输入尺寸匹配 self.pad_crop_params= self.get_padding_crop_param(center_xy_wh) # 如果需要padding,配置padding部分,否则只走crop if (self.pad_crop_params[0] != 0 or self.pad_crop_params[1] != 0 or self.pad_crop_params[2] != 0 or self.pad_crop_params[3] != 0): self.need_pad=True self.ai2d_pad.resize(nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel) self.ai2d_pad.pad([0, 0, 0, 0, self.pad_crop_params[0], self.pad_crop_params[1], self.pad_crop_params[2], self.pad_crop_params[3]], 0, [114, 114, 114]) output_size=[self.rgb888p_size[0]+self.pad_crop_params[2]+self.pad_crop_params[3],self.rgb888p_size[1]+self.pad_crop_params[0]+self.pad_crop_params[1]] self.ai2d_pad.build([1,3,ai2d_input_size[1],ai2d_input_size[0]],[1,3,output_size[1],output_size[0]]) self.ai2d_crop.resize(nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel) self.ai2d_crop.crop(int(self.pad_crop_params[4]),int(self.pad_crop_params[6]),int(self.pad_crop_params[5]-self.pad_crop_params[4]+1),int(self.pad_crop_params[7]-self.pad_crop_params[6]+1)) self.ai2d_crop.build([1,3,output_size[1],output_size[0]],[1,3,self.model_input_size[1],self.model_input_size[0]]) else: self.need_pad=False self.ai2d_crop.resize(nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel) self.ai2d_crop.crop(int(center_xy_wh[0]-self.pad_crop_params[8]/2.0),int(center_xy_wh[1]-self.pad_crop_params[8]/2.0),int(self.pad_crop_params[8]),int(self.pad_crop_params[8])) self.ai2d_crop.build([1,3,ai2d_input_size[1],ai2d_input_size[0]],[1,3,self.model_input_size[1],self.model_input_size[0]]) # 重写预处理函数preprocess,因为该部分不是单纯的走一个ai2d做预处理,所以该函数需要重写 def preprocess(self,input_np): with ScopedTiming("preprocess",self.debug_mode>0): if self.need_pad: pad_output=self.ai2d_pad.run(input_np).to_numpy() return [self.ai2d_crop.run(pad_output)] else: return [self.ai2d_crop.run(input_np)] # 自定义后处理,results是模型输出array的列表 def postprocess(self,results): with ScopedTiming("postprocess",self.debug_mode > 0): return results[0] # 计算padding和crop参数 def get_padding_crop_param(self,center_xy_wh): s_z = round(np.sqrt((center_xy_wh[2] + self.CONTEXT_AMOUNT * (center_xy_wh[2] + center_xy_wh[3])) * (center_xy_wh[3] + self.CONTEXT_AMOUNT * (center_xy_wh[2] + center_xy_wh[3])))) * self.ratio_src_crop c = (s_z + 1) / 2 context_xmin = np.floor(center_xy_wh[0] - c + 0.5) context_xmax = int(context_xmin + s_z - 1) context_ymin = np.floor(center_xy_wh[1] - c + 0.5) context_ymax = int(context_ymin + s_z - 1) left_pad = int(max(0, -context_xmin)) top_pad = int(max(0, -context_ymin)) right_pad = int(max(0, int(context_xmax - self.rgb888p_size[0] + 1))) bottom_pad = int(max(0, int(context_ymax - self.rgb888p_size[1] + 1))) context_xmin = context_xmin + left_pad context_xmax = context_xmax + left_pad context_ymin = context_ymin + top_pad context_ymax = context_ymax + top_pad return [top_pad,bottom_pad,left_pad,right_pad,context_xmin,context_xmax,context_ymin,context_ymax,s_z] # 重写deinit def deinit(self): with ScopedTiming("deinit",self.debug_mode > 0): del self.ai2d_pad del self.ai2d_crop super().deinit() 3.追踪应用类。 class TrackerApp(AIBase): def __init__(self,kmodel_path,crop_input_size,thresh,rgb888p_size=[1280,720],display_size=[1920,1080],debug_mode=0): super().__init__(kmodel_path,rgb888p_size,debug_mode) # kmodel路径 self.kmodel_path=kmodel_path # crop模型的输入尺寸 self.crop_input_size=crop_input_size # 跟踪框阈值 self.thresh=thresh # 跟踪框宽、高调整系数 self.CONTEXT_AMOUNT = 0.5 # sensor给到AI的图像分辨率,宽16字节对齐 self.rgb888p_size=[ALIGN_UP(rgb888p_size[0],16),rgb888p_size[1]] # 视频输出VO分辨率,宽16字节对齐 self.display_size=[ALIGN_UP(display_size[0],16),display_size[1]] # debug模式 self.debug_mode=debug_mode self.ai2d=Ai2d(debug_mode) self.ai2d.set_ai2d_dtype(nn.ai2d_format.NCHW_FMT,nn.ai2d_format.NCHW_FMT,np.uint8, np.uint8) def config_preprocess(self,input_image_size=None): with ScopedTiming("set preprocess config",self.debug_mode > 0): pass # 重写run函数,因为没有预处理过程,所以原来run操作中包含的preprocess->inference->postprocess不合适,这里只包含inference->postprocess def run(self,input_np_1,input_np_2,center_xy_wh): input_tensors=[] input_tensors.append(nn.from_numpy(input_np_1)) input_tensors.append(nn.from_numpy(input_np_2)) results=self.inference(input_tensors) return self.postprocess(results,center_xy_wh) # 自定义后处理,results是模型输出array的列表,这里使用了aidemo的nanotracker_postprocess列表 def postprocess(self,results,center_xy_wh): with ScopedTiming("postprocess",self.debug_mode > 0): det = aidemo.nanotracker_postprocess(results[0],results[1],[self.rgb888p_size[1],self.rgb888p_size[0]],self.thresh,center_xy_wh,self.crop_input_size[0],self.CONTEXT_AMOUNT) return det class NanoTracker: def __init__(self,track_crop_kmodel,track_src_kmodel,tracker_kmodel,crop_input_size,src_input_size,threshold=0.25,rgb888p_size=[1280,720],display_size=[1920,1080],debug_mode=0): # 跟踪模版模型路径 self.track_crop_kmodel=track_crop_kmodel # 跟踪实时模型路径 self.track_src_kmodel=track_src_kmodel # 跟踪模型路径 self.tracker_kmodel=tracker_kmodel # 跟踪模版模型输入分辨率 self.crop_input_size=crop_input_size # 跟踪实时模型输入分辨率 self.src_input_size=src_input_size self.threshold=threshold self.CONTEXT_AMOUNT=0.5 # 跟踪框宽、高调整系数 self.ratio_src_crop = 0.0 # src模型和crop模型输入比值 self.track_x1 = float(600) # 起始跟踪目标框左上角点x self.track_y1 = float(300) # 起始跟踪目标框左上角点y self.track_w = float(100) # 起始跟踪目标框w self.track_h = float(100) # 起始跟踪目标框h self.draw_mean=[] # 初始目标框位置列表 self.center_xy_wh = [] self.track_boxes = [] self.center_xy_wh_tmp = [] self.track_boxes_tmp=[] self.crop_output=None self.src_output=None # 跟踪框初始化时间 self.seconds = 8 self.endtime = time.time() + self.seconds self.enter_init = True # sensor给到AI的图像分辨率,宽16字节对齐 self.rgb888p_size=[ALIGN_UP(rgb888p_size[0],16),rgb888p_size[1]] # 视频输出VO分辨率,宽16字节对齐 self.display_size=[ALIGN_UP(display_size[0],16),display_size[1]] self.init_param() self.track_crop=TrackCropApp(self.track_crop_kmodel,model_input_size=self.crop_input_size,ratio_src_crop=self.ratio_src_crop,center_xy_wh=self.center_xy_wh,rgb888p_size=self.rgb888p_size,display_size=self.display_size,debug_mode=0) self.track_src=TrackSrcApp(self.track_src_kmodel,model_input_size=self.src_input_size,ratio_src_crop=self.ratio_src_crop,rgb888p_size=self.rgb888p_size,display_size=self.display_size,debug_mode=0) self.tracker=TrackerApp(self.tracker_kmodel,crop_input_size=self.crop_input_size,thresh=self.threshold,rgb888p_size=self.rgb888p_size,display_size=self.display_size) self.track_crop.config_preprocess() # run函数 def run(self,input_np): # 在初始化时间内,crop模版部分的到跟踪模版特征,否则,对当前帧进行src推理得到特征并使用tracker对两个特征推理,得到跟踪框的坐标 nowtime = time.time() if (self.enter_init and nowtime <= self.endtime): print("倒计时: " + str(self.endtime - nowtime) + " 秒") self.crop_output=self.track_crop.run(input_np) time.sleep(1) return self.draw_mean else: self.track_src.config_preprocess(self.center_xy_wh) self.src_output=self.track_src.run(input_np) det=self.tracker.run(self.crop_output,self.src_output,self.center_xy_wh) return det # 绘制效果,绘制跟踪框位置 def draw_result(self,pl,box): pl.osd_img.clear() if self.enter_init: pl.osd_img.draw_rectangle(box[0],box[1],box[2],box[3],color=(255, 0, 255, 0),thickness = 4) if (time.time() > self.endtime): self.enter_init = False else: self.track_boxes = box[0] self.center_xy_wh = box[1] track_bool = True if (len(self.track_boxes) != 0): track_bool = self.track_boxes[0] > 10 and self.track_boxes[1] > 10 and self.track_boxes[0] + self.track_boxes[2] < self.rgb888p_size[0] - 10 and self.track_boxes[1] + self.track_boxes[3] < self.rgb888p_size[1] - 10 else: track_bool = False if (len(self.center_xy_wh) != 0): track_bool = track_bool and self.center_xy_wh[2] * self.center_xy_wh[3] < 40000 else: track_bool = False if (track_bool): self.center_xy_wh_tmp = self.center_xy_wh self.track_boxes_tmp = self.track_boxes x1 = int(float(self.track_boxes[0]) * self.display_size[0] / self.rgb888p_size[0]) y1 = int(float(self.track_boxes[1]) * self.display_size[1] / self.rgb888p_size[1]) w = int(float(self.track_boxes[2]) * self.display_size[0] / self.rgb888p_size[0]) h = int(float(self.track_boxes[3]) * self.display_size[1] / self.rgb888p_size[1]) pl.osd_img.draw_rectangle(x1, y1, w, h, color=(255, 255, 0, 0),thickness = 4) else: self.center_xy_wh = self.center_xy_wh_tmp self.track_boxes = self.track_boxes_tmp x1 = int(float(self.track_boxes[0]) * self.display_size[0] / self.rgb888p_size[0]) y1 = int(float(self.track_boxes[1]) * self.display_size[1] / self.rgb888p_size[1]) w = int(float(self.track_boxes[2]) * self.display_size[0] / self.rgb888p_size[0]) h = int(float(self.track_boxes[3]) * self.display_size[1] / self.rgb888p_size[1]) pl.osd_img.draw_rectangle(x1, y1, w, h, color=(255, 255, 0, 0),thickness = 4) pl.osd_img.draw_string_advanced( x1 , y1-50,32, "请远离摄像头,保持跟踪物体大小基本一致!" , color=(255, 255 ,0 , 0)) pl.osd_img.draw_string_advanced( x1 , y1-100,32, "请靠近中心!" , color=(255, 255 ,0 , 0)) # crop参数初始化 def init_param(self): self.ratio_src_crop = float(self.src_input_size[0])/float(self.crop_input_size[0]) print(self.ratio_src_crop) if (self.track_x1 < 50 or self.track_y1 < 50 or self.track_x1+self.track_w >= self.rgb888p_size[0]-50 or self.track_y1+self.track_h >= self.rgb888p_size[1]-50): print("**剪切范围超出图像范围**") else: track_mean_x = self.track_x1 + self.track_w / 2.0 track_mean_y = self.track_y1 + self.track_h / 2.0 draw_mean_w = int(self.track_w / self.rgb888p_size[0] * self.display_size[0]) draw_mean_h = int(self.track_h / self.rgb888p_size[1] * self.display_size[1]) draw_mean_x = int(track_mean_x / self.rgb888p_size[0] * self.display_size[0] - draw_mean_w / 2.0) draw_mean_y = int(track_mean_y / self.rgb888p_size[1] * self.display_size[1] - draw_mean_h / 2.0) self.draw_mean=[draw_mean_x,draw_mean_y,draw_mean_w,draw_mean_h] self.center_xy_wh = [track_mean_x,track_mean_y,self.track_w,self.track_h] self.center_xy_wh_tmp=[track_mean_x,track_mean_y,self.track_w,self.track_h] self.track_boxes = [self.track_x1,self.track_y1,self.track_w,self.track_h,1] self.track_boxes_tmp=np.array([self.track_x1,self.track_y1,self.track_w,self.track_h,1]) 4.main函数。其引用了模型文件路径,初始化初始化PipeLine,建立目标追踪任务,在主循环获取、推理当前帧,绘制当前帧推理结果、打印结果、展示推理结果。 if __name__=="__main__": # 显示模式,默认"hdmi",可以选择"hdmi"和"lcd" display_mode="lcd" if display_mode=="hdmi": display_size=[1920,1080] else: display_size=[800,480] # 跟踪模板模型路径 track_crop_kmodel_path="/sdcard/examples/kmodel/cropped_test127.kmodel" # 跟踪实时模型路径 track_src_kmodel_path="/sdcard/examples/kmodel/nanotrack_backbone_sim.kmodel" # 跟踪模型路径 tracker_kmodel_path="/sdcard/examples/kmodel/nanotracker_head_calib_k230.kmodel" # 其他参数 rgb888p_size=[1280,720] track_crop_input_size=[127,127] track_src_input_size=[255,255] threshold=0.1 # 初始化PipeLine,只关注传给AI的图像分辨率,显示的分辨率 pl=PipeLine(rgb888p_size=rgb888p_size,display_size=display_size,display_mode=display_mode) pl.create() track=NanoTracker(track_crop_kmodel_path,track_src_kmodel_path,tracker_kmodel_path,crop_input_size=track_crop_input_size,src_input_size=track_src_input_size,threshold=threshold,rgb888p_size=rgb888p_size,display_size=display_size) clock = time.clock() while True: clock.tick() img=pl.get_frame() # 获取当前帧 output=track.run(img) # 推理当前帧 track.draw_result(pl,output) # 绘制当前帧推理结果 print(output) # 打印当前结果 pl.show_image() # 展示推理结果 gc.collect() print(clock.fps()) #打印帧率 6.测验。       运行起来后将绿框对准跟踪目标(这里选择的是苹果),等待绿框变红后说明学习成功并开始追踪,这时移动摄像头,在视距范围内,始终能跟踪找到目标。效果如下         至此实现K230目标跟踪功能。  

  • 2025-03-22
  • 回复了主题帖: 《ROS2智能机器人开发实践》--7.ROS 2机器人仿真与仿真到实物

    秦天qintian0303 发表于 2025-3-22 09:58 机器人模型可视化这个不错,直观   是的,深入可学的也挺多

  • 2025-03-21
  • 发表了主题帖: 《ROS2智能机器人开发实践》--7.ROS 2机器人仿真与仿真到实物

    本帖最后由 dirty 于 2025-3-21 23:28 编辑       前面学习了ROS2原理和常用工具,是开发的很重要基础。本篇讲述ROS 2机器人仿真。 一.机器人仿真 ①URDF机器人建模 1.代码display.launch.py from ament_index_python.packages import get_package_share_path from launch import LaunchDescription from launch.actions import DeclareLaunchArgument from launch.conditions import IfCondition, UnlessCondition from launch.substitutions import Command, LaunchConfiguration from launch_ros.actions import Node from launch_ros.parameter_descriptions import ParameterValue def generate_launch_description(): urdf_tutorial_path = get_package_share_path('learning_urdf') default_model_path = urdf_tutorial_path / 'urdf/mbot_base.urdf' default_rviz_config_path = urdf_tutorial_path / 'rviz/urdf.rviz' gui_arg = DeclareLaunchArgument(name='gui', default_value='false', choices=['true', 'false'], description='Flag to enable joint_state_publisher_gui') model_arg = DeclareLaunchArgument(name='model', default_value=str(default_model_path), description='Absolute path to robot urdf file') rviz_arg = DeclareLaunchArgument(name='rvizconfig', default_value=str(default_rviz_config_path), description='Absolute path to rviz config file') robot_description = ParameterValue(Command(['xacro ', LaunchConfiguration('model')]), value_type=str) robot_state_publisher_node = Node( package='robot_state_publisher', executable='robot_state_publisher', parameters=[{'robot_description': robot_description}] ) # Depending on gui parameter, either launch joint_state_publisher or joint_state_publisher_gui joint_state_publisher_node = Node( package='joint_state_publisher', executable='joint_state_publisher', condition=UnlessCondition(LaunchConfiguration('gui')) ) joint_state_publisher_gui_node = Node( package='joint_state_publisher_gui', executable='joint_state_publisher_gui', condition=IfCondition(LaunchConfiguration('gui')) ) rviz_node = Node( package='rviz2', executable='rviz2', name='rviz2', output='screen', arguments=['-d', LaunchConfiguration('rvizconfig')], ) return LaunchDescription([ gui_arg, model_arg, rviz_arg, joint_state_publisher_node, joint_state_publisher_gui_node, robot_state_publisher_node, rviz_node ]) 2.机器人模型可视化。命令如下 ros2 launch learning_urdf display.launch.py urdf_to_graphviz mbot_base.urdf # 在模型所在的文件夹下运行       效果如下图 ②Gazebo机器人仿真 1.代码load_urdf_into_gazebo_harmonic.launch.py import os import xacro from ament_index_python.packages import get_package_share_directory from launch import LaunchDescription from launch.actions import IncludeLaunchDescription from launch.launch_description_sources import PythonLaunchDescriptionSource from launch_ros.actions import Node def generate_launch_description(): # Include the robot_state_publisher launch file, provided by our own package. Force sim time to be enabled # !!! MAKE SURE YOU SET THE PACKAGE NAME CORRECTLY !!! package_name='learning_gazebo_harmonic' #<--- CHANGE ME pkg_path = os.path.join(get_package_share_directory(package_name)) xacro_file = os.path.join(pkg_path,'urdf','mbot_gazebo_harmonic.xacro') world_file = os.path.join(pkg_path,'worlds','empty.sdf') robot_description_config = xacro.process_file(xacro_file) # Pose where we want to spawn the robot spawn_x_val = '0.0' spawn_y_val = '0.0' spawn_z_val = '0.3' spawn_yaw_val = '0.0' # Gazebo Sim pkg_ros_gz_sim = get_package_share_directory('ros_gz_sim') gazebo = IncludeLaunchDescription( PythonLaunchDescriptionSource( os.path.join(pkg_ros_gz_sim, 'launch', 'gz_sim.launch.py')), launch_arguments={'gz_args': '-r ' + world_file}.items(), ) # Run the spawner node from the gazebo_ros package. The entity name doesn't really matter if you only have a single robot. spawn_entity = Node(package='ros_gz_sim', executable='create', arguments=['-topic', 'robot_description', '-name', 'mbot', '-x', spawn_x_val, '-y', spawn_y_val, '-z', spawn_z_val, '-Y', spawn_yaw_val], output='screen') # Create a robot_state_publisher node params = {'robot_description': robot_description_config.toxml(), 'use_sim_time': True} node_robot_state_publisher = Node( package='robot_state_publisher', executable='robot_state_publisher', output='screen', parameters=[params] ) # ros gz bridge ros_gz_bridge = Node( package='ros_gz_bridge', executable='parameter_bridge', parameters=[{ 'config_file': os.path.join(get_package_share_directory(package_name), 'config', 'ros_gz_bridge_mbot.yaml'), 'qos_overrides./tf_static.publisher.durability': 'transient_local', }], output='screen' ) # Launch them all! return LaunchDescription([ gazebo, spawn_entity, ros_gz_bridge, node_robot_state_publisher, ]) 2.在Gazebo中加载机器人模型 ros2 launch learning_gazebo_harmonic load_urdf_into_gazebo_harmonic.launch.py       运行效果如下:   二.机器人仿真到实物 1.下载originbot_desktop 功能包集合 cd ~/dev_ws/src git clone https://gitee.com/guyuehome/originbot_desktop.git       功能包集合如下图所示 2.安装功能包依赖 cd ~/dev_ws/src/originbot_desktop ./install_prereq.sh 3.编译工作空间 cd ~/dev_ws colcon build 4.设置环境变量 echo "~/dev_ws/install/setup.sh" >> ~/.bashrc 5.机器人仿真测试 ros2 launch originbot_gazebo_harmonic load_originbot_into_gazebo.launch.py         关于机器人仿真的代码部分还需要细细去梳理学习理解,通过实践可以看到仿真界面。从这里开始,为机器人开发打开了一扇大门,在ROS2机器人应用方面,还有很大的探索空间。

  • 2025-03-15
  • 回复了主题帖: 【B-G431B-ESC1-无刷电机板】3-开环VF强拖

    电机是有感还是无感,购买链接有没?参考看下

  • 2025-03-12
  • 回复了主题帖: 嵌入式Rust修炼营入围名单来啦,一起度过嵌入式Rust学习时光的小伙伴集合啦~

    个人信息无误

  • 回复了主题帖: 【Wio Lite AI STM32H725AE视觉开发板】--6.人脸检测

    Eric_Tseng 发表于 2025-3-11 22:36 网上为数不多靠谱的资料。如果这块开发板连视觉都可以处理,那在工业或者电网行业,能不能通过采集一些数据 ... AI模型文件tflite是通过数据集训练获得,具体ST 边缘AI工具也是有迭代更新,最新Edge Impulse可以了解下https://edgeimpulse.com/

  • 回复了主题帖: 【Wio Lite AI STM32H725AE视觉开发板】--2.开发环境搭建与点灯

    Eric_Tseng 发表于 2025-3-11 22:30 我尝试过插上stlink,但是板载Led没有反应。但是用typec接口就会量蓝灯和绿灯,不知道是什么原因。而且创建 ... STLINK接线检查下,电源用3.3V。芯片Pack包可以先安装,Keil版本会有要求,在STM32CubeMx工程配置里有选项留意下版本方面要求。再有的STLINK驱动是CMSIS-DAP在stm32cubeide不可直接下载烧录。

  • 2025-03-02
  • 回复了主题帖: 《ROS2智能机器人开发实践》--6.ROS2常用工具

    Jacktang 发表于 2025-3-2 09:34 理解好机器人中的几个坐标系之间的关系看来主要的 是的,前面这些是基础

  • 2025-03-01
  • 发表了主题帖: 《ROS2智能机器人开发实践》--6.ROS2常用工具

          机器人开发设计系统非常复杂,ROS2中有很多好用的工具,可以让开发事半功倍。这些“神器”包含Launch、TF、URDF、Gazebo、Rviz、Rqt,下面做讲解与学习。 一.Launch:多节点启动与配置脚本       前面我们讲到运行一个ROS节点,都需要打开一个新的终端运行一个命令。一次性启动所有节点,这就是Launch启动文件,它是ROS系统中多节点启动与配置的一种脚本。ROS2中的Launch文件就是基于Python描述的。 1.多节点启动 启动终端,使用ros2中的launch命令来启动第一个launch文件。 ros2 launch learning_launch simple.launch.py 2.代码与运行结果如下,可以看到不需要打开两个终端分别命令运行。           3.ROS系统参数设置       ROS系统中的参数,也可以在Launch文件中设置。 4.Launch文件包含       在复杂的机器人系统中,launch文件也会有很多,此时我们可以使用类似编程中的include机制,让launch文件互相包含。   二.TF:机器人坐标系管理神器       ROS给我们提供了一个坐标系的管理神器——TF。 1.机器人中的坐标系       在机械臂形态的机器人中,机器人安装的位置叫做基坐标系Base Frame,机器人安装位置在外部环境下的参考系叫做世界坐标系World Frame,机器人末端夹爪的位置叫做工具坐标系,外部被操作物体的位置叫做工件坐标系,在机械臂抓取外部物体的过程中,这些坐标系之间的关系也在跟随变化。       关于坐标系变换关系的基本理论,可以分解为平移和旋转两个部分,通过一个四乘四的矩阵进行描述,在空间中画出坐标系,那两者之间的变换关系,其实就是向量的数学描述。       ROS中TF功能的底层原理,就是对这些数学变换进行了封装。 2.TF命令行操作 ●查看TF树 ros2 run tf2_tools view_frames 运行结果如下,默认在当前终端路径下生成了一个frames.pdf文件,打开之后,就可以看到系统中各个坐标系的关系了。 ●查询坐标信息变换 ros2 run tf2_ros tf2_echo turtle2 turtle1 ●坐标可视化 ros2 run rviz2 rviz2 -d $(ros2 pkg prefix --share turtle_tf2_py)/rviz/turtle_rviz.rviz   三.URDF:机器人建模方法       机器人建模方法——URDF,用来描述机器人外观、性能等各方面属性。 1.机器人组成       机器人一般是由硬件结构、驱动系统、传感器系统、控制系统四大部分组成。       机器人建模的过程,其实就是按照类似的思路,通过建模语言,把机器人每一个部分都描述清楚,再组合起来的过程。 2.URDF       ROS中的建模方法叫做URDF,全称是统一机器人描述格式,不仅可以清晰描述机器人自身的模型,还可以描述机器人的外部环境,比如这里的桌子,也可以算作一个模型。       URDF模型文件使用的是XML格式,由一系列尖括号包围的标签和其中的属性组合而成。       在建模中,大臂和小臂就类似机器人的这些独立的刚体部分,称为连杆Link,手肘就类似于机器人电机驱动部分,称为关节joint。       所以在URDF建模过程中,关键任务就是通过这里的<link>和<joint>,理清楚每一个连杆和关节的描述信息。   四.Gazebo:三维物理仿真平台 1.介绍       Gazebo是ROS系统中最为常用的三维物理仿真平台,支持动力学引擎,可以实现高质量的图形渲染,不仅可以模拟机器人及周边环境,还可以加入摩擦力、弹性系数等物理属性。 2.安装 sudo apt install ros-jazzy-gazebo-* 3.运行 ros2 launch gazebo_ros gazebo.launch.py 4.XACRO机器人模型优化       URDF文件格式的升级版本——XACRO文件。同样也是对机器人URDF模型的创建,XACRO文件加入了更多编程化的实现方法,可以让模型创建更友好。安装如下,文件语法可以了解一下。 sudo apt install ros-jazzy-xacro 5.Ignition:下一代Gazebo       随着技术的进步,Gazebo仿真平台也在不断迭代,新一代的Gazebo命名为Ignition,从渲染效果和仿真流畅度上都有较大的变化   五.Rviz:三维可视化显示平台 1.Rviz介绍       Rviz的核心框架是基于Qt可视化工具打造的一个开放式平台,官方出厂就自带了很多机器人常用的可视化显示插件,只要我们按照ROS中的消息发布对应的话题,就可以看到图形化的效果了。 2.运行方法       开启终端,运行以下命令 ros2 run rviz2 rviz2   六.RQT:模块化可视化工具       rqt和Rviz一样,也是基于QT可视化工具开发而来,在使用前安装与运行命令如下 sudo apt install ros-jazzy-rqt rqt       界面一样,里边可以加载很多小模块,每个模块都可以实现一个具体的小功能,一些常用的功能有:日志显示、图像显示、发布话题数据/调用服务请求、绘制数据曲线、数据包管理、节点可视化等。         本篇将ROS2中常用工具梳理了一遍,实际应用中可以作为一个参考。

  • 2025-02-26
  • 回复了主题帖: 突发!上海一家AI公司爆雷了!

    拉投资资本的,看了下成立时间也不长,收益与投入不能接续上就很容易爆,感觉最近AI炒得有些过热了

  • 2025-02-25
  • 回复了主题帖: 嵌入式Rust修炼营:动手写串口烧录工具和MCU例程,Rust达人Hunter直播带你入门Rust

    -参与理由&个人编程基础: 参与理由:Rust作为一门新兴编程语言,有其优势特点与普及应用。个人作为嵌入式软件开发者,也是机选巧合和一些见闻,对Rust也是看好也很感兴趣。希望可以借此机会,理论与工程实践结合,掌握Rust。 个人编程基础:C、C++、Python -查看修炼任务和活动时间表,预估可以跟着完成几级任务(初级、中级、高级): 看了实践活动表及开发板附的资料,预估可以完成初级、中级、高级任务。 -如探索过Rust,请说明Rust学习过程遇到难点,希望在参与活动中收获什么? EEWorld之前评测出过《Rust实战》这本书,有所关注。希望通过这次参与活动,掌握Rust编程语言,可以熟练运用。

  • 2025-02-23
  • 发表了主题帖: 【Wio Lite AI STM32H725AE视觉开发板】--6.人脸检测

    本帖最后由 dirty 于 2025-2-23 21:54 编辑       本篇讲述使用ST生态资源X-CUBE-AI实现人脸检测功能。 一.AI模型了解       X-CUBE-AI最新也称STM32Cube.AI,可用于在任意STM32微控制器上优化并部署由主流AI框架训练的神经网络模型。该工具可通过STM32CubeMX环境中的图形界面以及命令行方式使用。该工具现在也可以通过ST Edge AI Developer Cloud在线使用。官方相关文档【1】可以根据需要详细阅读了解,也可以到ST中文论坛搜集资料提问等【2】。   二.配置部署 1.安装X-CUBE-AI 6.0.0       在STM32CubeMX下,按如下选择安装 2.加载AI模型。这里使用到人脸检测模型文件person_detect.tflite。   三.代码 1.生成工程目录下会有x-CUBE-AI的应用API代码函数。 2.关于灰度转换。用到的函数STM32Ipl_Convert。 3.关于转换图像尺寸.用到的函数STM32Ipl_Resize。 4.关于AI的图像处理,用到aiRun。 /* 相对路径:X-CUBE-AI\App\ai_inference.c */ void aiRun(ai_u8 *pIn, ai_u8 *pOut) { ai_i32 batch; ai_error err; /* 1 - Create the AI buffer IO handlers with the default definition */ ai_buffer ai_input[AI_NETWORK_IN_NUM] = AI_NETWORK_IN; ai_buffer ai_output[AI_NETWORK_OUT_NUM] = AI_NETWORK_OUT; /* 2 - Update IO handlers with the data payload */ ai_input[0].n_batches = 1; ai_input[0].data = AI_HANDLE_PTR(pIn); ai_output[0].n_batches = 1; ai_output[0].data = AI_HANDLE_PTR(pOut); batch = ai_network_run(network, ai_input, ai_output); if (batch != 1) { err = ai_network_get_error(network); printf("AI ai_network_run error - type=%d code=%d\r\n", err.type, err.code); Error_Handler(); } } 5.模型处理。用到函数postprocess。可以看到在output_labels标记人脸检测结果,ai_output_buffer为置信度,并将检测信息拷贝到了msg. /** * @brief Run post-processing operation * @param ai_output_buffer pointer to NN output */ void postprocess(void* ai_output_buffer) { int ranking[NN_OUTPUT_CLASS_NUMBER]; /**NN ouput dequantization if required**/ for(int32_t i = AI_NET_OUTPUT_SIZE - 1; i >= 0; i--) { int32_t q_value = (int32_t) *(((uint8_t *) ai_output_buffer) + i); *(((float *)ai_output_buffer) + i) = 0.01627892255783081f * ((float) q_value - 113.0f); } /* Add missing softmax layer to get a normalized probability distribution */ AI_Softmax((float *)ai_output_buffer); /**Perform ranking**/ for (int i = 0; i < NN_OUTPUT_CLASS_NUMBER; i++) { ranking[i] = i; } UTILS_Bubblesort((float*)(ai_output_buffer), ranking, NN_OUTPUT_CLASS_NUMBER); sprintf(msg, "%s %.0f%%", output_labels[ranking[0]], 100* (*(float*)(ai_output_buffer))); //sprintf(msg, "%.3f, %.3f, %.3f", ((float*)(ai_output_buffer))[0], ((float*)(ai_output_buffer))[1], ((float*)(ai_output_buffer))[2]); } 6.在DCMI帧回调HAL_DCMI_FrameEventCallback里,可以看到AI人脸检测结果在LCD上显示。这里加了一个Title 标题。 /* 路径:\Drivers\BSP\OV2640\OV2640.c */ void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi) { char *title_ste={" Wio Lite AI STM32H725AE Board "}; LCD_Frame_Draw_Image(0, 0, 320, 240, (uint16_t *)_OV2640->frame->buffer, ROTATION_LEFT); LCD_Frame_Draw_String(200, 220, 300, 16, 16, msg, ROTATION_LEFT); LCD_Frame_Draw_String(44, 8, 300, 16, 16, title_ste, ROTATION_LEFT); LCD_Frame_Show(); OV2640_DMA_Config(_OV2640->frame->buffer, (_OV2640->frame->length)/4); } 7.main函数 /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MPU Configuration--------------------------------------------------------*/ MPU_Config(); /* Enable I-Cache---------------------------------------------------------*/ SCB_EnableICache(); /* Enable D-Cache---------------------------------------------------------*/ SCB_EnableDCache(); /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART3_UART_Init(); MX_LTDC_Init(); MX_I2C4_Init(); MX_DMA_Init(); MX_DCMI_Init(); MX_TIM2_Init(); MX_OCTOSPI1_Init(); MX_CRC_Init(); /* USER CODE BEGIN 2 */ LOG("here"); Psram_Init(); /*OV2640 Init*/ HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3); //XCLK OV2640_Init(&OV2640); OV2640_ReadID(&(OV2640.ID)); LOG("ID: %02X %02X \r\n", OV2640.ID.PIDH, OV2640.ID.PIDL); OV2640_UXGAConfig(); //flip after all the camera setting ,or camera setting will change REG04 value. OV2640_Flip(); OV2640_Start(); //initial IPL and AI STM32Ipl_InitLib(buffer_ipl, IPL_BUFFER_SIZE); /* The STM32 CRC IP clock should be enabled to use the network runtime library */ __HAL_RCC_CRC_CLK_ENABLE(); aiInit(); frame_rgb565.data = frame.buffer; /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOF, GPIO_PIN_0, GPIO_PIN_SET); /* * Here we use IPL to do image pre-processing, and then use the pre-processed results as input to the model, and finally do post-processing. */ // rgb565 320*240*2 to grayscale 320*240 if (STM32Ipl_Convert(&frame_rgb565, &frame_rgb565_grayscale) != stm32ipl_err_Ok) { while(1); } // grayscale 320*240 to grayscale 96x96x2 if (STM32Ipl_Resize(&frame_rgb565_grayscale, &frame_grayscale_resized, &roi) != stm32ipl_err_Ok) { while(1); } //AI aiRun(buffer_grayscale_resized, network_output); /* * for debug *sprintf(msg, "%d, %d, %d, %d", network_output[0], network_output[1], network_output[2], network_output[0]+network_output[1]+network_output[2]); */ postprocess(network_output); HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOF, GPIO_PIN_0, GPIO_PIN_RESET); } /* USER CODE END 3 */ }   四.测验       编译烧录后,摄像头不对准人,LCD显示如下。      人脸检测视频 [localvideo]48e4a911205b927709bc68236169c058[/localvideo]         至此实现开发板人脸检测。   【1】STM32Cube.AI简介     https://stm32ai.st.com/zh/stm32-cube-ai/ 【2】ST中文论坛     https://shequ.stmicroelectronics.cn/portal.php   PS:文末附工程代码.  

  • 发表了主题帖: 《ROS2智能机器人开发实践》--5.服务、通信接口、参数、动作、分布式通信、DDS

    本帖最后由 dirty 于 2025-2-23 16:04 编辑       本篇将梳理学习ROS2应用开发方法之服务、通信接口、参数、动作、分布式通信、DDS。 一.服务 1.通信模型       发送一个请求,反馈一个应答的形式,这种通信机制在ROS中成为服务,Service。 2.客户端服务端模型       从服务的实现机制上来看,这种你问我答的形式叫做客户端/服务器模型,简称为CS模型,客户端在需要某些数据的时候,针对某个具体的服务,发送请求信息,服务器端收到请求之后,就会进行处理并反馈应答信息。 3.同步通讯        在服务通信中,客户端可以通过接收到的应答信息,判断服务器端的状态,我们也称之为同步通信。 4.一对多通信       服务通信模型中,服务器端唯一,但客户端可以不唯一。 5.服务接口       服务通信的核心还是要传递数据,数据变成了两个部分,一个请求的数据,还有一个反馈的数据,这些数据和话题消息一样,在ROS中也是要标准定义的,话题使用.msg文件定义,服务使用的是.srv文件定义。 6.案例:加法求解器 (1)启动两个终端,并运行如下节点,第一个节点是服务端,等待请求数据并提供求和功能,第二个节点是客户端,发送传入的两个加数并等待求和结果。 ros2 run learning_service service_adder_server ros2 run learning_service service_adder_client 2 3       运行结果 (2)代码 (3)流程总结       客户端流程 ●编程接口初始化 ●创建节点并初始化 ●创建客户端对象 ●创建并发送请求数据 ●等待服务器端应答数据 ●销毁节点并关闭接口         服务端流程 ●编程接口初始化 ●创建节点并初始化 ●创建服务器端对象 ●通过回调函数处进行服务 ●向客户端反馈应答结果 ●销毁节点并关闭接口   二.通信接口 1.接口定义       ROS的通信系统,它的主要目的就是传输数据,那就得让大家高效的建立连接,并且准确包装和解析传输的数据内容,话题、服务等机制也就诞生了,他们传输的数据,都要符合通信接口的标准定义。 2.ROS通信接口       接口可以让程序之间的依赖降低,便于我们使用别人的代码,减少重复造轮子。       ROS有三种常用的通信机制,分别是话题、服务、动作,通过每一种通信种定义的接口,各种节点才能有机的联系到一起。 3.语言无关       为了保证每一个节点可以使用不同语言编程,ROS将这些接口的设计做成了和语言无关的。 ●话题通信接口的定义使用的是msg文件,由于是单向传输,只需要描述传输的每一帧数据是什么就行。 ●服务通信接口的定义使用的是.srv文件,包含请求和应答两部分定义,通过中间的“---”区分。 ●动作是另外一种通信机制,用来描述机器人的一个运动过程,使用.action文件定义,此时接口的定义分成了三个部分,分别是动作的目标、运动的结果、周期反馈。 4.标准接口       在ROS安装路径中的share文件夹中找到,涵盖众多标准定义。   三.动作       动作是一种一种ROS通信机制。目的就是便于对机器人某一完整行为的流程进行管理。 1.通信模型       一个需要执行一段时间的行为,使用动作的通信机制就更为合适,就像装了一个进度条,我们可以随时把控进度,如果运动过程当中,我们还可以随时发送一个取消运动的命令。 客户端/服务器模型       动作和服务类似,使用的也是客户端和服务器模型,客户端发送动作的目标,想让机器人干什么,服务器端执行动作过程, 控制机器人达到运动的目标,同时周期反馈动作执行过程中的状态。       客户端发送一个运动的目标,想让机器人动起来,服务器端收到之后,就开始控制机器人运动,一边运动,一边反馈当前的状态,如果是一个导航动作,这个反馈可能是当前所处的坐标,如果是机械臂抓取,这个反馈可能又是机械臂的实时姿态。当运动执行结束后,服务器再反馈一个动作结束的信息。整个通信过程就此结束。 2.一对多通信       和服务一样,动作通信中的客户端可以有多个,大家都可以发送运动命令,但是服务器端只能有一个,一个机器人,先执行完成一个动作,才能执行下一个动作。 3.同步通信       有反馈,动作也是一种同步通信机制,之前我们也介绍过,动作过程中的数据通信接口,使用.action文件进行定义。 4.由服务和话题合成       上面的动图,动作的三个通信模块,有两个是服务,一个是话题。当客户端发送运动目标时,使用的是服务的请求调用,服务器端也会反馈一个应带,表示收到命令。动作的反馈过程,其实就是一个话题的周期发布,服务器端是发布者,客户端是订阅者。       动作是一种应用层的通信机制,其底层就是基于话题和服务来实现的。   四.参数       参数是ROS机器人系统中的全局字典,可以运行多个节点中共享数据。 1.全局字典       在ROS系统中,参数是以全局字典的形态存在的。字典由名称和数值组成,也叫做键和值,合成键值。 2.可动态监控       在ROS2中,参数的特性非常丰富,比如某一个节点共享了一个参数,其他节点都可以访问,如果某一个节点对参数进行了修改,其他节点也有办法立刻知道,从而获取最新的数值。这在参数的高级编程中,大家都可能会用到。   五.分布式通信       在ROS系统中,机器人功能是由各种节点组成的,这些节点可能位于不同的计算机中,这种结构可以将原本资源消耗较多的任务,分配到不同的平台上,减轻计算压力,这就是分布式通信框架的典型应用之一。       分布式网络搭建包括装系统、安装ROS2、远程桌面网络控制。分布式数据传输确保设备与电脑连在同一个局域网络中,打通两个计算平台的通信能力,包括话题、服务、动作等通信。 分布式网络分组        ROS2提供了一个DOMAIN的机制,就类似分组一样,处于同一个DOMAIN中的计算机才能通信,我们可以在电脑和树莓派端的.bashrc中加入这样一句配置,即可将两者分配到一个小组中。 export ROS_DOMAIN_ID=<your_domain_id>   六.DDS       ROS2中最为重大的变化--DDS,前面学习的话题、服务、动作,他们底层通信的具体实现过程,都是靠DDS来完成的,它相当于是ROS机器人系统中的神经网络。 1.通信模型       DDS的核心是通信,能够实现通信的模型和软件框架非常多,这里我们列出常用的四种模型: ●点对点模型,许多客户端连接到一个服务端,每次通信时,通信双方必须建立一条连接。当通信节点增多时,连接数也会增多。而且每个客户端都需要知道服务器的具体地址和所提供的服务,一旦服务器地址发生变化,所有客户端都会受到影响。 ●Broker模型,针对点对点模型进行了优化,由Broker集中处理所有人的请求,并进一步找到真正能响应该服务的角色。优点:客户端就不用关心服务器的具体地址了。缺点:Broker作为核心,它的处理速度会影响所有节点的效率,当系统规模增长到一定程度,Broker就会成为整个系统的性能瓶颈。如果Broker发生异常,可能导致整个系统都无法正常运转。之前的ROS1系统,使用的就是类似这样的架构。 ●广播模型,所有节点都可以在通道上广播消息,并且节点都可以收到消息。这个模型解决了服务器地址的问题,而且通信双方也不用单独建立连接,但是广播通道上的消息太多了,所有节点都必须关心每条消息,其实很多是和自己没有关系的。 ●以数据为中心的DDS模型了,这种模型与广播模型有些类似,所有节点都可以在DataBus上发布和订阅消息。但它的先进之处在于,通信中包含了很多并行的通路,每个节点可以只关心自己感兴趣的消息,忽略不感兴趣的消息,有点像是一个旋转火锅,各种好吃的都在这个DataBus传送,我们只需要拿自己想吃的就行,其他的和我们没有关系。       在这几种通信模型中,DDS的优势更加明显。 2.DDS       DDS的全称是Data Distribution Service,也就是数据分发服务,是一套专门为实时系统设计的数据分发/订阅标准.       DDS强调以数据为中心,可以提供丰富的服务质量策略,以保障数据进行实时、高效、灵活地分发,可满足各种分布式实时通信应用需求。 3.DDS在ROS2中的应用       DDS在ROS2系统中的位置至关重要,所有上层建设都建立在DDS之上。在这个ROS2的架构图中,蓝色和红色部分就是DDS。       DDS是一种通信的标准,DDS是一种通信的标准,能够按照DDS标准实现的通信系统很多,这里每一个红色模块,就是某一企业或组织实现的一种DDS系统。       为了实现对多个DDS的兼容,ROS设计了一个Middleware中间件,也就是一个统一的标准,不管我们用那个DDS,保证上层编程使用的函数接口都是一样的。       DDS通讯模型如下       DDS中的基本结构是Domain,Domain将各个应用程序绑定在一起进行通信。DDS中另外一个重要特性就是质量服务策略,QoS。       QoS是一种网络传输策略,应用程序指定所需要的网络传输质量行为,QoS服务实现这种行为要求,尽可能地满足客户对通信质量的需求,可以理解为数据提供者和接收者之间的合约。具体策略如下: ●DEADLINE策略,表示通信数据必须要在每次截止时间内完成一次通信; ●HISTORY策略,表示针对历史数据的一个缓存大小; ●RELIABILITY策略,表示数据通信的模式,配置成BEST_EFFORT,就是尽力传输模式,网络情况不好的时候,也要保证数据流畅,此时可能会导致数据丢失,配置成RELIABLE,就是可信赖模式,可以在通信中尽量保证图像的完整性,我们可以根据应用功能场景选择合适的通信模式; ●DURABILITY策略,可以配置针对晚加入的节点,也保证有一定的历史数据发送过去,可以让新节点快速适应系统。       DDS的加入,让ROS2的通信系统焕然一新,多众多样的通信配置,可以更好的满足不同场景下的机器人应用。 4.案例:DDS编程 (1)代码配置DDS,以Hello World话题通信为例。       执行以下指令运行 ros2 run learning_qos qos_helloworld_pub ros2 run learning_qos qos_helloworld_sub       运行结果如下: (2)代码解析       发布者代码learning_qos/qos_helloworld_pub.py,订阅者代码learning_qos/qos_helloworld_sub.py         至此,ROS2的应用开发方法基本梳理学习实践了一遍,为后面进一步学习与深入奠定一个好的基础。

  • 2025-02-22
  • 发表了主题帖: 【Wio Lite AI STM32H725AE视觉开发板】--5.摄像头屏显

    本帖最后由 dirty 于 2025-2-22 19:19 编辑       本篇实现开发板摄像头驱动屏显。 一.原理       开发板使用了一颗200W像素 DVP接口 OV2640摄像头。其DCMI(数字摄像头接口)原理如下: 引脚定义 DCMI_D0--D7     8位数据线引脚 DCMI_HSYSN    行同步信号输入引脚,由摄像头输入。表示一行有效数据的传输开始或结束 DCMI_VSYSN    帧同步信号输入引脚,和行同步信号差不多,而该信号表示一帧有效数据的传输开始或结束 DCMI_PCLK       像素同步时钟输入,由摄像头提供,每个时钟周期数据会从D[0:7]传输8bit的像素过来。 DCMI_RST         复位信号(低电平有效). DCMI_PWDN     掉电模式使能(高电平有效) IIC4_SDA           SCCB接口数据线 IIC4_SCL           SCCB接口时钟线   二.DCMI接口配置 1.在STM32CubeMX 配置模式及引脚定义 2.参数设置。这里有两项:模式配置(包含像素时钟极性、垂直同步极性、水平同步极性、帧抓取频率、JPEG图像模式)和图像抓取接口配置(包含点选择模式和行选择模式)。 3.DAM配置 4.中断配置 5.SCCBs数据时钟引脚SDA/SCL配置,即I2C4配置。 6.复位引脚PH12、掉电模式使能引脚PE7 GPIO配置       至此,摄像头接口引脚配置基本完成,点击生成工程代码。   三.代码 1.OV2640初始化       在生成代码中,可以看到MX_I2C4_Init对I2C4进行了初始化。定义OV2640结构体变量如下 typedef struct { DCMI_HandleTypeDef *dcmi; DMA_HandleTypeDef *dma; I2C_HandleTypeDef *i2c; Frame_TypeDef *frame; OV2640_IDTypeDef ID; uint32_t fps; uint8_t time_show; }OV2640_TypeDef; static OV2640_TypeDef OV2640 ={ .dcmi = &hdcmi, .dma = &hdma_dcmi_pssi, .i2c = &hi2c4, .frame = &frame };       使用I2C配置摄像头寄存器。BSP包OV2640.c、OV2640.h源文件见附件。 /*OV2640 Init*/ HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3); //XCLK OV2640_Init(&OV2640); OV2640_ReadID(&(OV2640.ID)); LOG("ID: %02X %02X \r\n", OV2640.ID.PIDH, OV2640.ID.PIDL); OV2640_UXGAConfig(); //flip after all the camera setting ,or camera setting will change REG04 value. OV2640_Flip(); OV2640_Start(); 2.LCD初始化函数MX_LTDC_Init里,将图层配置使能打开,帧缓冲LCD_BUFFER定义,代码如下 #define EXTMEM __attribute__((section(".externalram"))) #define DTCMMEM __attribute__((section(".dtcmram"))) #define ITCMMEM __attribute__((section(".itcmram"))) EXTMEM uint8_t LCD_BUFFER[320 * 240 * 2]; EXTMEM static uint8_t LCD_FRAME_BUFFER[320 * 240 * 2]; /* LTDC init function */ void MX_LTDC_Init(void) { /* USER CODE BEGIN LTDC_Init 0 */ /*点亮背光*/ HAL_GPIO_WritePin(GPIOG, GPIO_PIN_5, GPIO_PIN_RESET); HAL_Delay(10); HAL_GPIO_WritePin(GPIOG, GPIO_PIN_5, GPIO_PIN_SET); HAL_Delay(10); HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_SET); //使能的LCD背光 HAL_Delay(100); /* USER CODE END LTDC_Init 0 */ LTDC_LayerCfgTypeDef pLayerCfg = {0}; /* USER CODE BEGIN LTDC_Init 1 */ /* USER CODE END LTDC_Init 1 */ hltdc.Instance = LTDC; hltdc.Init.HSPolarity = LTDC_HSPOLARITY_AL; hltdc.Init.VSPolarity = LTDC_VSPOLARITY_AL; hltdc.Init.DEPolarity = LTDC_DEPOLARITY_AL; hltdc.Init.PCPolarity = LTDC_PCPOLARITY_IPC; hltdc.Init.HorizontalSync = 9; hltdc.Init.VerticalSync = 3; hltdc.Init.AccumulatedHBP = 19; hltdc.Init.AccumulatedVBP = 5; hltdc.Init.AccumulatedActiveW = 259; hltdc.Init.AccumulatedActiveH = 325; hltdc.Init.TotalWidth = 297; hltdc.Init.TotalHeigh = 327; hltdc.Init.Backcolor.Blue = 0; hltdc.Init.Backcolor.Green = 0; hltdc.Init.Backcolor.Red = 0; if (HAL_LTDC_Init(&hltdc) != HAL_OK) { Error_Handler(); } #if 1 pLayerCfg.WindowX0 = 0; pLayerCfg.WindowX1 = 240; pLayerCfg.WindowY0 = 0; pLayerCfg.WindowY1 = 320; pLayerCfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565; pLayerCfg.Alpha = 255; pLayerCfg.Alpha0 = 0; pLayerCfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA; pLayerCfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA; pLayerCfg.FBStartAdress = LCD_BUFFER; pLayerCfg.ImageWidth = 240; pLayerCfg.ImageHeight = 320; pLayerCfg.Backcolor.Blue = 0; pLayerCfg.Backcolor.Green = 0; pLayerCfg.Backcolor.Red = 0; if (HAL_LTDC_ConfigLayer(&hltdc, &pLayerCfg, 0) != HAL_OK) { Error_Handler(); } #endif /* USER CODE BEGIN LTDC_Init 2 */ /* USER CODE END LTDC_Init 2 */ } 3.LCD显示库文件       LCD.c/LCD.h主要实现帧缓冲LCD_BUFFER数据赋值做LCD显示。源文件见附件。 4.在摄像头帧数据事件回调函数,将摄像头俘获图像映射到LCD显示如下,主要LCD_Frame_Show复制转换。 void HAL_DCMI_FrameEventCallback(DCMI_HandleTypeDef *hdcmi) { LCD_Frame_Draw_Image(0, 0, 320, 240, (uint16_t *)_OV2640->frame->buffer, ROTATION_LEFT); LCD_Frame_Draw_String(200, 220, 300, 16, 16, msg, ROTATION_LEFT); LCD_Frame_Show(); OV2640_DMA_Config(_OV2640->frame->buffer, (_OV2640->frame->length)/4); } 5.主函数如下 /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MPU Configuration--------------------------------------------------------*/ MPU_Config(); /* Enable I-Cache---------------------------------------------------------*/ SCB_EnableICache(); /* Enable D-Cache---------------------------------------------------------*/ SCB_EnableDCache(); /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART3_UART_Init(); MX_LTDC_Init(); MX_I2C4_Init(); MX_DMA_Init(); MX_DCMI_Init(); MX_TIM2_Init(); MX_OCTOSPI1_Init(); MX_CRC_Init(); /* USER CODE BEGIN 2 */ LOG("here"); Psram_Init(); /*OV2640 Init*/ HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3); //XCLK OV2640_Init(&OV2640); OV2640_ReadID(&(OV2640.ID)); LOG("ID: %02X %02X \r\n", OV2640.ID.PIDH, OV2640.ID.PIDL); OV2640_UXGAConfig(); //flip after all the camera setting ,or camera setting will change REG04 value. OV2640_Flip(); OV2640_Start(); /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ HAL_Delay(1000); } /* USER CODE END 3 */ }   四.测试       烧录后,可以看到LCD显示摄像头画面,如下 [localvideo]c233a44ecd62f82acacd4f2f99950fcc[/localvideo]       至此,实现摄像头屏显功能。  

  • 发表了主题帖: 【Wio Lite AI STM32H725AE视觉开发板】--4.LCD刷屏

    本帖最后由 dirty 于 2025-2-22 12:41 编辑       本篇讲述点亮LCD刷屏。 一.硬件原理       开发板使用RGB565触摸屏,分辨率240*320,硬件原理图如下: 二.配置       打开STM32CubeMX,选择好芯片型号,Start Project,进入配置。在Pinout&Configuration选项卡下,选中LTDC。 1.LCD显示屏型号与引脚配置。这里根据原理图逐一选择配置成如下。 2.参数设置。这里主要有宽度同步、高度同步、信号极性、图层默认颜色。 3.图层设置。主要有窗口位置、像素参数、混合、图层默认颜色、帧缓冲、图层数量。其中帧缓冲LCD_BUFFER是一个指向SDRAM起始地址的宏定义。 4.背光引脚PF5配置。       配置到此基本完成,这里选择STM32CubeIDE,点击GEBERATE CoDE,生成工程代码。   三.代码 1.背光引脚使能打开,这个手动添加在MX_LTDC_Init里如下,注意由低置高再稳定。 /*点亮背光*/ HAL_GPIO_WritePin(GPIOG, GPIO_PIN_5, GPIO_PIN_RESET); HAL_Delay(10); HAL_GPIO_WritePin(GPIOG, GPIO_PIN_5, GPIO_PIN_SET); HAL_Delay(10); HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_SET); //使能的LCD背光 HAL_Delay(100); 2.将Layer层函数注释。MX_LTDC_Init完整代码如下: /* LTDC init function */ void MX_LTDC_Init(void) { /* USER CODE BEGIN LTDC_Init 0 */ /*点亮背光*/ HAL_GPIO_WritePin(GPIOG, GPIO_PIN_5, GPIO_PIN_RESET); HAL_Delay(10); HAL_GPIO_WritePin(GPIOG, GPIO_PIN_5, GPIO_PIN_SET); HAL_Delay(10); HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_SET); //使能的LCD背光 HAL_Delay(100); /* USER CODE END LTDC_Init 0 */ LTDC_LayerCfgTypeDef pLayerCfg = {0}; /* USER CODE BEGIN LTDC_Init 1 */ /* USER CODE END LTDC_Init 1 */ hltdc.Instance = LTDC; hltdc.Init.HSPolarity = LTDC_HSPOLARITY_AL; hltdc.Init.VSPolarity = LTDC_VSPOLARITY_AL; hltdc.Init.DEPolarity = LTDC_DEPOLARITY_AL; hltdc.Init.PCPolarity = LTDC_PCPOLARITY_IPC; hltdc.Init.HorizontalSync = 9; hltdc.Init.VerticalSync = 3; hltdc.Init.AccumulatedHBP = 19; hltdc.Init.AccumulatedVBP = 5; hltdc.Init.AccumulatedActiveW = 259; hltdc.Init.AccumulatedActiveH = 325; hltdc.Init.TotalWidth = 297; hltdc.Init.TotalHeigh = 327; hltdc.Init.Backcolor.Blue = 0; hltdc.Init.Backcolor.Green = 0; hltdc.Init.Backcolor.Red = 0; if (HAL_LTDC_Init(&hltdc) != HAL_OK) { Error_Handler(); } #if 0 pLayerCfg.WindowX0 = 0; pLayerCfg.WindowX1 = 240; pLayerCfg.WindowY0 = 0; pLayerCfg.WindowY1 = 320; pLayerCfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565; pLayerCfg.Alpha = 255; pLayerCfg.Alpha0 = 0; pLayerCfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_PAxCA; pLayerCfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_PAxCA; pLayerCfg.FBStartAdress = LCD_BUFFER; pLayerCfg.ImageWidth = 240; pLayerCfg.ImageHeight = 320; pLayerCfg.Backcolor.Blue = 0; pLayerCfg.Backcolor.Green = 0; pLayerCfg.Backcolor.Red = 0; if (HAL_LTDC_ConfigLayer(&hltdc, &pLayerCfg, 0) != HAL_OK) { Error_Handler(); } #endif /* USER CODE BEGIN LTDC_Init 2 */ /* USER CODE END LTDC_Init 2 */ } 3.在main函数主循环添加刷屏代码 /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MPU Configuration--------------------------------------------------------*/ MPU_Config(); /* Enable I-Cache---------------------------------------------------------*/ SCB_EnableICache(); /* Enable D-Cache---------------------------------------------------------*/ SCB_EnableDCache(); /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART3_UART_Init(); MX_LTDC_Init(); /* USER CODE BEGIN WHILE */ while (1) { //红 hltdc.Init.Backcolor.Blue = 0; hltdc.Init.Backcolor.Green = 0; hltdc.Init.Backcolor.Red = 255; HAL_LTDC_Init(&hltdc); HAL_Delay(1000); //绿 hltdc.Init.Backcolor.Blue = 0; hltdc.Init.Backcolor.Green = 255; hltdc.Init.Backcolor.Red = 0; HAL_LTDC_Init(&hltdc); HAL_Delay(1000); //蓝 hltdc.Init.Backcolor.Blue = 255; hltdc.Init.Backcolor.Green = 0; hltdc.Init.Backcolor.Red = 0; HAL_LTDC_Init(&hltdc); HAL_Delay(1000); //黄色 hltdc.Init.Backcolor.Blue = 0; hltdc.Init.Backcolor.Green = 255; hltdc.Init.Backcolor.Red = 255; HAL_LTDC_Init(&hltdc); HAL_Delay(1000); //紫色 hltdc.Init.Backcolor.Blue = 255; hltdc.Init.Backcolor.Green = 0; hltdc.Init.Backcolor.Red = 255; HAL_LTDC_Init(&hltdc); HAL_Delay(1000); //青色 hltdc.Init.Backcolor.Blue = 255; hltdc.Init.Backcolor.Green = 255; hltdc.Init.Backcolor.Red = 0; HAL_LTDC_Init(&hltdc); HAL_Delay(1000); //白色 hltdc.Init.Backcolor.Blue = 255; hltdc.Init.Backcolor.Green = 255; hltdc.Init.Backcolor.Red = 255; HAL_LTDC_Init(&hltdc); HAL_Delay(1000); } /* USER CODE END 3 */ } 四.测验       编译烧录,这里在STM32CubeMX IDE里在线烧录需要调试烧录器驱动是严格ST-LINK,之前准备的创新工坊PWLINK2设备驱动CMSIS-DAP,这里将编译的elf固件用脱机烧录工具即可。显示屏依次循环刷屏不同颜色。效果见视频。 [localvideo]6c6814ca014ea8a00fd33d6983b0fc48[/localvideo]       至此,实现LCD驱动刷屏。

  • 2025-02-21
  • 回复了主题帖: 恭喜LitchiCheng走马上任机器人开发版块版主!!

    加油加油

  • 2025-02-20
  • 回复了主题帖: 【匠芯创D133CBS】--6.LVGL界面移植

    jjppo 发表于 2025-1-16 23:03   大佬,请问这个错误怎么解决 这宏定义提示没定义,看下有没定义以及头文件包含

  • 回复了主题帖: 【嘉楠K230开发板】跌倒检测

    Jacktang 发表于 2025-2-17 07:28 这个项目可以养老、护理方面大有用处,好好整理一下做成实物 养老、护理方面确实有防跌倒/跌倒报警需求,方案可行是实物落地前提,有需要也可以搞

  • 回复了主题帖: 【嘉楠K230开发板】跌倒检测

    lugl4313820 发表于 2025-2-17 08:59 可以计算身体变化的时间吗?如果他是慢慢的自己倒下,会不会检测得出来? 检测锚定,状态变化计算时间,多目标动态侦别管理,往深处研究应该可以。 慢慢倒下可以的,可以一张图片镜头旋转检测。

  • 2025-02-16
  • 发表了主题帖: 【嘉楠K230开发板】跌倒检测

          本篇讲述使用K230进行跌倒检测。 一.原理流程       跌倒检测主要根据人体姿态来判断,可以用于老人、小孩跌倒监护。CanMV AI视觉框架和MicroPython编程能快速实现人体关键点检测。       编程思路流程如下:   二.代码实现与测验       迭代检测代码如下: ''' 跌倒检测 ''' from libs.PipeLine import PipeLine, ScopedTiming from libs.AIBase import AIBase from libs.AI2D import Ai2d import os import ujson from media.media import * from time import * import nncase_runtime as nn import ulab.numpy as np import time import utime import image import random import gc import sys import aicube # 自定义跌倒检测类,继承自AIBase基类 class FallDetectionApp(AIBase): def __init__(self, kmodel_path, model_input_size, labels, anchors, confidence_threshold=0.2, nms_threshold=0.5, nms_option=False, strides=[8,16,32], rgb888p_size=[224,224], display_size=[1920,1080], debug_mode=0): super().__init__(kmodel_path, model_input_size, rgb888p_size, debug_mode) # 调用基类的构造函数 self.kmodel_path = kmodel_path # 模型文件路径 self.model_input_size = model_input_size # 模型输入分辨率 self.labels = labels # 分类标签 self.anchors = anchors # 锚点数据,用于跌倒检测 self.strides = strides # 步长设置 self.confidence_threshold = confidence_threshold # 置信度阈值 self.nms_threshold = nms_threshold # NMS(非极大值抑制)阈值 self.nms_option = nms_option # NMS选项 self.rgb888p_size = [ALIGN_UP(rgb888p_size[0], 16), rgb888p_size[1]] # sensor给到AI的图像分辨率,并对宽度进行16的对齐 self.display_size = [ALIGN_UP(display_size[0], 16), display_size[1]] # 显示分辨率,并对宽度进行16的对齐 self.debug_mode = debug_mode # 是否开启调试模式 self.color = [(255,0, 0, 255), (255,0, 255, 0), (255,255,0, 0), (255,255,0, 255)] # 用于绘制不同类别的颜色 # Ai2d实例,用于实现模型预处理 self.ai2d = Ai2d(debug_mode) # 设置Ai2d的输入输出格式和类型 self.ai2d.set_ai2d_dtype(nn.ai2d_format.NCHW_FMT, nn.ai2d_format.NCHW_FMT, np.uint8, np.uint8) # 配置预处理操作,这里使用了pad和resize,Ai2d支持crop/shift/pad/resize/affine,具体代码请打开/sdcard/libs/AI2D.py查看 def config_preprocess(self, input_image_size=None): with ScopedTiming("set preprocess config", self.debug_mode > 0): # 计时器,如果debug_mode大于0则开启 ai2d_input_size = input_image_size if input_image_size else self.rgb888p_size # 初始化ai2d预处理配置,默认为sensor给到AI的尺寸,可以通过设置input_image_size自行修改输入尺寸 top, bottom, left, right = self.get_padding_param() # 获取padding参数 self.ai2d.pad([0, 0, 0, 0, top, bottom, left, right], 0, [0,0,0]) # 填充边缘 self.ai2d.resize(nn.interp_method.tf_bilinear, nn.interp_mode.half_pixel) # 缩放图像 self.ai2d.build([1,3,ai2d_input_size[1],ai2d_input_size[0]],[1,3,self.model_input_size[1],self.model_input_size[0]]) # 构建预处理流程 # 自定义当前任务的后处理,results是模型输出array的列表,这里使用了aicube库的anchorbasedet_post_process接口 def postprocess(self, results): with ScopedTiming("postprocess", self.debug_mode > 0): dets = aicube.anchorbasedet_post_process(results[0], results[1], results[2], self.model_input_size, self.rgb888p_size, self.strides, len(self.labels), self.confidence_threshold, self.nms_threshold, self.anchors, self.nms_option) return dets # 绘制检测结果到画面上 def draw_result(self, pl, dets): with ScopedTiming("display_draw", self.debug_mode > 0): if dets: pl.osd_img.clear() # 清除OSD图像 for det_box in dets: # 计算显示分辨率下的坐标 x1, y1, x2, y2 = det_box[2], det_box[3], det_box[4], det_box[5] w = (x2 - x1) * self.display_size[0] // self.rgb888p_size[0] h = (y2 - y1) * self.display_size[1] // self.rgb888p_size[1] x1 = int(x1 * self.display_size[0] // self.rgb888p_size[0]) y1 = int(y1 * self.display_size[1] // self.rgb888p_size[1]) x2 = int(x2 * self.display_size[0] // self.rgb888p_size[0]) y2 = int(y2 * self.display_size[1] // self.rgb888p_size[1]) # 绘制矩形框和类别标签 pl.osd_img.draw_rectangle(x1, y1, int(w), int(h), color=self.color[det_box[0]], thickness=2) pl.osd_img.draw_string_advanced(x1, y1-50, 32," " + self.labels[det_box[0]] + " " + str(round(det_box[1],2)), color=self.color[det_box[0]]) else: pl.osd_img.clear() # 获取padding参数 def get_padding_param(self): dst_w = self.model_input_size[0] dst_h = self.model_input_size[1] input_width = self.rgb888p_size[0] input_high = self.rgb888p_size[1] ratio_w = dst_w / input_width ratio_h = dst_h / input_high if ratio_w < ratio_h: ratio = ratio_w else: ratio = ratio_h new_w = int(ratio * input_width) new_h = int(ratio * input_high) dw = (dst_w - new_w) / 2 dh = (dst_h - new_h) / 2 top = int(round(dh - 0.1)) bottom = int(round(dh + 0.1)) left = int(round(dw - 0.1)) right = int(round(dw - 0.1)) return top, bottom, left, right if __name__ == "__main__": # 显示模式,默认"hdmi",可以选择"hdmi"和"lcd" display_mode="lcd" if display_mode=="hdmi": display_size=[1920,1080] else: display_size=[800,480] # 设置模型路径和其他参数 kmodel_path = "/sdcard/examples/kmodel/yolov5n-falldown.kmodel" confidence_threshold = 0.3 nms_threshold = 0.45 rgb888p_size = [1920, 1080] labels = ["Fall","NoFall"] # 模型输出类别名称 anchors = [10, 13, 16, 30, 33, 23, 30, 61, 62, 45, 59, 119, 116, 90, 156, 198, 373, 326] # anchor设置 # 初始化PipeLine,用于图像处理流程 pl = PipeLine(rgb888p_size=rgb888p_size, display_size=display_size, display_mode=display_mode) pl.create() # 初始化自定义跌倒检测实例 fall_det = FallDetectionApp(kmodel_path, model_input_size=[640, 640], labels=labels, anchors=anchors, confidence_threshold=confidence_threshold, nms_threshold=nms_threshold, nms_option=False, strides=[8,16,32], rgb888p_size=rgb888p_size, display_size=display_size, debug_mode=0) fall_det.config_preprocess() clock = time.clock() while True: clock.tick() img = pl.get_frame() # 获取当前帧数据 res = fall_det.run(img) # 推理当前帧 fall_det.draw_result(pl, res) # 绘制结果到PipeLine的osd图像 print(res) # 打印结果 pl.show_image() # 显示当前的绘制结果 gc.collect() # 垃圾回收 print(clock.fps()) #打印帧率       从代码我们可以看到用到/sdcard/examples/kmodel/yolov5n-falldown.kmodel 模型。FallDetectionApp初始化自定义跌倒检测实例。config_preprocess配置预处理。postprocess自定义当前任务的后处理。draw_result绘制检测结果到画面上。main函数中的res为识别结果。运行识别结果如下       这个对特定人群,在养老、护理方面等还是挺实用的。         至此,实现K230跌倒检测功能。

统计信息

已有1284人来访过

  • 芯积分:926
  • 好友:24
  • 主题:166
  • 回复:382

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言