当前位置: 首页 > news >正文

YOLOv12推理详解及部署实现

目录

    • 前言
    • 一、YOLOv12推理(Python)
      • 1. YOLOv12预测
      • 2. YOLOv12预处理
      • 3. YOLOv12后处理
      • 4. YOLOv12推理
    • 二、YOLOv12推理(C++)
      • 1. ONNX导出
      • 2. YOLOv12预处理
      • 3. YOLOv12后处理
      • 4. YOLOv12推理
    • 三、YOLOv12部署
      • 1. 源码下载
      • 2. 环境配置
        • 2.1 配置CMakeLists.txt
        • 2.2 配置Makefile
      • 3. ONNX导出
      • 4. 源码修改
      • 5. 运行
    • 结语
    • 下载链接
    • 参考

前言

水篇文章,本篇文章主要分享博主在实现 YOLOv12 推理和部署时的一些尝试,不涉及任何的原理性分析。若有问题欢迎各位看官批评指正🤗

paper:YOLOv12: Attention-Centric Real-Time Object Detectors

code:https://github.com/sunsmarterjie/yolov12

refer:https://github.com/shouxieai/tensorRT_Pro

deploy:https://github.com/Melody-Zhou/tensorRT_Pro-YOLOv8

在这里插入图片描述

一、YOLOv12推理(Python)

YOLOv12 环境配置流程如下:

git clone https://github.com/sunsmarterjie/yolov12.git
cd yolov12
wget https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.3/flash_attn-2.7.3+cu11torch2.2cxx11abiFALSE-cp311-cp311-linux_x86_64.whl
conda create -n yolov12 python=3.11
conda activate yolov12
pip install -r requirements.txt
pip install -e .

Note:由于 YOLOv12 是基于注意力机制的 YOLO 框架,有使用到 flash-attention,需要提前下载,大家可以点击 here 下载博主准备好的包

1. YOLOv12预测

我们先尝试利用官方预训练权重来推理一张图片并保持,看能否成功

在 YOLOv12 主目录下新建 predict.py 预测脚本,其内容如下:

from ultralytics import YOLOmodel = YOLO('yolov12s.pt')
model.predict(save=True)

执行该脚本后,输出如下:

在这里插入图片描述

可以看到成功推理了,推理后的图片保持在 runs/detect/predict 文件夹下,如下所示:

在这里插入图片描述

2. YOLOv12预处理

模型预测成功后我们就需要自己动手来写下 YOLOv12 的预处理和后处理,方便后续在 C++ 上的实现,我们先来看看预处理的实现。

经过我们的调试分析可知 YOLOv12 的预处理过程在 yolov12/ultralytics/engine/predictor.py 文件中,可以参考:predictor.py#L116

代码如下:

def preprocess(self, im):"""Prepares input image before inference.Args:im (torch.Tensor | List(np.ndarray)): BCHW for tensor, [(HWC) x B] for list."""not_tensor = not isinstance(im, torch.Tensor)if not_tensor:im = np.stack(self.pre_transform(im))im = im[..., ::-1].transpose((0, 3, 1, 2))  # BGR to RGB, BHWC to BCHW, (n, 3, h, w)im = np.ascontiguousarray(im)  # contiguousim = torch.from_numpy(im)im = im.to(self.device)im = im.half() if self.model.fp16 else im.float()  # uint8 to fp16/32if not_tensor:im /= 255  # 0 - 255 to 0.0 - 1.0return im

它包含以下步骤:

  • self.pre_transform:即 letterbox 添加灰条
  • im[…,::-1]:BGR → RGB
  • transpose((0, 3, 1, 2)):添加 batch 维度,HWC → CHW
  • torch.from_numpy:to Tensor
  • im /= 255:除以 255,归一化

大家如果对 YOLOv5 的预处理熟悉的话,会发现 YOLOv12 的预处理和 YOLOv5 的预处理一模一样,因此我们不难写出对应的预处理代码,如下所示:

def preprocess_warpAffine(image, dst_width=640, dst_height=640):scale = min((dst_width / image.shape[1], dst_height / image.shape[0]))ox = (dst_width  - scale * image.shape[1]) / 2oy = (dst_height - scale * image.shape[0]) / 2M = np.array([[scale, 0, ox],[0, scale, oy]], dtype=np.float32)img_pre = cv2.warpAffine(image, M, (dst_width, dst_height), flags=cv2.INTER_LINEAR,borderMode=cv2.BORDER_CONSTANT, borderValue=(114, 114, 114))IM = cv2.invertAffineTransform(M)img_pre = (img_pre[...,::-1] / 255.0).astype(np.float32)img_pre = img_pre.transpose(2, 0, 1)[None]img_pre = torch.from_numpy(img_pre)return img_pre, IM

其中的 letterbox 添加灰条步骤我们可以通过仿射变换 warpAffine 实现,warpAffine 非常适合在 CUDA 上加速,关于 warpAffine 仿射变换的细节大家可以参考 YOLOv5推理详解及预处理高性能实现,这边不再赘述。其它步骤倒是和官方的没有区别。

值得注意的是,letterbox 的操作是先将长边缩放到 640,再将短边按比例缩放,同时确保缩放后的短边能整除 32,如果不能则向上取整多余部分填充。warpAffine 的操作则是将图像分辨率固定在 640x640,多余部分添加灰条,博主对一张 1080x810 分辨率的图像经过两种不同预处理后的结果进行了对比,如下图所示:

在这里插入图片描述

LetterBox预处理图像

在这里插入图片描述

warpAffine预处理图像

可以看到二者明显的差别,letterbox 中没有灰条,因为长边缩放到 640 后短边刚好缩放到 480,能整除 32。而 warpAffine 则是固定分辨率 640x640,因此短边多余部分将用灰条填充。

warpAffine 预处理方法将图像分辨率固定在 640x640,主要有以下几点考虑:(from chatGPT)

  • 简化处理逻辑:所有预处理后的图像分辨率相同,可以简化 CUDA 中并行处理的逻辑,使得代码更易于编写和维护。
  • 优化内存访问:在 GPU 上,连续的内存访问模式通常比非连续的访问更高效。如果所有图像具有相同的大小和布局,这可以帮助优化内存访问,提高处理速度。
  • 避免动态内存分配:动态内存分配和释放是昂贵的操作,特别是在 GPU 上。固定分辨率意味着可以预先分配足够的内存,而不需要根据每个图像的大小动态调整内存大小。

