YOLOv5 的量化流程及部署方法

01 技术背景

YOLOv5 是一种高效的目标检测算法,尤其在实时目标检测任务中表现突出。YOLOv5 通过三种不同尺度的检测头分别处理大、中、小物体;检测头共包括三个关键任务:边界框回归、类别预测、置信度预测;每个检测头都会逐像素地使用三个 Anchor,以帮助算法更准确地预测物体边界。

YOLOv5 具有多种不同大小的模型(YOLOv5n、YOLOv5s、YOLOv5m、YOLOv5l、YOLOv5x)以适配不同的任务类型和硬件平台。本文以基于色选机数据集训练出的 YOLOv5n 模型为例,介绍如何使用 PTQ 进行量化编译并使用 C++进行全流程的板端部署。

02 模型输入输出说明

本示例使用的 Yolov5n 模型,相较于公版在输入和输出上存在以下 2 点变动:

1、输入分辨率设定为 384x2048,从而输出分辨率也调整为了 48x256,24x128,12x64

2、类别数量设定为 17,因此输出 tensor 的通道数变为了(17+4+1)x3=66

从 pytorch 导出的 onnx 模型,具体的输入输出信息如下图所示:

同时,为了优化整体耗时,模型尾部的 sigmoid 计算被放在了后处理。

03 工具链环境

horizon-nn 1.1.0 
horizon_tc_ui 1.24.3 
hbdk 3.49.15

04 PTQ量化编译流程

4.1 准备校准数据

先准备 100 张如上图所示的色选机数据集图片存放在 seed100 文件夹,之后可借助 horizon_model_convert_sample 的 02_preprocess.sh 脚本帮助我们生成校准数据。

02_preprocess.sh

python3 ../../../data_preprocess.py \--src_dir ./seed100 \--dst_dir ./calibration_data_rgb_f32 \--pic_ext .rgb \--read_mode opencv \--saved_data_type float32

preprocess.py

def calibration_transformers():transformers = [PadResizeTransformer(target_size=(384, 2048)),HWC2CHWTransformer(),BGR2RGBTransformer(data_format="CHW"),]return transformers

校准数据仅需 resize 成符合模型输入的尺寸,并处理成 chw 和 rgb 即可。也就是说,除了归一化,其他操作都要对齐浮点模型训练时的数据预处理,而归一化可以放到模型的预处理节点中实现加速计算。

4.2 配置 yaml 文件

model_parameters:onnx_model: 'yolov5n.onnx'march: 'bayes-e'working_dir: 'model_output'output_model_file_prefix: 'yolov5n'
input_parameters:input_type_rt: 'nv12'input_type_train: 'rgb'input_layout_train: 'NCHW'norm_type: 'data_scale'scale_value: 0.003921568627451
calibration_parameters:cal_data_dir: './calibration_data_rgb_f32'cal_data_type: 'float32'calibration_type: 'default'
compiler_parameters:optimize_level: 'O3'

input_type_rt 指模型在部署时输入的数据类型,考虑到视频通路传来的通常都是 nv12,因此我们将该项置为 nv12。

input_type_train 指浮点模型训练时使用的数据类型,这里使用 rgb。

input_layout_train 指浮点模型训练时使用的数据排布,这里使用 NCHW。

norm_type 和 scale_value 根据浮点模型训练时使用的归一化参数设置,这里配置 scale 为 1/255。

这样配置后,上板模型会自带一个预处理节点,用来将 nv12 数据转换为 rgb 并做归一化,这个预处理节点可以被等效转换为卷积,从而支持 BPU 加速计算,进而显著减少预处理耗时。

我们强烈建议您在编译处理图像任务的模型时,使用这种配置方法。上板模型的数据输入类型可直接使用 nv12,同时我们也提供了板端读取 bgr 图片并转换为 nv12 格式的 C++代码供您参考。

4.3 编译上板模型

hb_mapper makertbin --config ./yolov5n_config.yaml --model-type onnx

执行以上命令后,即可编译出用于板端部署的 bin 模型。

=============================================================================
Output      Cosine Similarity  L1 Distance  L2 Distance  Chebyshev Distance  
-----------------------------------------------------------------------------
output      0.996914           0.234755     0.000420     5.957216            
613         0.997750           0.232995     0.000744     8.833645            
615         0.995946           0.281512     0.001877     4.717240

根据编译日志可看出,yolov5n 模型的三个输出头,量化前后的余弦相似度均>0.99,符合精度要求。

