【教程】在RK3568上部署(C++)语义分割算法BiSeNetv1/v2

news2024/11/29 2:42:13

引言

在本篇教程中,博主将记录国庆假期前在RK3568上部署分割算法的步骤以及代码。首先说一下,RK3568这个开发板本身的算力大概是0.8T(在实际开发中还会用到额外的计算卡,额外的计算卡后面文章再说,本篇文章主要记录在RK3568上的部署过程)。

一、获取rknn模型

1、这步不是很难,我之前也写过BiSeNet的教程,官方提供的代码也很好理解,并且提供了onnx模型的导出代码。教程--从零开始使用BiSeNet(语义分割)网络训练自己的数据集_计算机幻觉的博客-CSDN博客为了从图片分割出我们想要的特征,我们采用BiSeNet作为分割模型,并且在自己制作的数据集上进行训练测试。注:训练是在linux环境下的,Win下训练可能会有点问题。_bisenethttps://blog.csdn.net/qq_39149619/article/details/131882664?spm=1001.2014.3001.55012、将导出的onnx代码进行rknn转换,RK3568需要用到rknn-toolkit2,该环境的安装之前也写过:

RKNN-ToolKit2 1.5.0安装教程_rknn安装-CSDN博客由于种种原因需要用到开发版RK3568,需要预先安装RKNN-Toolkit2进行模型转化等,博主安装的版本是1.5.0,Ubuntu版本是20.04,python版本3.6。1、原本准备采取docker安装,但是文件有点大再加上网速不行,于是我们采用pip方法进行安装。,直接点击下载,得到rknn-toolkit2-master.zip,并且解压到任意文件夹中。_rknn安装https://blog.csdn.net/qq_39149619/article/details/131694631?spm=1001.2014.3001.5501转换代码:mean_values和std_values需要根据自己训练集修改

from rknn.api import RKNN

ONNX_MODEL = '/home/zw/Prg/Pycharm/file/RKNN3568/onnx/yolov5-seg/best_480x480.onnx'
platform = "rk3568"
RKNN_MODEL = '/home/zw/Prg/Pycharm/file/RKNN3568/rknn/BiSeNetV2/BiSeNetv2_320x320_min4_{}_out_opt.rknn'.format(platform)


if __name__ == '__main__':

    
    # Create RKNN object
    rknn = RKNN(verbose=False)

    # pre-process config
    print('--> config model')
    
    rknn.config(mean_values=[82.9835, 93.9795, 82.1893], std_values=[54.02, 54.804, 54.0225], target_platform='rk3568')  #BiSeNet
 

    print('done')

    # Load tensorflow model
    print('--> Loading model')
    ret = rknn.load_onnx(model=ONNX_MODEL, outputs=['preds'])  # 这里一定要根据onnx模型修改
    if ret != 0:
        print('Load onnx model failed!')
        exit(ret)
    print('done')

    # Build model
    print('--> Building model')
    ret = rknn.build(do_quantization=False, dataset='/home/zw/Prg/Pycharm/file/RKNN3568/dataset.txt')
    if ret != 0:
        print('Build rkmodel failed!')
        exit(ret)
    print('done')

    # rknn.export_rknn_precompile_model(RKNN_MODEL)
    rknn.export_rknn(RKNN_MODEL)

    rknn.release()

二、在RK3568上进行C++部署

1、首先,从官网下载rknpu2相关文件,官网地址:GitHub - rockchip-linux/rknpu2,该文件包含了rknn相关的接口文件以及提供的示例代码。同时,我们创建bisenetv2的例子,如下图:

model存放转换好的rknn文件,main是推理C++代码。

2、想在3568上部署,需要对程序进行编译,详细的参考官方提供的说明pdf,讲解的简单易懂,这里就不多说。编译需要用到交叉编译工具,这里提供下载地址:Firefly-Linux / prebuilts / gcc / linux-x86 / aarch64 / gcc-buildroot-9.3.0-2020.03-x86_64_aarch64-rockchip-linux-gnu · GitLab