这两种不同的预处理方法生成的图片输入到神经网络时的维度不同,letterbox 的输入是 torch.Size([1, 3, 640, 480]),warpAffine 的输入是 torch.Size([1, 3, 640, 640])。由于输入维度不同将导致模型输出维度的差异,leetrbox 的输出是 torch.Size([1, 84, 6300]) 只有 6300 个框,而 warpAffine 的输出是 torch.Size([1, 84, 8400]) 有 8400 个框,这点大家需要清楚。

3. YOLOv12后处理

我们再来看看后处理的实现

经过我们的调试分析可知 YOLOv12 的后处理过程在 ultralytics/models/yolo/detect/predict.py 文件中,可以参考:detect/predict.py#L23

# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/licensefrom ultralytics.engine.predictor import BasePredictor
from ultralytics.engine.results import Results
from ultralytics.utils import opsclass DetectionPredictor(BasePredictor):"""A class extending the BasePredictor class for prediction based on a detection model.Example:```pythonfrom ultralytics.utils import ASSETSfrom ultralytics.models.yolo.detect import DetectionPredictorargs = dict(model="yolo11n.pt", source=ASSETS)predictor = DetectionPredictor(overrides=args)predictor.predict_cli()```"""def postprocess(self, preds, img, orig_imgs):"""Post-processes predictions and returns a list of Results objects."""preds = ops.non_max_suppression(preds,self.args.conf,self.args.iou,agnostic=self.args.agnostic_nms,max_det=self.args.max_det,classes=self.args.classes,)if not isinstance(orig_imgs, list):  # input images are a torch.Tensor, not a listorig_imgs = ops.convert_torch2numpy_batch(orig_imgs)results = []for pred, orig_img, img_path in zip(preds, orig_imgs, self.batch[0]):pred[:, :4] = ops.scale_boxes(img.shape[2:], pred[:, :4], orig_img.shape)results.append(Results(orig_img, path=img_path, names=self.model.names, boxes=pred))return results

它包含以下步骤:

  • ops.non_max_suppression:非极大值抑制,即 NMS
  • ops.scale_boxes:框的解码,即 decode boxes

大家如果对 YOLOv8 的后处理熟悉的话,会发现 YOLOv12 的后处理和 YOLOv8 的后处理一模一样,因此我们不难写出对应的后处理代码,如下所示:

def iou(box1, box2):def area_box(box):return (box[2] - box[0]) * (box[3] - box[1])left   = max(box1[0], box2[0])top    = max(box1[1], box2[1])right  = min(box1[2], box2[2])bottom = min(box1[3], box2[3])cross  = max((right-left), 0) * max((bottom-top), 0)union  = area_box(box1) + area_box(box2) - crossif cross == 0 or union == 0:return 0return cross / uniondef NMS(boxes, iou_thres):remove_flags = [False] * len(boxes)keep_boxes = []for i, ibox in enumerate(boxes):if remove_flags[i]:continuekeep_boxes.append(ibox)for j in range(i + 1, len(boxes)):if remove_flags[j]:continuejbox = boxes[j]if(ibox[5] != jbox[5]):continueif iou(ibox, jbox) > iou_thres:remove_flags[j] = Truereturn keep_boxesdef postprocess(pred, IM=[], conf_thres=0.25, iou_thres=0.45):# 输入是模型推理的结果,即8400个预测框# 1,8400,84 [cx,cy,w,h,class*80]boxes = []for item in pred[0]:cx, cy, w, h = item[:4]label = item[4:].argmax()confidence = item[4 + label]if confidence < conf_thres:continueleft    = cx - w * 0.5top     = cy - h * 0.5right   = cx + w * 0.5bottom  = cy + h * 0.5boxes.append([left, top, right, bottom, confidence, label])boxes = np.array(boxes)lr = boxes[:,[0, 2]]tb = boxes[:,[1, 3]]boxes[:,[0,2]] = IM[0][0] * lr + IM[0][2]boxes[:,[1,3]] = IM[1][1] * tb + IM[1][2]boxes = sorted(boxes.tolist(), key=lambda x:x[4], reverse=True)return NMS(boxes, iou_thres)

其中预测框的解码我们是通过仿射变换逆矩阵 IM 实现的,关于 IM 的细节大家可以参考 YOLOv5推理详解及预处理高性能实现,这边不再赘述。关于 NMS 的代码参考自 tensorRT_Pro 中的实现:yolo.cpp#L119

对于一张 640x640 的图片来说,YOLO11 预测框的总数量是 8400 ,每个预测框的维度是 84(针对 COCO 数据集的 80 个类别而言)

8400 × 84 = 80 × 80 × 84 + 40 × 40 × 84 + 20 × 20 × 84 = 80 × 80 × ( 4 + 80 ) + 40 × 40 × ( 4 + 80 ) + 20 × 20 × ( 4 + 80 ) \begin{aligned} 8400\times84&=80\times80\times84+40\times40\times84+20\times20\times84\\ &=80\times80\times(4+80)+40\times40\times(4+80)+20\times20\times(4+80) \end{aligned} 8400×84=80×80×84+40×40×84+20×20×84=80×80×(4+80)+40×40×(4+80)+20×20×(4+80)

其中的 4 对应的是 cx, cy, w, h,分别代表的含义是边界框中心点坐标、宽高;80 对应的是 COCO 数据集中的 80 个类别置信度。

4. YOLOv12推理

通过上面对 YOLOv12 的预处理和后处理分析之后,整个推理过程就显而易见了。YOLOv12 的推理包括图像预处理、模型推理、预测结果后处理三部分,其中预处理主要包括 warpAffine 仿射变换,后处理主要包括 decode 解码和 NMS 两部分。

完整的推理代码如下:

import cv2
import torch
import numpy as np
from ultralytics.data.augment import LetterBox
from ultralytics.nn.autobackend import AutoBackenddef preprocess_letterbox(image):letterbox = LetterBox(new_shape=640, stride=32, auto=True)image = letterbox(image=image)image = (image[..., ::-1] / 255.0).astype(np.float32) # BGR to RGB, 0 - 255 to 0.0 - 1.0image = image.transpose(2, 0, 1)[None]  # BHWC to BCHW (n, 3, h, w)image = torch.from_numpy(image)return imagedef preprocess_warpAffine(image, dst_width=640, dst_height=640):scale = min((dst_width / image.shape[1], dst_height / image.shape[0]))ox = (dst_width  - scale * image.shape[1]) / 2oy = (dst_height - scale * image.shape[0]) / 2M = np.array([[scale, 0, ox],[0, scale, oy]], dtype=np.float32)img_pre = cv2.warpAffine(image, M, (dst_width, dst_height), flags=cv2.INTER_LINEAR,borderMode=cv2.BORDER_CONSTANT, borderValue=(114, 114, 114))IM = cv2.invertAffineTransform(M)img_pre = (img_pre[...,::-1] / 255.0).astype(np.float32)img_pre = img_pre.transpose(2, 0, 1)[None]img_pre = torch.from_numpy(img_pre)return img_pre, IMdef iou(box1, box2):def area_box(box):return (box[2] - box[0]) * (box[3] - box[1])left   = max(box1[0], box2[0])top    = max(box1[1], box2[1])right  = min(box1[2], box2[2])bottom = min(box1[3], box2[3])cross  = max((right-left), 0) * max((bottom-top), 0)union  = area_box(box1) + area_box(box2) - crossif cross == 0 or union == 0:return 0return cross / uniondef NMS(boxes, iou_thres):remove_flags = [False] * len(boxes)keep_boxes = []for i, ibox in enumerate(boxes):if remove_flags[i]:continuekeep_boxes.append(ibox)for j in range(i + 1, len(boxes)):if remove_flags[j]:continuejbox = boxes[j]if(ibox[5] != jbox[5]):continueif iou(ibox, jbox) > iou_thres:remove_flags[j] = Truereturn keep_boxesdef postprocess(pred, IM=[], conf_thres=0.25, iou_thres=0.45):# 输入是模型推理的结果,即8400个预测框# 1,8400,84 [cx,cy,w,h,class*80]boxes = []for item in pred[0]:cx, cy, w, h = item[:4]label = item[4:].argmax()confidence = item[4 + label]if confidence < conf_thres:continueleft    = cx - w * 0.5top     = cy - h * 0.5right   = cx + w * 0.5bottom  = cy + h * 0.5boxes.append([left, top, right, bottom, confidence, label])boxes = np.array(boxes)lr = boxes[:,[0, 2]]tb = boxes[:,[1, 3]]boxes[:,[0,2]] = IM[0][0] * lr + IM[0][2]boxes[:,[1,3]] = IM[1][1] * tb + IM[1][2]boxes = sorted(boxes.tolist(), key=lambda x:x[4], reverse=True)return NMS(boxes, iou_thres)def hsv2bgr(h, s, v):h_i = int(h * 6)f = h * 6 - h_ip = v * (1 - s)q = v * (1 - f * s)t = v * (1 - (1 - f) * s)r, g, b = 0, 0, 0if h_i == 0:r, g, b = v, t, pelif h_i == 1:r, g, b = q, v, pelif h_i == 2:r, g, b = p, v, telif h_i == 3:r, g, b = p, q, velif h_i == 4:r, g, b = t, p, velif h_i == 5:r, g, b = v, p, qreturn int(b * 255), int(g * 255), int(r * 255)def random_color(id):h_plane = (((id << 2) ^ 0x937151) % 100) / 100.0s_plane = (((id << 3) ^ 0x315793) % 100) / 100.0return hsv2bgr(h_plane, s_plane, 1)if __name__ == "__main__":img = cv2.imread("ultralytics/assets/bus.jpg")# img_pre = preprocess_letterbox(img)img_pre, IM = preprocess_warpAffine(img)model  = AutoBackend(weights="yolov12s.pt")names  = model.namesresult = model(img_pre)[0].transpose(-1, -2)  # 1,8400,84boxes  = postprocess(result, IM)for obj in boxes:left, top, right, bottom = int(obj[0]), int(obj[1]), int(obj[2]), int(obj[3])confidence = obj[4]label = int(obj[5])color = random_color(label)cv2.rectangle(img, (left, top), (right, bottom), color=color ,thickness=2, lineType=cv2.LINE_AA)caption = f"{names[label]} {confidence:.2f}"w, h = cv2.getTextSize(caption, 0, 1, 2)[0]cv2.rectangle(img, (left - 3, top - 33), (left + w + 10, top), color, -1)cv2.putText(img, caption, (left, top - 5), 0, 1, (0, 0, 0), 2, 16)cv2.imwrite("infer.jpg", img)print("save done")  

推理效果如下图:

在这里插入图片描述

至此,我们在 Python 上面完成了 YOLO11 的整个推理过程,下面我们去 C++ 上实现

二、YOLOv12推理(C++)

C++ 上的实现我们使用的 repo 依旧是 tensorRT_Pro,现在我们就基于 tensorRT_Pro 完成 YOLO12 在 C++ 上的推理

1. ONNX导出

首先我们需要将 YOLOv12 模型导出为 ONNX,为了适配 tensorRT_Pro 我们需要做一些修改

  • 修改输出节点名为 output,输入输出只让 batch 维度动态,宽高不动态
  • 增加 transpose 节点交换输出的 2、3 维度

具体修改如下:

1. 在 ultralytics/engine/exporter.py 文件中改动一处

  • 499 行:输出节点名修改为 output
  • 502 行:输入只让 batch 维度动态,宽高不动态
  • 507 行:输出只让 batch 维度动态,宽高不动态
# ========== exporter.py ==========# ultralytics/engine/exporter.py第499行# output_names = ["output0", "output1"] if isinstance(self.model, SegmentationModel) else ["output0"]
# dynamic = self.args.dynamic
# if dynamic:
#     dynamic = {"images": {0: "batch", 2: "height", 3: "width"}}  # shape(1,3,640,640)
#     if isinstance(self.model, SegmentationModel):
#         dynamic["output0"] = {0: "batch", 2: "anchors"}  # shape(1, 116, 8400)
#         dynamic["output1"] = {0: "batch", 2: "mask_height", 3: "mask_width"}  # shape(1,32,160,160)
#     elif isinstance(self.model, DetectionModel):
#         dynamic["output0"] = {0: "batch", 2: "anchors"}  # shape(1, 84, 8400)
# 修改为:output_names = ["output0", "output1"] if isinstance(self.model, SegmentationModel) else ["output"]
dynamic = self.args.dynamic
if dynamic:dynamic = {"images": {0: "batch"}}  # shape(1,3,640,640)if isinstance(self.model, SegmentationModel):dynamic["output0"] = {0: "batch", 2: "anchors"}  # shape(1, 116, 8400)dynamic["output1"] = {0: "batch", 2: "mask_height", 3: "mask_width"}  # shape(1,32,160,160)elif isinstance(self.model, DetectionModel):dynamic["output0"] = {0: "batch"}  # shape(1, 84, 8400)

2. 在 ultralytics/nn/modules/head.py 文件中改动一处

  • 74 行:添加 transpose 节点交换输出的第 2 和第 3 维度
# ========== head.py ==========# ultralytics/nn/modules/head.py第74行,forward函数
# return y if self.export else (y, x)
# 修改为:return y.permute(0, 2, 1) if self.export else (y, x)