4.4 onnx 和 bin 的一致性验证(可选流程)

PTQ 量化流程会生成 yolov5n_quantized_model.onnx 和 yolov5n.bin,前者是量化后的 onnx 模型,后者是上板模型。通常来说,这两个模型具有完全相同的精度,可以使用这种方法进行验证。

yolov5n_quantized_model.onnx

import cv2 
import numpy as np 
from PIL import Image 
from horizon_tc_ui import HB_ONNXRuntime                               def bgr2nv12(image): image = image.astype(np.uint8) height, width = image.shape[0], image.shape[1] yuv420p = cv2.cvtColor(image, cv2.COLOR_BGR2YUV_I420).reshape((height * width * 3 // 2, )) y = yuv420p[:height * width] uv_planar = yuv420p[height * width:].reshape((2, height * width // 4)) uv_packed = uv_planar.transpose((1, 0)).reshape((height * width // 2, )) nv12 = np.zeros_like(yuv420p) nv12[:height * width] = y nv12[height * width:] = uv_packed return nv12 def nv12Toyuv444(nv12, target_size): height = target_size[0] width = target_size[1] nv12_data = nv12.flatten() yuv444 = np.empty([height, width, 3], dtype=np.uint8) yuv444[:, :, 0] = nv12_data[:width * height].reshape(height, width) u = nv12_data[width * height::2].reshape(height // 2, width // 2) yuv444[:, :, 1] = Image.fromarray(u).resize((width, height),resample=0) v = nv12_data[width * height + 1::2].reshape(height // 2, width // 2) yuv444[:, :, 2] = Image.fromarray(v).resize((width, height),resample=0) return yuv444 def preprocess(input_name):bgr_input = cv2.imread("seed.jpg")nv12_input = bgr2nv12(bgr_input)nv12_input.tofile("seed_nv12.bin") yuv444 = nv12Toyuv444(nv12_input, (384,2048))yuv444 = yuv444[np.newaxis,:,:,:]yuv444_128 = (yuv444-128).astype(np.int8)return yuv444_128def main(): sess = HB_ONNXRuntime(model_file="./yolov5n_quantized_model.onnx")input_names = [input.name for input in sess.get_inputs()]output_names = [output.name for output in sess.get_outputs()]feed_dict = dict()for input_name in input_names:feed_dict[input_name] = preprocess(input_name)output = sess.run(output_names, feed_dict)     print(output[0][0][0][0])if __name__ == '__main__':main()

在读取原始图像后,将其转换为 nv12 格式并保存,之后处理成 yuv444_128 格式并送给模型推理。

由 print(output[0][0][0][0])打印出的信息如下:

[  0.18080421   0.4917729    0.34173843   0.26877916 -10.983349-3.8538744   -1.8031031   -2.2803051   -1.5579813   -1.8910917-3.7208636   -2.4970834   -2.8638227   -3.5894732   -3.338331
......

yolov5n.bin

hrt_model_exec infer --model-file yolov5n.bin --input-file seed_nv12.bin --enable_dump true --dump_format txt

这里我们将上一步保存的 nv12 数据作为 bin 模型的输入,并保存输出数据,其中第一个输出分支的数据如下:

0.180804208 
0.491772890 
0.341738433 
0.268779159 
-10.983348846 
-3.853874445 
-1.803103089 
-2.280305147 
-1.557981253 
-1.891091704 
-3.720863581 
-2.497083426 
-2.863822699 
-3.589473248 
-3.338330984 
......

可以看到,yolov5n_quantized_model.onnx 和 yolov5n.bin 具有相同的输出。

05 Runtime 部署流程

在算法工具链的交付包中,ai benchmark 示例包含了读图、前处理、推理、后处理等完整流程的 C++源码,但考虑到 ai benchmark 代码耦合度较高,有不低的学习成本,不方便用户嵌入到自己的工程应用中,因此我们提供了基于 horizon_runtime_sample 示例修改的简易版本 C++代码,只包含 1 个头文件和 1 个 C++源码,用户仅需替换原有的 00_quick_start 示例即可编译运行。

该 C++ demo 包含对单帧数据的读图(bgr->nv12),模型推理(包含预处理),后处理,打印输出结果等步骤。

5.1 头文件

该头文件内容主要来自于 ai benchmark 的 code/include/base/perception_common.h 头文件,包含了对 argmax 和计时功能的定义,以及目标检测任务相关结构体的定义。

#include typedef std::chrono::steady_clock::time_point Time;
typedef std::chrono::duration Micro;template 
inline size_t argmax(ForwardIterator first, ForwardIterator last) {return std::distance(first, std::max_element(first, last));
}typedef struct Bbox {float xmin{0.0};float ymin{0.0};float xmax{0.0};float ymax{0.0};Bbox() {}Bbox(float xmin, float ymin, float xmax, float ymax): xmin(xmin), ymin(ymin), xmax(xmax), ymax(ymax) {}friend std::ostream &operator<<(std::ostream &os, const Bbox &bbox) {const auto precision = os.precision();const auto flags = os.flags();os << "[" << std::fixed << std::setprecision(6) << bbox.xmin << ","<< bbox.ymin << "," << bbox.xmax << "," << bbox.ymax << "]";os.flags(flags);os.precision(precision);return os;}~Bbox() {}
} Bbox;typedef struct Detection {int id{0};float score{0.0};Bbox bbox;const char *class_name{nullptr};Detection() {}Detection(int id, float score, Bbox bbox): id(id), score(score), bbox(bbox) {}Detection(int id, float score, Bbox bbox, const char *class_name): id(id), score(score), bbox(bbox), class_name(class_name) {}friend bool operator>(const Detection &lhs, const Detection &rhs) {return (lhs.score > rhs.score);}friend std::ostream &operator<<(std::ostream &os, const Detection &det) {const auto precision = os.precision();const auto flags = os.flags();os << "{"<< R"("bbox")"<< ":" << det.bbox << ","<< R"("prob")"<< ":" << std::fixed << std::setprecision(6) << det.score << ","<< R"("label")"<< ":" << det.id << ","<< R"("class_name")"<< ":\"" << det.class_name << "\"}";os.flags(flags);os.precision(precision);return os;}~Detection() {}
} Detection;struct Perception {std::vector det;enum {DET = (1 << 0),} type;friend std::ostream &operator<<(std::ostream &os, Perception &perception) {os << "[";if (perception.type == Perception::DET) {auto &detection = perception.det;for (int i = 0; i < detection.size(); i++) {if (i != 0) {os << ",";}os << detection[i];}} os << "]";return os;}
};

5.2 源码

为方便用户阅读,该源码使用全局变量定义了若干参数,请用户在实际的应用工程中,避免使用过多全局变量。代码中已在合适的位置添加中文注释。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "dnn/hb_dnn.h"
#include "opencv2/core/mat.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include "head.h"// 上板模型的路径
auto modelFileName = "yolov5n.bin";
// 单张测试图片的路径
std::string imagePath = "seed.jpg";
// 测试图片的宽度
int image_width = 2048;
// 测试图片的高度
int image_height = 384;
// 置信度阈值
float score_threshold = 0.2;
// 分类目标数
int num_classes = 17;
// 模型输出的通道数
int num_pred = num_classes + 4 + 1;
// nms的topk
int nms_top_k = 5000;
// nms的iou阈值
float nms_iou_threshold = 0.5;// 为模型推理准备输入输出内存空间
void prepare_tensor(int input_count, int output_count, hbDNNTensor *input_tensor,hbDNNTensor *output_tensor,hbDNNHandle_t dnn_handle) {hbDNNTensor *input = input_tensor;for (int i = 0; i < input_count; i++) {hbDNNGetInputTensorProperties(&input[i].properties, dnn_handle, i);int input_memSize = input[i].properties.alignedByteSize;hbSysAllocCachedMem(&input[i].sysMem[0], input_memSize);input[i].properties.alignedShape = input[i].properties.validShape;}hbDNNTensor *output = output_tensor;for (int i = 0; i < output_count; i++) {hbDNNGetOutputTensorProperties(&output[i].properties, dnn_handle, i);int output_memSize = output[i].properties.alignedByteSize;hbSysAllocCachedMem(&output[i].sysMem[0], output_memSize);}
}// 读取bgr图片并转换为nv12格式再存储进输入内存
void read_image_2_tensor_as_nv12(std::string imagePath, hbDNNTensor *input_tensor) {hbDNNTensor *input = input_tensor;hbDNNTensorProperties Properties = input->properties;int input_h = Properties.validShape.dimensionSize[2];int input_w = Properties.validShape.dimensionSize[3];cv::Mat bgr_mat = cv::imread(imagePath, cv::IMREAD_COLOR);cv::Mat yuv_mat;cv::cvtColor(bgr_mat, yuv_mat, cv::COLOR_BGR2YUV_I420);uint8_t *nv12_data = yuv_mat.ptr();auto input_data = input->sysMem[0].virAddr;int32_t y_size = input_h * input_w;memcpy(reinterpret_cast(input_data), nv12_data, y_size);int32_t uv_height = input_h / 2;int32_t uv_width = input_w / 2;uint8_t *nv12 = reinterpret_cast(input_data) + y_size;uint8_t *u_data = nv12_data + y_size;uint8_t *v_data = u_data + uv_height * uv_width;for (int32_t i = 0; i < uv_width * uv_height; i++) {if (u_data && v_data) {*nv12++ = *u_data++;*nv12++ = *v_data++;}}
}// 后处理的核心代码(不包括nms),初步筛选检测框
void process_tensor_core(hbDNNTensor *tensor,int layer,std::vector &dets){hbSysFlushMem(&(tensor->sysMem[0]), HB_SYS_MEM_CACHE_INVALIDATE);int height, width, stride;std::vector> anchors;if(layer == 0){height = 48; width = 256; stride = 8; anchors = {{10, 13}, {16, 30}, {33, 23}};} else if (layer == 1){height = 24; width = 128; stride = 16; anchors = {{30, 61}, {62, 45}, {59, 119}};} else if (layer == 2){height = 12; width = 64; stride = 32; anchors = {{116, 90}, {156, 198}, {373, 326}};}int anchor_num = anchors.size();auto *data = reinterpret_cast(tensor->sysMem[0].virAddr);for (uint32_t h = 0; h < height; h++) {for (uint32_t w = 0; w < width; w++) {for (int k = 0; k < anchor_num; k++) {        double anchor_x = anchors[k].first;double anchor_y = anchors[k].second;float *cur_data = data + k * num_pred;float objness = cur_data[4];if (objness < score_threshold)continue;int id = argmax(cur_data + 5, cur_data + 5 + num_classes);// 模型检测头不包含sigmoid算子,而将sigmoid计算安排在后处理进行                  double x1 = 1 / (1 + std::exp(-objness)) * 1; double x2 = 1 / (1 + std::exp(-cur_data[id + 5]));double confidence = x1 * x2;if (confidence < score_threshold)continue;float center_x = cur_data[0];float center_y = cur_data[1];float scale_x = cur_data[2];float scale_y = cur_data[3];double box_center_x =((1.0 / (1.0 + std::exp(-center_x))) * 2 - 0.5 + w) * stride;double box_center_y =((1.0 / (1.0 + std::exp(-center_y))) * 2 - 0.5 + h) * stride;             double box_scale_x =std::pow((1.0 / (1.0 + std::exp(-scale_x))) * 2, 2) * anchor_x;double box_scale_y =std::pow((1.0 / (1.0 + std::exp(-scale_y))) * 2, 2) * anchor_y;double xmin = (box_center_x - box_scale_x / 2.0);double ymin = (box_center_y - box_scale_y / 2.0);double xmax = (box_center_x + box_scale_x / 2.0);double ymax = (box_center_y + box_scale_y / 2.0);          double xmin_org = xmin; double xmax_org = xmax; double ymin_org = ymin;double ymax_org = ymax;if (xmax_org <= 0 || ymax_org <= 0)continue;if (xmin_org > xmax_org || ymin_org > ymax_org)continue;xmin_org = std::max(xmin_org, 0.0);xmax_org = std::min(xmax_org, image_width - 1.0);ymin_org = std::max(ymin_org, 0.0);ymax_org = std::min(ymax_org, image_height - 1.0);Bbox bbox(xmin_org, ymin_org, xmax_org, ymax_org);dets.emplace_back((int)id, confidence, bbox);}data = data + num_pred * anchors.size();}}
}// nms处理,精挑细选出合适的检测框
void yolo5_nms(std::vector &input,std::vector &result,bool suppress) {std::stable_sort(input.begin(), input.end(), std::greater());std::vector skip(input.size(), false);std::vector areas;areas.reserve(input.size());for (size_t i = 0; i < input.size(); i++) {float width = input[i].bbox.xmax - input[i].bbox.xmin;float height = input[i].bbox.ymax - input[i].bbox.ymin;areas.push_back(width * height);}int count = 0;for (size_t i = 0; count < nms_top_k && i < skip.size(); i++) {if (skip[i]) {continue;}skip[i] = true;++count;for (size_t j = i + 1; j < skip.size(); ++j) {if (skip[j]) {continue;}if (suppress == false) {if (input[i].id != input[j].id) {continue;}}float xx1 = std::max(input[i].bbox.xmin, input[j].bbox.xmin);float yy1 = std::max(input[i].bbox.ymin, input[j].bbox.ymin);float xx2 = std::min(input[i].bbox.xmax, input[j].bbox.xmax);float yy2 = std::min(input[i].bbox.ymax, input[j].bbox.ymax);if (xx2 > xx1 && yy2 > yy1) {float area_intersection = (xx2 - xx1) * (yy2 - yy1);float iou_ratio =area_intersection / (areas[j] + areas[i] - area_intersection);if (iou_ratio > nms_iou_threshold) {skip[j] = true;}}}result.push_back(input[i]); // 打印最终筛选出的检测框的置信度和位置信息std::cout << "score " << input[i].score;std::cout << " xmin " << input[i].bbox.xmin;std::cout << " ymin " << input[i].bbox.ymin;std::cout << " xmax " << input[i].bbox.xmax;std::cout << " ymax " << input[i].bbox.ymax << std::endl; }
}// 多线程加速后处理计算
std::mutex dets_mutex;
void process_tensor_thread(hbDNNTensor *tensor, int layer, std::vector &dets){std::vector local_dets;process_tensor_core(tensor, layer, local_dets);std::lock_guard lock(dets_mutex);dets.insert(dets.end(), local_dets.begin(), local_dets.end());
}void post_process(std::vector &tensors, Perception *perception){perception->type = Perception::DET;std::vector dets;std::vector threads;for (int i = 0; i < tensors.size(); ++i) {threads.emplace_back([&tensors, i, &dets](){process_tensor_thread(&tensors[i], i, dets);});}for (auto &thread : threads) thread.join();yolo5_nms(dets, perception->det, false);
}int main(int argc, char **argv) {//初始化模型hbPackedDNNHandle_t packed_dnn_handle;hbDNNHandle_t dnn_handle;const char **model_name_list;int model_count = 0;hbDNNInitializeFromFiles(&packed_dnn_handle, &modelFileName, 1);hbDNNGetModelNameList(&model_name_list, &model_count, packed_dnn_handle);hbDNNGetModelHandle(&dnn_handle, packed_dnn_handle, model_name_list[0]);std::cout<< "yolov5 demo begin!" << std::endl;std::cout<< "load model success" <

5.3 运行说明

用户可将头文件和源码放入 horizon_runtime_sample/code/00_quick_start/src 路径,并执行 build_x5.sh 编译工程,再将 horizon_runtime_sample/x5 文件夹复制到开发板的 /userdata 目录,并在 /userdata/x5/script/00_quick_start/ 路径下存放上板模型、测试图片等文件,并编写板端运行脚本:

bin=../aarch64/bin/run_mobileNetV1_224x224
lib=../aarch64/libexport LD_LIBRARY_PATH=${lib}:${LD_LIBRARY_PATH}
export BMEM_CACHEABLE=true${bin} 

运行结果如下:

yolov5 demo begin!
load model success
prepare intput and output tensor success
read image to tensor as nv12 success
model infer time: 7.763 ms
model infer success
score 0.365574 xmin 1448.69 ymin 148.4 xmax 1518.55 ymax 278.487
postprocess time: 1.376 ms
postprocess success
release resources success
yolov5 demo end!

对于这次推理,我们的输入图像为下图:

可以看到,推理程序成功识别到了 1 枚瓜子,并且给出了正确的坐标信息。

5.4 模型推理耗时说明

需要强调的是,应用程序在推理第一帧的时候,会产生加载推理框架导致的额外耗时,因此运行该程序测出的模型推理耗时是偏高的。

准确的模型的推理时间应当以 hrt_model_exec 工具实测结果为准,参考命令:

hrt_model_exec perf --model-file ./yolov5n.bin --thread-num 1(测试单线程单帧延时,关注latency)
hrt_model_exec perf --model-file ./yolov5n.bin --thread-num 8(测试多线程极限吞吐量,关注FPS)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/858235.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

重庆市某区教委城域网网络管理与态势感知项目

重庆某区教委需稳定高效运维系统管理教育城域网,智和信通提供集中部署方案,实现统一管理、实时监控、告警通知与智能分析,保障教学活动,推动教育信息化发展。 重庆市某区教育委员会是区政府直辖的一级政府职能部门,主要负责本区的教育工作。 项目现状重庆市某区教育…

Redis-十大数据类型

Reids数据类型指的是value的类型,key都是字符串 redis-server:启动redis服务 redis-cli:进入redis交互式终端常用的key的操作redis的命令和参数不区分大小写 ,key和value区分查看当前库所有的key keys *判断某个key是否存在 exists key查看key是什么类型 type key删除指定的k…

12款免费项目管理工具全方位对比【功能与实用性评测】

在当今数字化时代,项目管理的效率和效果直接影响着企业的竞争力与业务成果。无论是小型创业团队,还是大型企业的复杂项目,合适的项目管理工具都能成为成功的关键助力。然而,面对市场上琳琅满目的项目管理软件,如何挑选一款免费且功能强大、实用性高的工具并非易事。今天,…

作业帮基于 Apache DolphinScheduler 3_0_0 的缺陷修复与优化

文|作业帮大数据团队(阮文俊、孙建业) 背 景 基于 Apache DolphinScheduler (以下简称DolphinScheduler)搭建的 UDA 任务调度平台有效支撑了公司的业务数据开发需求,处理着日均百万级别的任务量。 整个 UDA 的架构如下图所示,其中我们的引擎层主要基于 DolphinScheduler …

Bootstrap垂直手风琴折叠菜单

这是一款Bootstrap垂直手风琴折叠菜单。该Bootstrap垂直手风琴折叠菜单使用boostrap网格进行布局,并通过对bootstrap面板组进行美化,构建出精美的垂直手风琴折叠菜单效果。在线预览 下载使用方法 在页面中引jquery和bootstrap相关文件。<link href="path/to/css/bo…

免费学习基于SpringBoot的高考志愿智能推荐系统

本篇文章使用Java与MYSQL技术搭建了一个高考志愿智能推荐系统。首先,对用户提出的功能进行合理分析,然后搭建开发平台以及配置计算机软硬件;通过对数据流图以及系统结构的设计,创建相应的数据库;进行详细的设计,实现主要功能。最后测试网站,并分析测试结果,完善系统,得…

2024 新版Clion安装使用教程(附激活以及常见问题处理)

Clion 简介 在同学向我吐槽codeblock多么难用于是我疯狂安利CLion的时候,他发出了灵魂的拷问——“CLion是啥?” CLion和codeblock一样都是可以编写C/CPP的IDE(集成开发环境),CLion的优点多多,尤其是CLion对小白非常友好。 配置简单:只需要点几下鼠标就可以完成编译器配…

可调整提亮度8级/数码管显示屏驱动-VK1638 SOP28点阵LED数码管驱动芯片(ic)

产品品牌:永嘉微电/VINKA 产品型号:VK1638 封装形式:SOP28 概述 VK1638是一种带键盘扫描接口的数码管或点阵LED驱动控制专用芯片,内部集成有3线 串行接口、数据锁存器、LED 驱动、键盘扫描等电路。SEG脚接LED阳极,GRID脚接LED阴 极,可支持10SEGx8GRID的点阵LED显示面板,…

不写一行代码,通义灵码 5 分钟“手撕”年会抽奖程序

年会中的抽奖环节不可或缺,但每年为了选择合适的抽奖小程序,团队往往需要投入大量时间和精力。然而,抽奖结束后,参与者通常只记得自己是否中奖,其他细节多被遗忘。在 AI 技术日益成熟的今天,如何打造一个既高效又有技术含量的抽奖应用呢?今天,就让我们跟随通义灵码,仅…

网络抓包调试实践

网络抓包这个话题,有一定开发经验的多少都有所了解,常用软件Wireshark,Fiddler用起来也非常傻瓜,本文不会涉及。 一般的介绍网络抓包的文章,到能抓到数据包通常就结束了。但显然认识工具是一会儿事儿,理解本质,清楚实践中啥时候该用,是另外一会儿事儿。让新人小白自行举…

根据监控点或者视频的分辨率自适应调整播放画面的宽高比

3.根据监控点或者视频的分辨率自适应调整播放画面的宽高比 视频的分辨率宽高比和播放窗口的宽高比例不一定相同,播放时如果填充整个播放窗口,会出现播放画面横向或纵向拉伸以填充整个窗口,这样会导致播放画面变形或者不协调,影响观看效果;为了保持视频的按照原来的宽高比,…

软考通过申请加分

2024下半年中级软件设计师考试通过,20224089连赛轩申请加分10分