下载到任意位置即可,打开build-linux_RK3566_RK3568.sh文件,修改入下:gcc替换成你自己的地址。

#!/bin/bash
set -e

TARGET_SOC="rk356x"

# for aarch64
# GCC_COMPILER=aarch64-linux-gnu
export TOOL_CHAIN=/home/zw/Downloads/gcc-buildroot-9.3.0-2020.03-x86_64_aarch64-rockchip-linux-gnu-firefly
GCC_COMPILER=/home/zw/Downloads/gcc-buildroot-9.3.0-2020.03-x86_64_aarch64-rockchip-linux-gnu-firefly/bin/aarch64-rockchip-linux-gnu
export LD_LIBRARY_PATH=${TOOL_CHAIN}/lib64:$LD_LIBRARY_PATH
export CC=${GCC_COMPILER}-gcc
export CXX=${GCC_COMPILER}-g++

ROOT_PWD=$( cd "$( dirname $0 )" && cd -P "$( dirname "$SOURCE" )" && pwd )

# build
BUILD_DIR=${ROOT_PWD}/build/build_linux_aarch64

if [[ ! -d "${BUILD_DIR}" ]]; then
  mkdir -p ${BUILD_DIR}
fi

cd ${BUILD_DIR}
cmake ../.. \
    -DTARGET_SOC=${TARGET_SOC} \
    -DCMAKE_C_COMPILER=${GCC_COMPILER}-gcc \
    -DCMAKE_CXX_COMPILER=${GCC_COMPILER}-g++
make -j4
make install
cd -

cmakelist文件爱你没啥要改的,修改好自己项目名称即可:

cmake_minimum_required(VERSION 3.4.1)

project(rknn_bisenetv2_demo)

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

# rknn api
if(TARGET_SOC STREQUAL "rk356x")
  set(RKNN_API_PATH ${CMAKE_SOURCE_DIR}/../../runtime/RK356X/${CMAKE_SYSTEM_NAME}/librknn_api)
elseif(TARGET_SOC STREQUAL "rk3588")
  set(RKNN_API_PATH ${CMAKE_SOURCE_DIR}/../../runtime/RK3588/${CMAKE_SYSTEM_NAME}/librknn_api)
else()
  message(FATAL_ERROR "TARGET_SOC is not set, ref value: rk356x or rk3588")
endif()

if (CMAKE_SYSTEM_NAME STREQUAL "Android")
  set(RKNN_RT_LIB ${RKNN_API_PATH}/${CMAKE_ANDROID_ARCH_ABI}/librknnrt.so)
else()
  if (CMAKE_C_COMPILER MATCHES "aarch64")
    set(LIB_ARCH aarch64)
  else()
    set(LIB_ARCH armhf)
  endif()
  set(RKNN_RT_LIB ${RKNN_API_PATH}/${LIB_ARCH}/librknnrt.so)
endif()
include_directories(${RKNN_API_PATH}/include)
include_directories(${CMAKE_SOURCE_DIR}/../3rdparty)

# opencv
if (CMAKE_SYSTEM_NAME STREQUAL "Android")
    set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/../3rdparty/opencv/OpenCV-android-sdk/sdk/native/jni/abi-${CMAKE_ANDROID_ARCH_ABI})
else()
  if(LIB_ARCH STREQUAL "armhf")
    set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/../3rdparty/opencv/opencv-linux-armhf/share/OpenCV)
  else()
    set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/../3rdparty/opencv/opencv-linux-aarch64/share/OpenCV)
  endif()
endif()
find_package(OpenCV REQUIRED)

set(CMAKE_INSTALL_RPATH "lib")

add_executable(rknn_bisenetv2_demo
    src/main.cc
)

target_link_libraries(rknn_bisenetv2_demo
  ${RKNN_RT_LIB}
  ${OpenCV_LIBS}
)

# install target and libraries
set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/install/rknn_bisenetv2_demo_${CMAKE_SYSTEM_NAME})
install(TARGETS rknn_bisenetv2_demo DESTINATION ./)