以上就是为了适配 tensorRT_Pro 而做出的代码修改,修改好以后,将预训练权重 yolov12s.pt 放在 yolov12 主目录下,新建导出文件 export.py,内容如下:

from ultralytics import YOLOmodel = YOLO('yolov12s.pt')model.export(format="onnx", dynamic=True)

在终端执行如下指令即可完成 onnx 导出:

python export.py

导出过程如下图所示:

在这里插入图片描述

可以看到导出的 pytorch 模型的输入 shape 是 1x3x640x640,输出 shape 是 1x8400x84,符合我们的预期。

导出成功后会在当前目录下生成 yolov12s.onnx 模型,我们可以使用 Netron 可视化工具查看,如下图所示:

在这里插入图片描述

在这里插入图片描述

可以看到输入节点名是 images,维度是 batchx3x640x640,保证只有 batch 维度动态,输出节点名是 output,维度是 batchx8400x84,保证只有 batch 维度动态,符合 tensorRT_Pro 的格式。另外整个模型结构比较简单,没有什么奇怪的算子

2. YOLOv12预处理

之前有提到过 YOLOv12 预处理部分和 YOLOv5 实现一模一样,因此我们在 tensorRT_Pro 中 YOLOv12 模型的预处理可以直接使用 YOLOv5 的预处理

tensorRT_Pro 中预处理的代码如下:

__global__ void warp_affine_bilinear_and_normalize_plane_kernel(uint8_t* src, int src_line_size, int src_width, int src_height, float* dst, int dst_width, int dst_height, uint8_t const_value_st, float* warp_affine_matrix_2_3, Norm norm, int edge){int position = blockDim.x * blockIdx.x + threadIdx.x;if (position >= edge) return;float m_x1 = warp_affine_matrix_2_3[0];float m_y1 = warp_affine_matrix_2_3[1];float m_z1 = warp_affine_matrix_2_3[2];float m_x2 = warp_affine_matrix_2_3[3];float m_y2 = warp_affine_matrix_2_3[4];float m_z2 = warp_affine_matrix_2_3[5];int dx      = position % dst_width;int dy      = position / dst_width;float src_x = m_x1 * dx + m_y1 * dy + m_z1;float src_y = m_x2 * dx + m_y2 * dy + m_z2;float c0, c1, c2;if(src_x <= -1 || src_x >= src_width || src_y <= -1 || src_y >= src_height){// out of rangec0 = const_value_st;c1 = const_value_st;c2 = const_value_st;}else{int y_low = floorf(src_y);int x_low = floorf(src_x);int y_high = y_low + 1;int x_high = x_low + 1;uint8_t const_value[] = {const_value_st, const_value_st, const_value_st};float ly    = src_y - y_low;float lx    = src_x - x_low;float hy    = 1 - ly;float hx    = 1 - lx;float w1    = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx;uint8_t* v1 = const_value;uint8_t* v2 = const_value;uint8_t* v3 = const_value;uint8_t* v4 = const_value;if(y_low >= 0){if (x_low >= 0)v1 = src + y_low * src_line_size + x_low * 3;if (x_high < src_width)v2 = src + y_low * src_line_size + x_high * 3;}if(y_high < src_height){if (x_low >= 0)v3 = src + y_high * src_line_size + x_low * 3;if (x_high < src_width)v4 = src + y_high * src_line_size + x_high * 3;}// same to opencvc0 = floorf(w1 * v1[0] + w2 * v2[0] + w3 * v3[0] + w4 * v4[0] + 0.5f);c1 = floorf(w1 * v1[1] + w2 * v2[1] + w3 * v3[1] + w4 * v4[1] + 0.5f);c2 = floorf(w1 * v1[2] + w2 * v2[2] + w3 * v3[2] + w4 * v4[2] + 0.5f);}if(norm.channel_type == ChannelType::Invert){float t = c2;c2 = c0;  c0 = t;}if(norm.type == NormType::MeanStd){c0 = (c0 * norm.alpha - norm.mean[0]) / norm.std[0];c1 = (c1 * norm.alpha - norm.mean[1]) / norm.std[1];c2 = (c2 * norm.alpha - norm.mean[2]) / norm.std[2];}else if(norm.type == NormType::AlphaBeta){c0 = c0 * norm.alpha + norm.beta;c1 = c1 * norm.alpha + norm.beta;c2 = c2 * norm.alpha + norm.beta;}int area = dst_width * dst_height;float* pdst_c0 = dst + dy * dst_width + dx;float* pdst_c1 = pdst_c0 + area;float* pdst_c2 = pdst_c1 + area;*pdst_c0 = c0;*pdst_c1 = c1;*pdst_c2 = c2;
}

关于预处理部分其实就是调用了上述 CUDA 核函数来实现 warpAffine,由于在 CUDA 中我们是对每个像素进行操作,因此非常容易实现 BGR → RGB,/255.0 等操作。关于代码的具体分析可以参考 YOLOv5推理详解及预处理高性能实现,这边不再赘述。

3. YOLOv12后处理

之前有提到过 YOLOv12 后处理部分和 YOLOv8 相同,和 YOLOv5 基本相似,YOLOv8 和 YOLO11 都是基于 anchor-free 的,因此对于 decode 解码部分我们需要进行简单调整,代码可参考:yolo.cu#L129

因此我们不难写出 YOLOv12 的 decode 解码部分的实现代码,如下所示:

static __global__ void decode_kernel(float *predict, int num_bboxes, int num_classes, float confidence_threshold, float* invert_affine_matrix, float* parray, int MAX_IMAGE_BOXES){int position = blockDim.x * blockIdx.x + threadIdx.x;if (position >= num_bboxes) return;float* pitem            = predict + (4 + num_classes) * position;float* class_confidence = pitem + 4;float confidence        = *class_confidence++;int label               = 0;for(int i = 1; i < num_classes; ++i, ++class_confidence){if(*class_confidence > confidence){confidence = *class_confidence;label      = i;}}if(confidence < confidence_threshold)return;int index = atomicAdd(parray, 1);if(index >= MAX_IMAGE_BOXES)return;float cx         = *pitem++;float cy         = *pitem++;float width      = *pitem++;float height     = *pitem++;float left   = cx - width  * 0.5f;float top    = cy - height * 0.5f;float right  = cx + width  * 0.5f;float bottom = cy + height * 0.5f;affine_project(invert_affine_matrix, left,  top,    &left,  &top);affine_project(invert_affine_matrix, right, bottom, &right, &bottom);float *pout_item = parray + 1 + index * NUM_BOX_ELEMENT;*pout_item++ = left;*pout_item++ = top;*pout_item++ = right;*pout_item++ = bottom;*pout_item++ = confidence;*pout_item++ = label;*pout_item++ = 1;  // 1 = keep, 0 = ignore
}

关于 decode 的具体实现其实是启动多个线程,每个线程处理一个框的解码,我们会通过仿射变换逆矩阵 IM 将坐标映射回原图上,关于 decode 代码的详细分析可参考 infer源码阅读之yolo.cu,这边不再赘述,另外关于 NMS 部分的实现无需修改,其具体实现可以参考:yolo_decode.cu#L81

4. YOLOv12推理

通过上面对 YOLOv12 的预处理和后处理分析之后,整个推理过程就显而易见了。C++ 上 YOLOv12 的预处理部分可直接沿用 YOLOv5 的预处理,后处理中的 decode 解码部分需要简单修改,NMS 部分无需修改。

我们在终端执行如下指令即可完成推理(注意!完整流程博主会在后续内容介绍,这边只是简单演示

make yolo -j64

编译图解如下所示:

在这里插入图片描述

推理结果如下图所示:

在这里插入图片描述

至此,我们在 C++ 上面完成了 YOLOv12 的整个推理过程,下面我们将完整的走一遍流程

三、YOLOv12部署

博主新建了一个仓库 tensorRT_Pro-YOLOv8,该仓库基于 shouxieai/tensorRT_Pro,并进行了调整以支持 YOLOv8 的各项任务,目前已支持分类、检测、分割、姿态点估计任务。

下面我们就来具体看看如何利用 tensorRT_Pro-YOLOv8 这个 repo 完成 YOLOv12 的推理。

1. 源码下载

tensorRT_Pro-YOLOv8 的代码可以直接从 GitHub 官网上下载,源码下载地址是 https://github.com/Melody-Zhou/tensorRT_Pro-YOLOv8,Linux 下代码克隆指令如下:

git clone https://github.com/Melody-Zhou/tensorRT_Pro-YOLOv8.git

也可手动点击下载,点击右上角的 Code 按键,将代码下载下来。至此整个项目就已经准备好了。也可以点击 here 下载博主准备好的源代码(注意代码下载于 2025/2/19 日,若有改动请参考最新

2. 环境配置

需要使用的软件环境有 TensorRT、CUDA、cuDNN、OpenCV、Protobuf,所有软件环境的安装可以参考 Ubuntu20.04软件安装大全,这里不再赘述,需要各位看官自行配置相关环境😄,外网访问较慢,这里提供博主安装过程中的软件安装包下载链接 Baidu Drive【pwd:yolo】🚀🚀🚀

tensorRT_Pro-YOLOv8 提供 CMakeLists.txt 和 Makefile 两种编译方式,二者选一即可

2.1 配置CMakeLists.txt

主要修改五处

1. 修改第 13 行,修改 OpenCV 路径

set(OpenCV_DIR   "/usr/local/include/opencv4")

2. 修改第 15 行,修改 CUDA 路径

set(CUDA_TOOLKIT_ROOT_DIR     "/usr/local/cuda-11.6")

3. 修改第 16 行,修改 cuDNN 路径

set(CUDNN_DIR    "/usr/local/cudnn8.4.0.27-cuda11.6")

4. 修改第 17 行,修改 tensorRT 路径

set(TENSORRT_DIR "/opt/TensorRT-8.4.1.5")

5. 修改第 20 行,修改 protobuf 路径

set(PROTOBUF_DIR "/home/jarvis/protobuf")
2.2 配置Makefile

主要修改五处

1. 修改第 4 行,修改 protobuf 路径

lean_protobuf  := /home/jarvis/protobuf

2. 修改第 5 行,修改 tensorRT 路径

lean_tensor_rt := /opt/TensorRT-8.4.1.5

3. 修改第 6 行,修改 cuDNN 路径

lean_cudnn     := /usr/local/cudnn8.4.0.27-cuda11.6

4. 修改第 7 行,修改 OpenCV 路径

lean_opencv    := /usr/local

5. 修改第 8 行,修改 CUDA 路径

lean_cuda      := /usr/local/cuda-11.6

3. ONNX导出

导出细节可以查看之前的内容,这边不再赘述。记得将导出的 ONNX 模型放在 tensorRT_Pro-YOLOv8/workspace 文件夹下。

4. 源码修改

如果你想推理自己训练的模型还需要修改下源代码,YOLOv12 模型的推理代码在 app_yolo.cpp 文件中,我们就只需要修改这一个文件中的内容即可,源码修改较简单主要有以下几点:

  • 1. app_yolo.cpp 277 行,注释
  • 2. app_yolo.cpp 288 行,取消注释,“yolov12s” 修改为你导出的 ONNX 模型名
  • 3. app_yolo.cpp 11 行,将 cocolabels 数组中的类别名词修改为你训练的类别

具体修改示例如下:

// test(Yolo::Type::V8, TRT::Mode::FP32, "yolov8s")	// 修改1 277行注释test(Yolo::Type::V12, TRT::Mode::FP32, "best");	// 修改2 288行取消注释,"yolov12s"改成"best"static const char *cocolabels[] = {"have_mask", "no_mask"};	// 修改2 11行修改检测类别,为自训练模型的类别名称

5. 运行

OK!源码修改好了,Makefile 编译文件也搞定了,ONNX 模型也准备好了,现在可以编译运行了,直接在终端执行如下指令即可:

make yolo

编译过程如下所示:

在这里插入图片描述

编译运行成功后在 workspace 文件夹下会生成 engine 文件 yolov12s.FP32.trtmodel 用于模型推理,同时它还会生成 yolov12s_YoloV12_FP32_result 文件夹,该文件夹下保存了推理的图片。

模型推理效果如下图所示:

在这里插入图片描述

OK!以上就是使用 tensorRT_Pro-YOLOv8 推理 YOLOv12 的大致流程,若有问题,欢迎各位看官批评指正。

结语

博主在这里针对 YOLOv12 的预处理和后处理做了简单分析,同时与大家分享了 C++ 上的实现流程,目的是帮大家理清思路,更好的完成后续的部署工作😄。感谢各位看到最后,创作不易,读后有收获的看官请帮忙点个👍⭐️

最后大家如果觉得 tensorRT_Pro-YOLOv8 这个 repo 对你有帮助的话,不妨点个 ⭐️ 支持一波,这对博主来说非常重要,感谢各位🙏。

下载链接

  • 软件安装包下载链接【提取码:yolo】🚀🚀🚀
  • 源代码、权重下载链接【提取码:1234】

参考

  • https://github.com/shouxieai/infer
  • https://github.com/sunsmarterjie/yolov12
  • https://github.com/shouxieai/tensorRT_Pro
  • https://github.com/Melody-Zhou/tensorRT_Pro-YOLOv8
  • YOLOv5推理详解及预处理高性能实现

相关文章:

YOLOv12推理详解及部署实现

目录 前言一、YOLOv12推理(Python)1. YOLOv12预测2. YOLOv12预处理3. YOLOv12后处理4. YOLOv12推理 二、YOLOv12推理(C)1. ONNX导出2. YOLOv12预处理3. YOLOv12后处理4. YOLOv12推理 三、YOLOv12部署1. 源码下载2. 环境配置2.1 配置CMakeLists.txt2.2 配置Makefile 3. ONNX导出…...

docker 安装的open-webui链接ollama出现网络错误

# 故事背景 部署完ollama以后&#xff0c;使用谷歌浏览器的插件Page Assist - 本地 AI 模型的 Web UI 可以比较流畅的使用DeepSeek&#xff0c;但是只局限于个人使用&#xff0c;想分享给更多的小伙伴使用&#xff0c;于是打算使用open-webui 来管理用户&#xff0c;经官网推荐…...

【大模型】DeepSeek 的人工智能发展之路

【大模型】DeepSeek 的人工智能发展之路 初出茅庐&#xff1a;成立与奠基&#xff08;2023 年&#xff09;崭露头角&#xff1a;大模型的初步发布&#xff08;2024 年&#xff09;首个大模型 DeepSeek LLM 发布&#xff08;2024 年 1 月 5 日&#xff09;开源第二代 MoE 大模型…...

关于Unity的一些基础知识点汇总

1.Prefab实例化后&#xff0c;哪些资源是共用的&#xff1f;哪些资源是拷贝的&#xff1f; 共用资源 脚本组件&#xff1a;实例化后的 Prefab 共享脚本组件的代码。若脚本中无状态数据&#xff0c;多个实例对脚本方法的调用会有相同逻辑。比如一个控制物体移动的脚本&#xff0…...

Java 大视界 -- 企业数字化转型中的 Java 大数据战略与实践(93)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…...

Compose常用UI组件

Compose常用UI组件 概述Modifier 修饰符常用Modifier修饰符作用域限定Modifier Modifier 实现原理Modifier.Element链的构建链的解析 常用基础组件文字组件图片组件按钮组件选择器对话框进度条 常用布局组件线性布局帧布局 列表组件 概述 Compose 预置了很多基础组件&#xff…...

【EndNote】WPS 导入EndNote 21

写在前面&#xff1a;有没有人有激活码&#xff0c;跪求&#xff01; EndNote&#xff0c;在文献管理和文献引用方面很好用。写文章的时候&#xff0c;使用EndNote引入需要的文献会很方便。我目前用的WPS&#xff0c;想把EndNote的CWYW&#xff08;Cite While You Write&#…...

【Spring详解三】默认标签的解析

三、默认标签的解析 Spring的标签中有 默认标签和 自定义标签&#xff0c;两者的解析有着很大的不同&#xff0c;这次重点说默认标签的解析过程。 DefaultBeanDefinitionDocumentReader.class 默认标签的解析是在 DefaultBeanDefinitionDocumentReader.parseDefaultElement()函…...

leetcode 2585. 获得分数的方法数

题目如下 数据范围 莫要被困难的外衣骗了&#xff0c;本题就是有数量限制的完全背包问题。显然我们可以令 f(x,y)为当有x种题目时分数为y时的方法数 令某种题目的数量为k 那么方法数应该是 f(x,y) f(x - 1,y - k * (分值))其中(0 < k < 题目数量)通过代码 class So…...

Java Spring boot 篇:常用注解

Configuration 作用 Configuration 注解的核心作用是把一个类标记为 Spring 应用上下文里的配置类。配置类就像一个 Java 版的 XML 配置文件&#xff0c;能够在其中定义 Bean 定义和 Bean 之间的依赖关系。当 Spring 容器启动时&#xff0c;会扫描这些配置类&#xff0c;解析其…...

检索增强生成(RAG)技术应用方案设计

检索增强生成&#xff08;RAG&#xff09;技术应用方案设计 目录 引言背景分析核心技术原理应用领域与案例分析设计方案 5.1 设计目标5.2 技术路线5.3 实施步骤 风险评估与应对措施预期效果与长远展望总结 1. 引言 随着人工智能技术的飞速发展&#xff0c;大型语言模型&…...

【JavaEE进阶】数据库连接池

目录 &#x1f334;数据库连接池 &#x1f38b;数据库连接池的使用 &#x1f332;MySQL企业开发规范 &#x1f334;数据库连接池 数据库连接池负责分配、管理和释放数据库连接&#xff0c;它允许应⽤程序重复使⽤⼀个现有的数据库连接&#xff0c;⽽不是再重新建⽴⼀个. 没…...

意图识别概述

在当今的人工智能领域&#xff0c;意图识别技术是自然语言处理&#xff08;NLP&#xff09;中的一个重要分支&#xff0c;它使得机器能够理解人类语言背后的目的或意图。对于鸿蒙操作系统而言&#xff0c;掌握意图识别技术可以极大地提升用户体验&#xff0c;使设备能更好地响应…...

istio实现灰度发布,A/B发布, Kiali网格可视化(二)

代码发布是软件开发生命周期中的一个重要环节&#xff0c;确保新功能和修复能够顺利上线。以下是几种常见的代码发布流程。在学习灰度发布之前。我们首先回忆下代码发布常用的几种方法。 A/B&#xff08;蓝绿&#xff09;发布&#xff1a; 蓝绿部署是一种通过维护两套独立的环…...

python-leetcode 36.二叉树的最大深度

题目&#xff1a; 给定一个二叉树root,返回其最大深度 二叉树的最大深度是指从根节点到最远叶子节点的最长路径上的节点数 方法一&#xff1a;深度优先搜索 知道了左子树和右子树的最大深度l和r&#xff0c;那么该二叉树的最大深度即为:max(l,r)1 而左子树和右子树的最大深…...

在 Ubuntu 22.04 中修改主机名称(hostname)

在 Ubuntu 22.04 中修改主机名称&#xff08;hostname&#xff09;可以通过以下两种方法实现&#xff0c;一种是临时修改&#xff08;重启后失效&#xff09;&#xff0c;另一种是永久修改。以下是详细步骤&#xff1a; 方法 1&#xff1a;使用 hostnamectl 永久修改 查看当前主…...

解锁机器学习核心算法 | 随机森林算法:机器学习的超强武器

一、引言 在机器学习的广阔领域中&#xff0c;算法的选择犹如为一场冒险挑选趁手的武器&#xff0c;至关重要。面对海量的数据和复杂的任务&#xff0c;合适的算法能够化繁为简&#xff0c;精准地挖掘出数据背后隐藏的模式与价值。机器学习领域有十大核心算法&#xff0c;而随…...

JAVA学习第五天

接口的变量定义固定为静态变量 接口里面只能有抽象方法&#xff0c;且不能有构造方法 如果不重写tostring方法&#xff0c;会打印没有价值的信息...

【CUDA 】第4章 全局内存——4.4 核函数可达到的带宽(4对角转置)

CUDA C编程笔记 第四章 全局内存4.4 核函数可达到的带宽4.4.2.4 对角转置【让DRAM访问更均匀&#xff0c;提高性能】 待解决的问题&#xff1a; 第四章 全局内存 4.4 核函数可达到的带宽 4.4.2.4 对角转置【让DRAM访问更均匀&#xff0c;提高性能】 前置条件场景&#xff1…...

深入理解 lua_KFunction 和 lua_CFunction

在 Lua C API 中,lua_KFunction 和 lua_CFunction 是两个核心概念,尤其在处理协程和 C 函数扩展时扮演着至关重要的角色。lua_CFunction 作为一种 C 函数类型,允许开发者将 C 函数注册到 Lua 环境中,使得这些 C 函数可以在 Lua 脚本中被调用,进而实现 Lua 的功能扩展。而 …...

Nacos Derby 远程命令执行漏洞修复建议

由于Nacos < 2.4.0 BETA 存在 Derby 远程命令执行漏洞&#xff0c;恶意攻击者利用此漏洞可以未授权执行SQL语句&#xff0c;最终导致任意代码执行。目前该漏洞PoC和技术细节已在互联网上公开。 一、漏洞情况分析 Nacos 是一个功能强大的服务注册与发现、配置管理平台&#…...

MySQL(1)基础篇

执行一条 select 语句&#xff0c;期间发生了什么&#xff1f; | 小林coding 目录 1、连接MySQL服务器 2、查询缓存 3、解析SQL语句 4、执行SQL语句 5、MySQL一行记录的存储结构 Server 层负责建立连接、分析和执行 SQL存储引擎层负责数据的存储和提取。支持InnoDB、MyIS…...

java每日精进 2.13 MySql迁移人大金仓

1.迁移数据库 1. 数据库创建语句 MySQL&#xff1a; CREATE DATABASE dbname; 人大金仓&#xff08;Kingbase&#xff09;&#xff1a; 在人大金仓中&#xff0c;CREATE DATABASE 的语法通常相同&#xff0c;但可能需要特别注意字符集的指定&#xff08;如果涉及到多语言支持…...

Java开发实习面试笔试题(含答案)

在广州一家中大公司面试&#xff08;BOSS标注是1000-9999人&#xff0c;薪资2-3k&#xff09;&#xff0c;招聘上写着Java开发&#xff0c;基本没有标注前端要求&#xff0c;但是到场知道是前后端分离人不分离。开始先让你做笔试&#xff08;12道问答4道SQL题&#xff09;&…...

C语言流程控制学习笔记

1. 顺序结构 顺序结构是程序中最基本的控制结构&#xff0c;代码按从上到下的顺序依次执行。大多数C语言程序都是由顺序结构组成的。 2. 选择结构 选择结构根据条件的真假来决定执行哪一段代码。在C语言中&#xff0c;选择结构主要有以下几种&#xff1a; 2.1 if 语句 if语…...

解析DrugBank数据库数据|Python

一、DrugBank 数据库简介 DrugBank 是一个综合性的生物信息学和化学信息学数据库&#xff0c;专门收录药物和靶点的详细信息。它由加拿大阿尔伯塔大学的 Wishart 研究组 维护&#xff0c;提供化学、药理学、相互作用、代谢、靶点等多方面的药物数据。DrugBank 结合了实验数据和…...

排序算法:冒泡排序

冒泡排序标准代码&#xff08;C语言&#xff09; c复制代码 #include <stdio.h>// 冒泡排序函数 void bubbleSort(int arr[], int n) {// 外层循环&#xff1a;控制排序轮数for (int i 0; i < n - 1; i) {// 内层循环&#xff1a;控制每轮比较次数for (int j 0; j…...

网络爬虫学习:借助DeepSeek完善爬虫软件,实现模拟鼠标右键点击,将链接另存为本地文件

一、前言 最近几个月里&#xff0c;我一直在学习网络爬虫方面的知识&#xff0c;每有收获都会将所得整理成文发布&#xff0c;不知不觉已经发了7篇日志了&#xff1a; 网络爬虫学习&#xff1a;从百度搜索结果抓取标题、链接、内容&#xff0c;并保存到xlsx文件中 网络爬虫学…...

[原创](Modern C++)现代C++的关键性概念: 妙用std::reference_wrapper, 让std::list容器具有随机访问功能.

[作者] 常用网名: 猪头三 出生日期: 1981.XX.XX 企鹅交流: 643439947 个人网站: 80x86汇编小站 编程生涯: 2001年~至今[共24年] 职业生涯: 22年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、Delphi、XCode、Eclipse…...

智享AI直播三代系统,马斯克旗下AI人工智能直播工具,媲美DeepSeek!

智享AI直播三代系统&#xff0c;马斯克旗下AI人工智能直播工具,媲美DeepSeek&#xff01; 在科技飞速发展的当下&#xff0c;人工智能正以前所未有的态势重塑着各个行业的格局。直播领域&#xff0c;作为信息传播与商业交互的前沿阵地&#xff0c;也在AI技术的赋能下迎来了颠覆…...

2.19学习记录

Web easyupload3.0 这是一道构造.htaccess文件的传马 如下&#xff1a; <FilesMatch "jpg">SetHandler application/x-httpd-php </FilesMatch>.htaccess文件可以作为一个解释器&#xff0c;可以将传进去的图片马改为php马上传之后再传个图片马&#…...

RT-Thread+STM32L475VET6实现呼吸灯

文章目录 前言一、板载资源资源说明二、具体步骤1.新建rt_thread项目2. 打开PWM设备驱动3. 在Stm32CubeMX配置定时器3.1打开Stm32CubeMX3.2 使用外部高速时钟&#xff0c;并修改时钟树3.3打开定时器1&#xff0c;并配置通道一为PWM输出模式(定时器根据自己需求调整)3.4 打开串口…...

SpringBoot 项目配置动态数据源

目录 一、前言二、操作1、引入依赖2、配置默认数据库 13、定义数据源实体和 Repository4、定义动态数据源5、配置数据源6、定义切换数据源注解7、定义切面类8、使用注解切换数据源 一、前言 通过切面注解方式根据不同业务动态切换数据库 二、操作 1、引入依赖 <dependen…...

动态订阅kafka mq实现(消费者组动态上下线)

和上篇文章 动态订阅rocket mq实现(消费者组动态上下线) 目的一致&#xff0c;直接上代码 /*** Kafka topic container集合*/private static final Map<String, ConcurrentMessageListenerContainer<String, String>> topics new HashMap<>();public void r…...

Coze扣子怎么使用更强大doubao1.5模型

最近&#xff0c;豆包刚刚发布了最新的doubao1.5系列模型&#xff0c;并且加量不加价。 在性能极大进步的情况下&#xff0c;价格还与之前一致。真是业界良心了。 在同样的价格下&#xff0c;肯定要使用性能更强大的模型嘛 于是我准备把所有的智能体和工作流切换到doubao1.5…...

Zookeeper 和 Redis 哪种更好?

目录 前言 &#xff1a; 什么是Zookeeper 和 Redis &#xff1f; 1. 核心定位与功能 2. 关键差异点 (1) 一致性模型 (2) 性能 (3) 数据容量 (4) 高可用性 3. 适用场景 使用 Zookeeper 的场景 使用 Redis 的场景 4. 替代方案 5. 如何选择&#xff1f; 6. 常见误区 7. 总结 前言…...

22.4.3.2 TCP/UDP连接信息

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 通过IPGlobalProperties的方法可以获得有关TCP和UDP连接的信息&#xff1a; GetActiveTcpConnections方法获得所有的TCP连接的信息…...

【Node.js】express框架

目录 1初识express框架 2 初步使用 2.1 安装 2.2 创建基本的Web服务器 2.3 监听方法 2.3.1 监听get请求 2.3.2 监听post请求 2.4 响应客户端 2.5 获取url中的参数(get) 2.5.1 获取查询参数 2.5.2 获取动态参数 2.6 托管静态资源 2.6.1 挂载路径前缀 2.6.2 托管多…...

拦截器VS过滤器:Spring Boot中请求处理的艺术!

目录 一、拦截器&#xff08;Interceptor&#xff09;和过滤器&#xff08;Filter&#xff09;&#xff1a;都是“守门员”&#xff01;二、如何实现拦截器和过滤器&#xff1f;三、拦截器和过滤器的区别四、执行顺序五、真实的应用场景六、总结 &#x1f31f;如果喜欢作者的讲…...

分布式架构与XXL-JOB

目录 先了解什么是任务调度&#xff1f; 什么是分布式任务调度&#xff1f; 了解XXL-JOB分布式任务调度平台 如何搭建XXL-JOB&#xff1f; 分片广播 作业分片方案 最近学习在项目的媒资管理模块如何高效处理大量视频&#xff0c;上传单个视频可能涉及到转码&#xff0c…...

6121A 音频分析仪

6121A 音频分析仪 音频信号产生 音频信号分析 国产 6121A是具有音频信号产生和音频信号分析功能的测试仪器&#xff0c;适用于语音性能测试和音频功放测试等领域&#xff0c;满足电台、移动通信、音响设备和水声通信设备对频响、谐波失真和信噪比等指标的测试需求&#xff…...

什么是幂等性?

一.幂等性 什么是幂等性&#xff1f; 在计算机科学和数学领域中&#xff0c;” 幂等性 “虽然源于相同的概念&#xff0c;但其应用和具体含义有所不同 在数学中&#xff1a;幂等性是一个代数性质&#xff0c;描述的是一个操作或函数在多次应用后结果不变的特性 在分布式系统…...

2025/2/19机试准备

1.%c不忽略空格&#xff08; &#xff09; 2.启示 #include <stdio.h> #include <string.h> int main(){char str[100]{0};int x,y;int n1,n2,n3;int i;while(scanf("%s",str)!EOF){istrlen(str);if(i%30){//12446-2n1i/3;n3i/3;n2i/32;}else if(i%31){…...

【wrk】wrk 压测工具入门

1. 简介 wrk 是我无意间发现的一款简单好用的 HTTP 接口性能测试工具&#xff0c;目前在 Github 上已经有 38k 的 star 数了&#xff01; ⭐ Github地址&#xff1a;https://github.com/wg/wrk 2. 安装 环境要求&#xff1a; windows10 平台安装过 ubuntu 等 Linux 子系统 …...

12.1 Android中协程的基本使用

文章目录 前言1、导入依赖2、使用协程获取服务器中的数据2.1 定义请求回调结果的数据类2.2 网络请求 3、网络回调结构4、通过ViewModel处理网络请求数据 前言 在使用协程的时候一直没有一个具体的概念&#xff0c;只知道协程能够使得异步操作等同于同步操作&#xff0c;且不会…...

华为 eNSP:MSTP

一、MSTP是什么 MSTP是多业务传送平台&#xff08;Multi-Service Transport Platform&#xff09;的缩写&#xff0c;它是一种基于SDH&#xff08;同步数字体系&#xff09;技术的传输网络技术&#xff0c;用于同时实现TDM、ATM、以太网等多种业务的接入、处理和传送。 MSTP技…...

HMSC联合物种分布模型在群落生态学中的贝叶斯统计分析应用

联合物种分布模型&#xff08;Joint Species Distribution Modelling&#xff0c;JSDM&#xff09;在生态学领域&#xff0c;特别是群落生态学中发展最为迅速&#xff0c;它在分析和解读群落生态数据的革命性和独特视角使其受到广大国内外学者的关注。在学界不同研究团队研发出…...

LangChain大模型应用开发:消息管理与聊天历史存储

介绍 大家好&#xff0c;博主又来给大家分享知识了。今天要给大家分享的是LangChain中的消息管理与聊天历史存储。 在LangChain里&#xff0c;消息管理可精细区分用户、助手、系统等不同角色消息&#xff0c;有序调度处理&#xff0c;让交互更顺畅。而聊天历史存储则赋予模型 …...

flink-cdc同步数据到doris中

1 创建数据库和表 1.1 数据库脚本 -- 创建数据库eayc create database if not exists ods_eayc; -- 创建数据表2 数据同步 2.1 flnk-cdc 参考Flink CDC实时同步MySQL到Doris Flink CDC 概述 2.1.1 最简单的单表同步 从下面的yml脚本可以看到&#xff0c;并没有doris中创建…...

轻量级在线ETL数据集成工具架构设计与技术实现深度剖析

在当今数字化时代,企业面临着海量异构数据的整合挑战。ETL(Extract, Transform, Load)工具作为数据集成的核心,负责将分散在不同数据源中的数据进行抽取、转换和加载,以构建统一的数据视图。本文将深入剖析一款基于诺依框架开发的在线ETL数据集成工具,重点阐述其架构设计…...