install(DIRECTORY model DESTINATION ./)
install(PROGRAMS ${RKNN_RT_LIB} DESTINATION lib)

3、废话不多说了,直接提供C++代码,需要注意的是代码中的图像尺寸这里写死了,根据自己需要来修改代码即可。

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <queue>
#include "rknn_api.h"
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <chrono>
#include <sys/time.h>
using namespace cv;
using namespace std;

/* 打印结构体rknn_tensor_attr所代表的张量信息,这个结构体包含了关于张量的信息;
rknn_tensor_attr *attr:指向 rknn_tensor_attr 结构体的指针,这个结构体包含了关于张量的信息。 
 %d 整数 %s 字符串 %f 浮点数
*/
void printRKNNTensor(rknn_tensor_attr *attr) { 
    printf("index=%d name=%s n_dims=%d dims=[%d %d %d %d] n_elems=%d size=%d "
           "fmt=%d type=%d qnt_type=%d fl=%d zp=%d scale=%f\n",
           attr->index,  // 张量的索引
           attr->name, // 张量的名字
           attr->n_dims, //张量的维度数
            attr->dims[3], attr->dims[2], 
           attr->dims[1], attr->dims[0], // 张量在每个维度上的大小
            attr->n_elems,  // 张量中元素的总数
            attr->size,  // 张量的大小
            0, 
            attr->type,  // 张量的数据类型
           attr->qnt_type, // 张量的量化类型
            attr->fl, // 浮点层(与量化有关)
             attr->zp,  //零点(与量化有关)
             attr->scale); //缩放值(与量化有关)
}

 /* 
 这段代码是一个用于后处理的函数,主要是将一个输入数组 input0 中的数据转换成伪彩色图像,
 然后将伪彩色图像与原始图像 resize_img 进行融合,
 最后保存三张图像:伪彩色图像、原始图像、和融合后的图像。
 int:函数返回一个整数值作为结果。
 float *input0:指向 float 类型的数组,存储了后处理前的数据。
 cv::Mat resize_img:OpenCV 中的 cv::Mat 类型,代表原始图像。
  */
int post_process_u8(float *input0,cv::Mat resize_img,int w,int h){
    //将 float 类型的数组转换为 int 类型的向量 vec_host_scores,并将 input0 数组中的数据逐个添加到这个向量中。

    std::vector<int> vec_host_scores;
    for(int i=0;i<w*h;i++){
        vec_host_scores.emplace_back(input0[i]);
    }
    /* 
    根据预设的 num_class 值(256),生成颜色映射表 color_map,
    用于将 input0 中的整数值映射为伪彩色值。
    在这里,每个整数值被视为一个类别标签,然后将其转换为对应的伪彩色值。
    这个过程是通过位操作来实现的,根据 input0 中的整数值生成对应的 R、G、B 分量值。
     */
    int num_class = 256;//提取到外面 只执行一次即可,自己改吧
    vector<int> color_map(num_class * 3);
    for (int i = 0; i < num_class; i++) {
        int j = 0;
        int lab = i;
        while (lab) {
            color_map[i * 3] |= ((lab >> 0 & 1) << (7 - j));
            color_map[i * 3 + 1] |= (((lab >> 1) & 1) << (7 - j));
            color_map[i * 3 + 2] |= (((lab >> 2) & 1) << (7 - j));
            j += 1;
            lab >>= 3;
        }
    }
    /* 
    创建一个 cv::Mat 类型的 pseudo_img 对象,用于存储生成的伪彩色图像。
    在这里,该图像的尺寸与输入的 w 和 h 相同,通道数为 3(代表 RGB 颜色通道)。
    用于创建大小为 w x h 的 cv::Mat 对象 pseudo_img 并将所有像素设置为黑色。
     */
    cv::Mat pseudo_img(w, h, CV_8UC3, cv::Scalar(0, 0, 0));
    for (int r = 0; r < w; r++) {
        for (int c = 0; c < h; c++) {
            int idx = vec_host_scores[r*h  + c];
            pseudo_img.at<Vec3b>(r, c)[0] = color_map[idx * 3];
            pseudo_img.at<Vec3b>(r, c)[1] = color_map[idx * 3 + 1];
            pseudo_img.at<Vec3b>(r, c)[2] = color_map[idx * 3 + 2];
        }
    }
    cv::Mat result;
    cv::Mat resize_result;
    cv::addWeighted(resize_img, 0.4, pseudo_img, 0.6, 0, result, 0);
    cv::resize(result, resize_result, cv::Size(640, 480));
    // cv::imshow("pseudo_img", pseudo_img);
    cv::imwrite("pseudo_img.jpg", pseudo_img);
    // cv::imshow("bgr", resize_img);
    cv::imwrite("resize_img.jpg", resize_img);
    // cv::imshow("result", result);
    cv::imwrite("result.jpg", resize_result);
    // cv::waitKey(0);
    return 0;
}

double __get_us(struct timeval t) { return (t.tv_sec * 1000000 + t.tv_usec); }
 
int main(int argc, char **argv) {
    const char *img_path = argv[2];   
    const char *model_path = argv[1];
    const char *post_process_type = "fp";//fp  
    struct timeval start_time, stop_time;
    // const int target_width = 960;
    // const int target_height = 720;

    if (argc != 3) {
    printf("Usage: %s <rknn model> <image_path> \n", argv[0]);
    return -1;
    }

    // Load image
    cv::Mat bgr = cv::imread(img_path);
    if (!bgr.data) {
        printf("cv::imread %s fail!\n", img_path);
        return -1;
    }
    cv::Mat rgb;
    //BGR->RGB
    cv::cvtColor(bgr, rgb, cv::COLOR_BGR2RGB);
    
    //调整rgb图像的大小
    cv::Mat img_resize;
    cv::resize(rgb,img_resize,cv::Size(320,320));
    int width=img_resize.cols; //获取原始bgr图像的大小
    int height=img_resize.rows;
 
 
    // Load model
    FILE *fp = fopen(model_path, "rb");
    if (fp == NULL) {
        printf("fopen %s fail!\n", model_path);
        return -1;
    }
    fseek(fp, 0, SEEK_END); // 将文件指针移动到文件的末尾
    int model_len = ftell(fp); // 然后使用 ftell(fp) 获取当前文件指针的位置,即文件的大小
    void *model = malloc(model_len); //分配大小为 model_len 字节的内存块,并将内存块的起始地址保存在指针变量 model 中
    fseek(fp, 0, SEEK_SET); //是将文件指针重新设置到文件的开头,以便后续读取文件数据或执行其他操作。
    if (model_len != fread(model, 1, model_len, fp)) { //model用于存储从文件中读取的数据,model_len这个参数指定要读取的数据的总字节数
        printf("fread %s fail!\n", model_path);
        free(model);
        return -1;
    }
 
    /* 
    定义了一个 rknn_context 类型的变量 ctx,并初始化为0。
    rknn_context 是 RKNN 提供的一个上下文对象,用于执行模型推理的各种操作。
    通过将其初始化为0,表示暂时没有创建 RKNN 上下文。
     */
    rknn_context ctx = 0;
    
    /* 
    rknn_init 函数用于创建 RKNN 上下文,并将模型数据加载到上下文中。它的参数如下:
    ctx: 这是一个指向 rknn_context 的指针的地址,通过传递指针的地址,函数可以在内部分配内存并创建一个新的 RKNN 上下文,
    并将其地址存储在 ctx 变量中;
    model:这是之前通过 fread 从模型文件中读取的模型数据的指针。它包含了要加载到 RKNN 上下文的模型数据;
    rknn_init 函数执行成功后,会返回一个非负值,表示初始化成功,并将 RKNN 上下文的地址存储在 ctx 变量中。
    如果初始化失败,返回值将是一个负数,表示初始化失败的错误码。
     */
    int ret = rknn_init(&ctx, model, model_len, RKNN_FLAG_COLLECT_PERF_MASK, NULL);
    if (ret < 0) {  
        printf("rknn_init fail! ret=%d\n", ret);
        return -1;
    }
 
    /* Query sdk version 
    查询 Rockchip Neural Network Toolkit(RKNN)的 SDK 版本和驱动版本,并输出它们的信息。
    */
    rknn_sdk_version version;
    ret = rknn_query(ctx, RKNN_QUERY_SDK_VERSION, &version,
                     sizeof(rknn_sdk_version));
    if (ret < 0) {
        printf("rknn_init error ret=%d\n", ret);
        return -1;
    }
    printf("sdk version: %s driver version: %s\n", version.api_version,
           version.drv_version);
 
 
    /* Get input,output attr 
    查询模型的输入和输出数量,并将结果输出到控制台。
    io_num,用于存储模型的输入和输出数量信息;rknn_query 函数来查询模型的输入和输出数量
    */
    rknn_input_output_num io_num;
    ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));
    if (ret < 0) {
        printf("rknn_init error ret=%d\n", ret);
        return -1;
    }
    printf("model input num: %d, output num: %d\n", io_num.n_input,
           io_num.n_output);
 
    /* 
    查询模型的输入属性,并将输入属性信息打印到控制台,memset 函数将 input_attrs 数组的内存清零,以确保所有属性初始值为0。

     */
    rknn_tensor_attr input_attrs[io_num.n_input];
    memset(input_attrs, 0, sizeof(input_attrs));
    for (int i = 0; i < io_num.n_input; i++) {
        input_attrs[i].index = i;
        ret = rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs[i]),
                         sizeof(rknn_tensor_attr));
        if (ret < 0) {
            printf("rknn_init error ret=%d\n", ret);
            return -1;
        }
        printRKNNTensor(&(input_attrs[i]));
    }
    /* 
    查询模型的输出属性,并将输出属性信息打印到控制台
     */
    rknn_tensor_attr output_attrs[io_num.n_output];
    memset(output_attrs, 0, sizeof(output_attrs));
    for (int i = 0; i < io_num.n_output; i++) {
        output_attrs[i].index = i;
        ret = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]),
                         sizeof(rknn_tensor_attr));
        printRKNNTensor(&(output_attrs[i]));
    }

    /*
     确定模型的输入格式(NCHW或NHWC)以及输入的宽度、高度和通道数,并将这些信息输出到控制台。
     */
    int input_channel = 3;
    int input_width = 0;
    int input_height = 0;
    if (input_attrs[0].fmt == RKNN_TENSOR_NCHW) {
        printf("model is NCHW input fmt\n");
        input_width = input_attrs[0].dims[0];
        input_height = input_attrs[0].dims[1];
        printf("input_width=%d input_height=%d\n", input_width, input_height);
    } else {
        printf("model is NHWC input fmt\n");
        input_width = input_attrs[0].dims[2];
        input_height = input_attrs[0].dims[1];
        printf("input_width=%d input_height=%d\n", input_width, input_height);
    }
 
    printf("model input height=%d, width=%d, channel=%d\n", input_height, input_width,
           input_channel);
 
 
    /* Init input tensor 
    准备模型推理所需的输入数据
    */
    rknn_input inputs[1];  //定义了一个 rknn_input 数组 inputs,其中包含一个元素
    memset(inputs, 0, sizeof(inputs)); // 使用 memset 函数将 inputs 数组的内存清零,以确保所有属性初始值为0。
    // 设置 inputs[0] 的属性。
    inputs[0].index = 0; //将 index 设置为0,表示这是模型的第一个输入。
    //将输入数据的指针 img_resize.data 赋值给 inputs[0].buf。这表示输入数据的实际内容存储在 img_resize.data 中,而 inputs[0].buf 指向该数据。
    inputs[0].buf = img_resize.data; 
    inputs[0].type = RKNN_TENSOR_UINT8; //表示输入数据的数据类型为无符号8位整数(uint8)。
    // 将 inputs[0].size 设置为输入数据的大小,即输入数据的宽度 (input_width)、高度 (input_height) 和通道数 (input_channel) 的乘积。这表示输入数据的总字节数。
    inputs[0].size = input_width * input_height * input_channel;
    inputs[0].fmt = RKNN_TENSOR_NHWC; //表示输入数据的格式为 NHWC
    inputs[0].pass_through = 0; //表示在输入数据到达模型之前,不对输入数据进行任何处理。
 
    /* Init output tensor
    用于进行模型的推理(inference)过程,将输入数据输入到模型中并获取输出结果。
     */
    rknn_output outputs[io_num.n_output]; //定义了一个 rknn_output 数组 outputs,用于存储模型的输出数据
    memset(outputs, 0, sizeof(outputs));

    //want_float 属性表示是否希望输出结果为浮点数(float)。将它设置为1表示希望输出为浮点数。这通常在需要对输出进行后处理时使用。
    for (int i = 0; i < io_num.n_output; i++) {
            outputs[i].want_float = 1;
    }

    printf("img.cols: %d, img.rows: %d\n", img_resize.cols, img_resize.rows);
    // auto t1=std::chrono::steady_clock::now(); //记录当前时间,用于计算推理时间
    //rknn_inputs_set 函数用于将输入数据绑定到 RKNN 上下文,以便进行推理。
    gettimeofday(&start_time, NULL);
    rknn_inputs_set(ctx, io_num.n_input, inputs); 
    ret = rknn_run(ctx, NULL); //执行模型的推理过程
    if (ret < 0) {
        printf("ctx error ret=%d\n", ret);
        return -1;
    }
    ret = rknn_outputs_get(ctx, io_num.n_output, outputs, NULL); //用于从 RKNN 上下文中获取输出数据
    //毫秒级
    // auto t2=std::chrono::steady_clock::now(); //获取当前时间
    // double dr_ms=std::chrono::duration<double,std::milli>(t2-t1).count(); //计算推理时间
    gettimeofday(&stop_time, NULL);
    printf("once run use %f ms\n", (__get_us(stop_time) - __get_us(start_time)) / 1000);
    // printf("%lf ms\n",dr_ms);
    if (ret < 0) {
        printf("outputs error ret=%d\n", ret);
        return -1;
    }
    // rknn_perf_detail perf_detail;
    // ret = rknn_query(ctx, RKNN_QUERY_PERF_DETAIL, &perf_detail, sizeof(perf_detail));
    // printf("Perf detail:\n");  
    // printf("process_detil : %s",perf_detail.perf_data);
    
    // printf(&perf_detail);
    
    /* Post process 
    后处理(post-process)模型输出;
    out_scales 和 out_zps,用于存储输出数据的缩放因子和零点偏移值
    */
    std::vector<float> out_scales;
    std::vector<uint8_t> out_zps;
    for (int i = 0; i < io_num.n_output; ++i) {
        out_scales.push_back(output_attrs[i].scale);
        out_zps.push_back(output_attrs[i].zp);
    }
    gettimeofday(&start_time, NULL);
    //通过比较 post_process_type 的值是否等于 "fp",来确定是否进行后处理。如果 post_process_type 是 "fp",则调用 post_process_u8 函数进行后处理。
    if (strcmp(post_process_type, "fp") == 0) {
        post_process_u8((float *) outputs[0].buf,img_resize,
                        320, 320);
    }
    gettimeofday(&stop_time, NULL);
    printf("process use %f ms\n", (__get_us(stop_time) - __get_us(start_time)) / 1000);

    /* 
    用于释放模型推理过程中获取的输出数据的内存,以便避免内存泄漏。
     */
    ret = rknn_outputs_release(ctx, io_num.n_output, outputs);
 
    if (ret < 0) {
        printf("rknn_query fail! ret=%d\n", ret);
        goto Error;
    }
    /* 
    错误处理部分,当在前面的代码执行过程中发生错误时,将会跳转到 Error 标签处进行错误处理。
     */
    Error:
    if (ctx > 0)
        rknn_destroy(ctx);
    if (model)
        free(model);
    if (fp)
        fclose(fp);
    return 0;
}

4、运行sh文件,开始编译

编译成功!

三、在板端运行

将编译好的可执行文件(install下的文件)全部送入到板端任意位置,执行以下命令即可(根据自己的路经修改):

./rknn_bisenetv2_demo ./model/RK3566_RK3568/.rknn ./image

后续等加入3T的计算棒之后,速度应该会更快。

点个赞呗!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AQS的简单说明

1.概述 AQS全称AbstractQueuedSynchronizer&#xff0c;是用来实现锁或者队列同步器的公共基础部分的抽象实现&#xff0c;是整个JUC体系的基石&#xff0c;用于解决锁分配给谁的问题&#xff0c;ReentrantLock底层的实现就是AQS。 2.AQS实现原理 AQS内部有一个由volatile修…

正点原子嵌入式linux驱动开发——Linux内核顶层Makefile详解

之前的几篇学习笔记重点讲解了如何移植uboot到STM32MP157开发板上&#xff0c;从本章就开始学习如何移植Linux内核。 同uboot一样&#xff0c;在具体移植之前&#xff0c;先来学习一下Linux内核的顶层Makefile文件&#xff0c;因为顶层 Makefile控制着Linux内核的编译流程。 L…

如何在Apache和Resin环境中实现HTTP到HTTPS的自动跳转:一次全面的探讨与实践

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

提取歌曲伴奏?用对软件一键帮你搞定~

相信大家经常想获取某首歌曲的伴奏&#xff0c;但是不知从何下手&#xff0c;今天这篇教程给大家分享一个超神奇软件&#xff0c;一键提取歌曲伴奏&#xff01; 第一步&#xff1a;打开【音分轨】APP&#xff0c;进入首页点击【人声分离】 第二步&#xff1a;选择导入方式&…

多电脑之间无线访问文件夹传输文件之“电子神偷”

目录 应用场景说明网络共享文件功能开启步骤1&#xff1a;确保电脑开启网络共享功能步骤2&#xff1a;在自己电脑某个盘创建一个文件夹&#xff0c;作为共享文件夹步骤3&#xff1a;查看当前电脑的用户名和ip地址 访问网络共享文件夹&#xff0c;在电脑B访问获取电脑A的文件数据…

Windows版MySql8.0安装(亲测成功!)

下载 下载地址&#xff1a;点我下载 下载完成后将其解压到自定义目录下,我所有的软件都保存在C:\zhushanglin\WindowsSoft&#xff0c;解压完成后会看见以下目录: 配置环境变量 此电脑 右键,然后点属性&#xff0c;步骤如下: 新建MYSQL_HOME系统变量 编辑Path系统变量&a…

读论文:Real-Time Encrypted Traffic Classification via Lightweight Neural Networks

基于轻量级神经网络的实时加密流量分类 0、摘要 提出一种轻量级模型&#xff0c;设计原则“maximize the reuse of thin modules”&#xff0c;thin modules采用多头注意和一维卷积网络。由于所有数据包的一步交互和多头注意力机制的并行计算&#xff0c;所提出的模型的优势是…

RF元素定位

元素定位方式&#xff1a;id, name, link, partial_link_text, xpath, css id 【登录输入框】id session_email_or_mobile_number input text id session_email_or_mobile_numbername 【登录输入框】name session[email_or_mobile_number] input text name sessi…

react-antd 文件导入按钮增加一个加载状态

1、效果图实例: 2、部分代码 2.1 props : 2.2 handleChange、上传的文件检验 : construction中定义 construction(props) { super(props); this.state { loadingStaus: flase, loadingDisabled: flase, // 作用:按钮如果在加 载状态中&#xff0c;没…

Android多线程学习:线程

一、概念 进程&#xff1a;系统资源分配的基本单位&#xff0c;进程之间相互独立&#xff0c;不能直接访问其他进程的地址空间。 线程&#xff1a;CPU调度的基本单位&#xff0c;线程之间共享所在进程的资源&#xff0c;包括共享内存&#xff0c;公有数据&#xff0c;全局变量…

【Pod】

Pod 一、Pod基本概念二、Pod的使用方式pause容器&#xff08;pod的基础容器&#xff09;核心功能pause容器使得Pod中所有容器可以共享两种资源&#xff1a;网络和存储网络存储 三、Pod分类自主式Pod/静态pod控制器管理的Pod 四、三种容器五、镜像拉取策略&#xff08;image Pul…

云计算安全和云原生安全的关系

云计算安全(Cloud Computing Security)指的是在云环境中保护数据、应用程序和基础设施的安全性。它包括保护云服务提供商的基础设施和平台&#xff0c;以及云服务用户的数据和应用程序。 云原生安全(Cloud-Native Security)则是指在云原生环境中保护应用程序和服务的安全性。云…

谁说手机没有高质量抓拍?华为Mate 60系列与Mate X5让你体验“时间凝固”!

我们日常拍照时&#xff0c;经常会出现“照片糊了”的现象&#xff0c;这是由于被拍摄的人或者物快速移动导致。 来源网图&#xff0c;侵删 抓拍&#xff0c;Snap photography&#xff0c;“抓住时机&#xff0c;把瞬间出现的情景拍摄下来拍照”&#xff0c;又名写实抓拍&…

手写Demo体验volatile可见性的作用

volatile是java的关键字&#xff0c;作用&#xff1a;①保证线程间的可见性&#xff1b;②防止指令重排。下面看一个demo&#xff0c;启动2个线程&#xff0c;一个线程读取flag变量的值&#xff0c;另外一个线程修改flag变量的值。 public class VolatileDemo {private static…

前端自动化测试入门教程

&#x1fab4; 背景 前端的自动化测试主要可以分为以下四种&#xff1a; 单元测试&#xff08;Unit Test&#xff09;&#xff1a;对一个函数/组件进行测试&#xff0c;一般用于公共函数/公共组件的测试维护。常用框架有 Jest、Jasmine、Mocha等&#xff1b; 集成测试&#x…

VR全景拍摄酒店,为用户消除“不透明度”

近日在各大社交平台上&#xff0c;出现了不少吐槽国庆期间酒店价格太贵的帖子&#xff0c;而一些热门旅游地的度假酒店、网红民宿的热门房型已经“一房难求”&#xff0c;这就出现酒店房型与预定房型不同的现象出现&#xff0c;VR全景拍摄技术同酒店行业的结合&#xff0c;就可…

韩语学习|韩语零基础|柯桥韩语学校,每日一词

今日一词:개방도 평지 韩语每日一词打卡:개방도[개방도]【名词】开放度,开放程度 原文&#xff1a;한 지역의 개방도는 경제 발전 수준에 달려 있습니다. 意思&#xff1a;一个地区的开放程度取决于经济发展水平。 【原文分解】 1、경제[경제]经济 2、지역[지역]地域 3、발전[발…

代码随想录算法训练营第23期day12| 239. 滑动窗口最大值 、347. 前K个高频元素

目录 一、&#xff08;leetcode 239&#xff09;滑动窗口最大值​​​jiao 二、&#xff08;leetcode 347&#xff09;前 K 个高频元素 优先级队列与大小顶堆 一、&#xff08;leetcode 239&#xff09;滑动窗口最大值​​​jiao 力扣题目链接 状态&#xff1a;待回顾&…

kafka、rabbitmq 、rocketmq的区别

一、语言不同 RabbitMQ是由内在高并发的erlanng语言开发&#xff0c;用在实时的对可靠性要求比较高的消息传递上。 kafka是采用Scala语言开发&#xff0c;它主要用于处理活跃的流式数据,大数据量的数据处理上 二、结构不同 RabbitMQ采用AMQP&#xff08;Advanced Message Q…

Excel·VBA使用ADO读取工作簿工作表数据

目录 查询遍历写入数组查询整体写入数组查询工作簿所有工作表名称查询工作簿所有工作表数据 不打开工作簿读取数据&#xff0c;以下举例都为《ExcelVBA合并工作簿》中 7&#xff0c;合并子文件夹同名工作簿中同名工作表&#xff0c;纵向汇总数据所举例的工作簿&#xff0c;使用…