【高性能计算】opencl语法及相关概念(四):结合opencv进行图像高斯模糊处理

news2024/12/27 11:47:31

目录

  • 高斯模糊简介
  • 主函数:host端
  • 设备端函数:mywork.cl
  • 效果图对比

高斯模糊简介

高斯模糊是一种常用的图像处理技术,用于减少图像中的噪点和细节,并实现图像的平滑效果。它是基于高斯函数的卷积操作,通过对每个像素周围的邻域像素进行加权平均来实现模糊效果。

具体而言,高斯模糊通过在图像上滑动一个卷积核,将卷积核与输入图像的对应像素进行一一相乘,并将结果相加,从而产生输出图像的每个像素值。这个卷积核是一个二维高斯函数,它的形状决定了模糊的程度。在高斯函数中,离中心像素越远的像素会被赋予更小的权重,从而降低了离中心像素的贡献,实现模糊的效果。

通过调整高斯核的大小和标准差参数,可以控制模糊的程度。较大的核和较大的标准差会导致更强烈的模糊效果,而较小的核和较小的标准差则会产生更细微的模糊。

主函数:host端

#include <iostream>
#include <fstream>
#include <sstream>
#include <string.h>
 
#ifdef __APPLE__
#include <OpenCL/cl.h>
#else
#include <CL/cl.h>
#endif
#include <opencv2/opencv.hpp>

 
//在第一个平台中创建只包括GPU的上下文
cl_context CreateContext()
{
    cl_int errNum;
    cl_uint numPlatforms;
    cl_platform_id firstPlatformId;
    cl_context context = NULL;
 
    // 选择第一个平台
    errNum = clGetPlatformIDs(1, &firstPlatformId, &numPlatforms);
    if (errNum != CL_SUCCESS || numPlatforms <= 0)
    {
        std::cerr << "Failed to find any OpenCL platforms." << std::endl;
        return NULL;
    }
 
    // 接下来尝试通过GPU设备建立上下文
    cl_context_properties contextProperties[] =
    {
        CL_CONTEXT_PLATFORM,
        (cl_context_properties)firstPlatformId,
        0
    };
    context = clCreateContextFromType(contextProperties, CL_DEVICE_TYPE_CPU,
                                      NULL, NULL, &errNum);
    if (errNum != CL_SUCCESS)
    {
        std::cout << "Could not create GPU context, trying CPU..." << std::endl;
        context = clCreateContextFromType(contextProperties, CL_DEVICE_TYPE_CPU,
                                          NULL, NULL, &errNum);
        if (errNum != CL_SUCCESS)
        {
            std::cerr << "Failed to create an OpenCL GPU or CPU context." << std::endl;
            return NULL;
        }
    }
 
    return context;
}
 
//在第一个设备上创建命令队列
cl_command_queue CreateCommandQueue(cl_context context, cl_device_id *device)
{
    cl_int errNum;
    cl_device_id *devices;
    cl_command_queue commandQueue = NULL;
    size_t deviceBufferSize = -1;
 
    // 首先获得设备的信息
    errNum = clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL, &deviceBufferSize);
    if (errNum != CL_SUCCESS)
    {
        std::cerr << "Failed call to clGetContextInfo(...,GL_CONTEXT_DEVICES,...)";
        return NULL;
    }
 
    if (deviceBufferSize <= 0)
    {
        std::cerr << "No devices available.";
        return NULL;
    }
 
    //为设备分配内存
    devices = new cl_device_id[deviceBufferSize / sizeof(cl_device_id)];
    errNum = clGetContextInfo(context, CL_CONTEXT_DEVICES, deviceBufferSize, devices, NULL);
    if (errNum != CL_SUCCESS)
    {
        std::cerr << "Failed to get device IDs";
        return NULL;
    }
 
    // 选择第一个设备并为其创建命令队列
    cl_queue_properties properties[] = {0};
    commandQueue = clCreateCommandQueueWithProperties(context, devices[0], properties, NULL);
    if (commandQueue == NULL)
    {
        std::cerr << "Failed to create commandQueue for device 0";
        return NULL;
    }
    
    //释放信息
    *device = devices[0];
    delete [] devices;
    return commandQueue;
}
 
//  创建OpenCL程序对象
cl_program CreateProgram(cl_context context, cl_device_id device, const char* fileName)
{
    cl_int errNum;
    cl_program program;
 
    std::ifstream kernelFile(fileName, std::ios::in);
    if (!kernelFile.is_open())
    {
        std::cerr << "Failed to open file for reading: " << fileName << std::endl;
        return NULL;
    }
 
    std::ostringstream oss;
    oss << kernelFile.rdbuf();
 
    std::string srcStdStr = oss.str();
    const char *srcStr = srcStdStr.c_str();
    program = clCreateProgramWithSource(context, 1,
                                        (const char**)&srcStr,
                                        NULL, NULL);
    if (program == NULL)
    {
        std::cerr << "Failed to create CL program from source." << std::endl;
        return NULL;
    }
 
    errNum = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
    if (errNum != CL_SUCCESS)
    {
        // 输出错误信息
        char buildLog[16384];
        clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG,
                              sizeof(buildLog), buildLog, NULL);
 
        std::cerr << "Error in kernel: " << std::endl;
        std::cerr << buildLog;
        clReleaseProgram(program);
        return NULL;
    }
 
    return program;
}
 
 
//清除资源
void Cleanup(cl_context context, cl_command_queue commandQueue,
             cl_program program, cl_kernel kernel, cl_mem imageObjects[2],
             cl_sampler sampler)
{
    for (int i = 0; i < 2; i++)
    {
        if (imageObjects[i] != 0)
            clReleaseMemObject(imageObjects[i]);
    }
    if (commandQueue != 0)
        clReleaseCommandQueue(commandQueue);
 
    if (kernel != 0)
        clReleaseKernel(kernel);
 
    if (program != 0)
        clReleaseProgram(program);
 
    if (sampler != 0)
        clReleaseSampler(sampler);
 
    if (context != 0)
        clReleaseContext(context);
 
}
const char* GetOpenCLErrorString(cl_int errorCode)
{
    switch (errorCode) {
        case CL_SUCCESS:
            return "CL_SUCCESS";
        case CL_DEVICE_NOT_FOUND:
            return "CL_DEVICE_NOT_FOUND";
        case CL_INVALID_VALUE:
            return "CL_INVALID_VALUE";
        // 其他错误码的处理
        default:
            return "Unknown error code";
    }
}

cl_mem LoadImage(cl_context context, char* fileName, int& width, int& height)
{
    cv::Mat image = cv::imread(fileName, cv::IMREAD_COLOR);
    
    if (image.empty())
    {
        std::cerr << "Error loading image" << std::endl;
        return 0;
    }
    
    /* 修改:将图像数据从 BGR 转换为 RGBA 格式 */
    //一般图像算法都为rgba的格式
    cv::cvtColor(image, image, cv::COLOR_BGR2RGBA);

    width = image.cols;
    height = image.rows;

    cl_image_format clImageFormat;
    clImageFormat.image_channel_order = CL_RGBA;
    clImageFormat.image_channel_data_type = CL_UNSIGNED_INT8;

    cl_int errNum;
    cl_mem clImage;

    cl_image_desc clImageDesc;
    memset(&clImageDesc, 0, sizeof(cl_image_desc));
    clImageDesc.image_type = CL_MEM_OBJECT_IMAGE2D;
    clImageDesc.image_width = width;
    clImageDesc.image_height = height;
    clImageDesc.image_row_pitch = 0;
    clImageDesc.image_slice_pitch = 0;
    clImageDesc.num_mip_levels = 0;
    clImageDesc.num_samples = 0;
    clImageDesc.image_depth = 1;

    /* 移除:不再需要使用缓冲区 */

    clImage = clCreateImage(
        context,
        CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
        &clImageFormat,
        &clImageDesc,
        image.data,
        &errNum
    );
    if (errNum != CL_SUCCESS)
    {
        std::cerr << "Error creating CL image object:" << GetOpenCLErrorString(errNum) << std::endl;
        return 0;
    }

    return clImage;
}

void saveRGBAtoJPG(const char* filename, const char* buffer, int width, int height) {
    cv::Mat image(height, width, CV_8UC4, (void*)buffer);
    // 转换 RGBA 到 BGR 格式
    cv::cvtColor(image, image, cv::COLOR_RGBA2BGR);
    // 保存图像为 JPG 文件
    cv::imwrite(filename, image);
}


//获取最接近的倍数
//任务均分:在并行计算中,经常需要将一个较大的任务或数据集分成多个小任务或数据块,分配给不同的处理单元并行执行。
//使用 RoundUp 函数可以将总任务数 globalSize 向上舍入到 groupSize 的倍数,确保每个处理单元都获得相等的任务数,避免了任务不均衡的情况。

//内存对齐:在一些场景下,为了提高内存访问的效率,需要将数据按照一定的对齐方式存储,即确保数据的起始地址和长度都是某个特定数值的倍数。
//通过使用 RoundUp 函数,可以将数据长度 globalSize 向上舍入到 groupSize 的倍数,以满足对齐的要求,从而获得更好的内存访问性能。
size_t RoundUp(int groupSize, int globalSize)
{
    int r = globalSize % groupSize;
    if(r == 0)
    {
         return globalSize;
    }
    else
    {
         return globalSize + groupSize - r;
    }
}
// 创建输出的图像对象
cl_mem CreateOutputImage(cl_context context, int width, int height)
{
    cl_image_format clImageFormat;
    clImageFormat.image_channel_order = CL_RGBA;
    clImageFormat.image_channel_data_type = CL_UNSIGNED_INT8;

    cl_int errNum;
    cl_mem clImage;

    cl_image_desc clImageDesc;
    memset(&clImageDesc, 0, sizeof(cl_image_desc));
    clImageDesc.image_type = CL_MEM_OBJECT_IMAGE2D;
    clImageDesc.image_width = width;
    clImageDesc.image_height = height;
    clImageDesc.image_row_pitch = 0;
    clImageDesc.image_slice_pitch = 0;
    clImageDesc.num_mip_levels = 0;
    clImageDesc.num_samples = 0;
    clImageDesc.image_depth = 1;

    clImage = clCreateImage(
        context,
        CL_MEM_WRITE_ONLY,
        &clImageFormat,
        &clImageDesc,
        NULL,
        &errNum
    );

    if (errNum != CL_SUCCESS)
    {
        std::cerr << "Error creating output CL image object: " << GetOpenCLErrorString(errNum) << std::endl;
        return 0;
    }

    return clImage;
}
 
int main()
{
    cl_context context = 0;
    cl_command_queue commandQueue = 0;
    cl_program program = 0;
    cl_device_id device = 0;
    cl_kernel kernel = 0;
    //这段代码定义了一个长度为 2 的 cl_mem 数组 imageObjects,并初始化所有元素为 0。
    cl_mem imageObjects[2] = { 0, 0 };
    //图像采样器 (cl_sampler) 可以与图像对象 (cl_mem) 一起使用,
    //用于在内核函数中从图像中获取特定位置像素的值。它控制着采样的方式,
    //以及在读取图像时如何处理越界的、边界问题,以及如何进行插值以获得平滑的结果。
    cl_sampler sampler = 0;
    cl_int errNum;
 
 
 
 
    // 创建上下文
    context = CreateContext();
    if (context == NULL)
    {
        std::cerr << "Failed to create OpenCL context." << std::endl;
        return 1;
    }
 
    // 创建命令队列
    commandQueue = CreateCommandQueue(context, &device);
    if (commandQueue == NULL)
    {
        Cleanup(context, commandQueue, program, kernel, imageObjects, sampler);
        return 1;
    }
 
    // 确保设备支持这种图像格式
    cl_bool imageSupport = CL_FALSE;
    clGetDeviceInfo(device, CL_DEVICE_IMAGE_SUPPORT, sizeof(cl_bool),
                    &imageSupport, NULL);
    if (imageSupport != CL_TRUE)
    {
        std::cerr << "OpenCL device does not support images." << std::endl;
        Cleanup(context, commandQueue, program, kernel, imageObjects, sampler);
        return 1;
    }
 
    // 加载图像
    int width, height;
    char* imagePath = "/work/myopencl/build/test.jpg";
    imageObjects[0] = LoadImage(context, imagePath, width, height);
    if (imageObjects[0] == 0)
    {
        std::cerr << "Error loading: " << std::string("123.png") << std::endl;
        Cleanup(context, commandQueue, program, kernel, imageObjects, sampler);
        return 1;
    }
 
    // 创建输出的图像对象
    imageObjects[1] = CreateOutputImage(context, width, height);
    if(imageObjects[1] == 0)
    {
        std::cerr << "Error creating CL output image object." << std::endl;
        Cleanup(context, commandQueue, program, kernel, imageObjects, sampler);
        return 1;
    }
 
 
    // 创建采样器对象
    //寻址模式(Addressing Mode)是一种定义在像素采样过程中如何处理超出纹理边界的采样坐标的方法。它决定了当采样坐标超过纹理边界时,如何获取纹理中的值。
    //CL_FALSE:这是一个布尔值参数,表示使用非规范化的采样坐标。非规范化坐标意味着采样坐标在整数范围内,而不是标准化到 [0, 1] 的范围。
    //CL_ADDRESS_CLAMP_TO_EDGE:这个参数指定了图像采样器的地址模式,CL_ADDRESS_CLAMP_TO_EDGE 表示超出图像边界的采样坐标将被截断到最近的边缘像素的颜色值。
    //CL_FILTER_NEAREST:这个参数指定了图像采样器的过滤方式,CL_FILTER_NEAREST 表示使用最近邻插值,也就是返回与采样坐标最近的像素值,不进行插值计算。
    cl_sampler_properties samplerProps[] = {
        CL_SAMPLER_NORMALIZED_COORDS, CL_FALSE, // 非规范化坐标
        CL_SAMPLER_ADDRESSING_MODE, CL_ADDRESS_CLAMP_TO_EDGE, // 寻址模式为 CL_ADDRESS_CLAMP_TO_EDGE
        CL_SAMPLER_FILTER_MODE, CL_FILTER_NEAREST, // 过滤模式为 CL_FILTER_NEAREST
        0 // 列表结束符
    };

    sampler = clCreateSamplerWithProperties(context, samplerProps, &errNum);
    if (errNum != CL_SUCCESS)
    {
        std::cerr << "Failed to create sampler: " << GetOpenCLErrorString(errNum) << std::endl;
        return 0;
    }
 
    // 创建OpenCL程序对象
    program = CreateProgram(context, device, "/work/myopencl/resource/mywork.cl");
    if (program == NULL)
    {
        Cleanup(context, commandQueue, program, kernel, imageObjects, sampler);
        return 1;
    }
 
    // 创建OpenCL核
    kernel = clCreateKernel(program, "gaussian_filter", NULL);
    if (kernel == NULL)
    {
        std::cerr << "Failed to create kernel" << std::endl;
        Cleanup(context, commandQueue, program, kernel, imageObjects, sampler);
        return 1;
    }
 
    // 设定参数
    //该操作的目的是将 clSetKernelArg 函数的返回值(错误码)累积到 errNum 变量中
    errNum = clSetKernelArg(kernel, 0, sizeof(cl_mem), &imageObjects[0]);
    errNum |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &imageObjects[1]);
    errNum |= clSetKernelArg(kernel, 2, sizeof(cl_sampler), &sampler);
    errNum |= clSetKernelArg(kernel, 3, sizeof(cl_int), &width);
    errNum |= clSetKernelArg(kernel, 4, sizeof(cl_int), &height);
    if (errNum != CL_SUCCESS)
    {
        std::cerr << "Error setting kernel arguments." << std::endl;
        Cleanup(context, commandQueue, program, kernel, imageObjects, sampler);
        return 1;
    }
 
    size_t localWorkSize[2] = { 16, 16 };
    size_t globalWorkSize[2] =  { RoundUp(localWorkSize[0], width),
                                  RoundUp(localWorkSize[1], height) };
 
    // 将内核排队
    errNum = clEnqueueNDRangeKernel(commandQueue, kernel, 2, NULL,
                                    globalWorkSize, localWorkSize,
                                    0, NULL, NULL);
    if (errNum != CL_SUCCESS)
    {
        std::cerr << "Error queuing kernel for execution." << std::endl;
        Cleanup(context, commandQueue, program, kernel, imageObjects, sampler);
        return 1;
    }
 
    // 将输出缓冲区读回主机
    char *buffer = new char [width * height * 4];
    size_t origin[3] = { 0, 0, 0 };
    size_t region[3] = { size_t(width), size_t(height), 1};
    //在 OpenCL 中,clEnqueueReadImage 函数用于从图像对象中读取数据到主机内存。该函数的 origin 和 region 参数用于指定要读取的区域。
    //origin 参数是一个包含三个元素的数组,即 [x, y, z],指定了要读取的起始位置在图像中的坐标。
    //origin[0] 表示 x 坐标,origin[1] 表示 y 坐标,origin[2] 表示 z 坐标。对于二维图像,我们通常将 origin[2] 设置为 0。
    //region 参数也是一个包含三个元素的数组,即 [width, height, depth],指定了要读取的区域的尺寸。
    //region[0] 表示区域的宽度,region[1] 表示区域的高度,region[2] 表示区域的深度。对于二维图像,我们可以将 region[2] 设置为 1
    errNum = clEnqueueReadImage(commandQueue, imageObjects[1], CL_TRUE,
                                origin, region, 0, 0, buffer,
                                0, NULL, NULL);
    if (errNum != CL_SUCCESS)
    {
        std::cerr << "Error reading result buffer." << std::endl;
        Cleanup(context, commandQueue, program, kernel, imageObjects, sampler);
        return 1;
    }
 
    std::cout << std::endl;
    std::cout << "Executed program succesfully." << std::endl;
 
    //保存输出图像
    saveRGBAtoJPG("result.jpg", buffer, width, height);
    delete [] buffer;
    Cleanup(context, commandQueue, program, kernel, imageObjects, sampler);
    return 0;
}

设备端函数:mywork.cl

__kernel void gaussian_filter(__read_only image2d_t srcImg,
                              __write_only image2d_t dstImg,
                              sampler_t sampler,
                              int width, int height)
{
    float kernelWeights[9] = { 1.0f, 2.0f, 1.0f,
                               2.0f, 4.0f, 2.0f,
                               1.0f, 2.0f, 1.0f };
 
    int2 startImageCoord = (int2) (get_global_id(0) - 1, get_global_id(1) - 1);
    int2 endImageCoord   = (int2) (get_global_id(0) + 1, get_global_id(1) + 1);
    int2 outImageCoord = (int2) (get_global_id(0), get_global_id(1));
 
    if (outImageCoord.x < width && outImageCoord.y < height)
    {
        int weight = 0;
        float4 outColor = (float4)(0.0f, 0.0f, 0.0f, 0.0f);
        for( int y = startImageCoord.y; y <= endImageCoord.y; y++)
        {
            for( int x = startImageCoord.x; x <= endImageCoord.x; x++)
            {
                outColor += convert_float4(read_imageui(srcImg, sampler, (int2)(x, y)))/255.0f * (kernelWeights[weight] / 16.0f);
                weight += 1;
            }
        }
 
        //写入输出图像
        //write_imagef(dstImg, outImageCoord, outColor);//正常的高斯模糊后的图像
        //write_imagef(dstImg, outImageCoord, (float4)(1.0f,outColor.yzw));//二次处理
        float4 multipliedPixel = outColor * 255.0f;
        int4 clampedPixel = convert_int4(clamp(multipliedPixel, 0.0f, 255.0f));
        write_imagei(dstImg, outImageCoord, clampedPixel);
    }
}

int2 是 OpenCL 中的内建类型之一,用于表示二维整数向量。它由两个 int 类型的组成,分别表示 x 和 y 坐标。

read_imagef(srcImg, sampler, (int2)(x, y)) 是一个 OpenCL 内建函数,用于从图像对象中的某个二维点坐标读取指定位置的4通道像素值。

具体来说,read_imagef() 函数的作用是在给定的图像对象 srcImg 中,使用指定的采样器 sampler,读取位于 (x, y) 坐标位置的像素值。

该函数返回一个 float4 类型的像素值,其中的四个分量分别表示红色、绿色、蓝色和透明度(RGBA)。这是一个浮点型的 RGBA 值,范围通常是从 0.0 到 1.0。

注:image2d_t 是 OpenCL 中用于表示二维图像的类型。它并不是用来表示具体的内存类型,而是用来表示一个图像对象的引用。

在 OpenCL 中,图像对象是存储在设备内存中的二维图像数据。image2d_t 类型的对象实际上是对图像数据的引用,可以用于在内核函数中对图像数据进行读取和写入操作。

图像数据可以存储在不同的设备内存类型中,如全局内存、纹理内存等,具体取决于实际的硬件和内存配置。在创建图像对象时,您需要明确指定图像的内存类型,并在内核函数中使用相应的函数进行图像操作。

在内核函数中,可以使用像 read_imagefwrite_imagef 这样的函数来读取和写入 image2d_t 类型的图像对象。这些函数可以根据图像的内存类型进行相应的操作,以实现对图像数据的读写。

总结来说,image2d_t 并不是一个具体的内存类型,它是用来表示二维图像数据对象的引用,并提供一组操作函数来访问和修改图像数据。具体的图像数据存储在设备的某种内存类型中,如全局内存或纹理内存。

效果图对比

模糊后:
模糊后
模糊前:
模糊前

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

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

相关文章

单片机-如何让数码管动态显示

数码管硬件图 1、数码管 连接 74HC245 芯片 单片机IO口输出难稳定&#xff0c;需要数码管与单片机连接需要增加驱动电路&#xff0c; 使用 74HC245 abcdefgDP并联导出 74HC245 对数码管进行驱动&#xff0c;P0 是输出电流 来驱动各个段的 驱动芯片 增加电阻 是为了防止电流…

WPF实战项目十四(API篇):登录注册接口

1、新建UserDto.cs public class UserDto : BaseDto{private string userName;/// <summary>/// 用户名/// </summary>public string UserName{get { return userName; }set { userName value;OnPropertyChanged(); }}private string account;/// <summary>…

【论文阅读】自动驾驶中车道检测系统的物理后门攻击

文章目录 AbstractIntroduction 论文题目&#xff1a; Physical Backdoor Attacks to Lane Detection Systems in Autonomous Driving&#xff08;自动驾驶中车道检测系统的物理后门攻击&#xff09; 发表年份&#xff1a; 2022-MM&#xff08;ACM International Conference on…

2023开学季图书馆荐八一新书《乡村振兴战略下传统村落文化旅游设计 》中大许少辉博士后著

2023开学季图书馆荐八一新书《乡村振兴战略下传统村落文化旅游设计 》中大许少辉博士后著

图像扭曲之旋转

源码&#xff1a; void twirl(cv::Mat& src,cv::Mat& dst,double angle,double radius) {dst.create(src.rows, src.cols, CV_8UC3);dst.setTo(0);int radius2radius*radius;int cx src.cols / 2;int cy src.rows / 2;int distance,distance2 0;for (int h 0; h &…

2023年信息安全管理与评估任务书模块一网络平台搭建与设备安全防护

全国职业院校技能大赛 高等职业教育组 信息安全管理与评估 任务书 模块一 网络平台搭建与设备安全防护 比赛时间 本阶段比赛时长为180分钟。 赛项信息 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 第一阶段 网络平台搭建与设备安全防护 任务1 网络平台搭建 9:00- 12:00 …

JavaExcel:自动生成数据表并插入数据

故事背景 出于好奇&#xff0c;当下扫描excel读取数据进数据库 or 导出数据库数据组成excel的功能层出不穷&#xff0c;代码也是前篇一律&#xff0c;poi或者easy excel两种SDK的二次利用带来了各种封装方法。 那么为何不能直接扫描excel后根据列的属性名与行数据的属性建立S…

C 语言控制台打印不同颜色字体方法

方法 1&#xff0c;使用 printf 格式化打印&#xff0c;该方法在 visual Studio 中生效&#xff0c;在 Dev C 中未生效 示例代码&#xff1a; #include <stdio.h>#define CLOUR_ON "\033[41;37m" #define CLOUR_OFF "\033[0m"int…

GIT命令只会抄却不理解?看完原理才能事半功倍!

系列文章目录 手把手教你安装Git&#xff0c;萌新迈向专业的必备一步 GIT命令只会抄却不理解&#xff1f;看完原理才能事半功倍&#xff01; 系列文章目录一、Git 的特征1. 文件系统2. 分布式 二、GIT的术语1. 区域术语2. 名词术语1. 提交对象2. 分支3. HEAD4. 标签&#xff0…

Unity UI与粒子 层级问题Camera depth Sorting Layer Order in Layer RenderQueue

Unity游戏开发中&#xff0c;模型、界面、特效等&#xff0c;需要规划好layer的概念&#xff0c;涉及到摄像机&#xff08;Camera&#xff09;、画布&#xff08;Canvas&#xff09;、Shader等相关内容。 在 Unity 中&#xff0c;渲染顺序是由多个因素共同决定的&#xff0c;大…

java八股文面试[多线程]——线程间通信方式

多个线程在并发执行的时候&#xff0c;他们在CPU中是随机切换执行的&#xff0c;这个时候我们想多个线程一起来完成一件任务&#xff0c;这个时候我们就需要线程之间的通信了&#xff0c;多个线程一起来完成一个任务&#xff0c;线程通信一般有4种方式&#xff1a; 通过 volat…

详解IP协议

在介绍IP协议之前&#xff0c;先抛出一个概念&#xff1a;IP地址的作用——定位主机&#xff0c;具有将数据从主机A跨网络传输到主机B的能力&#xff0c;有了TCP提供的策略&#xff0c;例如滑动窗口、拥塞控制等&#xff0c;IP去执行它&#xff0c;所以我们通常叫TCP/IP协议&am…

阿里云centos9stream安装宝塔+vscode(code-server)集成云端开发环境

一、 安装宝塔面板 官网 https://www.bt.cn/new/download.htm 题外话&#xff1a;虽然感觉现在宝塔没以前好用了&#xff0c;而且有centos7、8 mysql编译导致OOM服务器挂掉无法ssh登录的情况&#xff0c;但他还是远程管理服务器的好选择&#xff0c;提示宝塔只支持最新的centos…

基于广义正态分布算法优化的BP神经网络(预测应用) - 附代码

基于广义正态分布算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于广义正态分布算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.广义正态分布优化BP神经网络2.1 BP神经网络参数设置2.2 广义正态分布算法应用 4.测试结…

[管理与领导-60]:IT基层管理者 - 扩展技能 - 3 - 通过面试招到合适的人选

目录 前言&#xff1a; 一、招聘 1.1 什么是招聘 1.2 招聘 VS 招募 1.3 甄选 1.4 招聘中的重要原则 1.5 招聘的本质 1.6 人才匹配的维度 1.7 人员招聘中的误区 二、面试 2.1 何为面试 2.2 为什么面试 2.3 面试的注意事项 2.4 面试的误区 2.5 如何进行面试 前言…

Vulnstack----5、ATTCK红队评估实战靶场五

文章目录 一 环境搭建二 外网渗透三 内网信息收集3.1 本机信息收集3.2 域内信息收集 四 横向移动4.1 路由转发和代理通道4.2 抓取域用户密码4.3 使用Psexec登录域控4.4 3389远程登录 五、痕迹清理 一 环境搭建 1、项目地址 http://vulnstack.qiyuanxuetang.net/vuln/detail/7/ …

交互设计都有哪些准则?

UI交互设计的本质不是完全基于用户的需求&#xff0c;而是交互设计师需要学习根据用户描述的产品形式来了解用户需要什么。 在交互设计过程中&#xff0c;遵循科学交互设计的本质是整个交互设计过程的重要组成部分&#xff0c;这与产品使用过程中给用户带来的体验密切相关。本…

matlab函数 状态空间系统ss、能控性矩阵ctrb、矩阵的秩rank、能控标准型canon、零极点配置place、系统极点pole等函数(线性定常系统)

matlab函数 能控性矩阵ctrb、能控标准型canon、零极点配置place 第一章&#xff0c;线性定常系统 ss 如果已知线性定常系统的ABCD四个矩阵&#xff0c;可以得到状态空间系统 其他更具体的用法请直接看帮助文档。 用法&#xff1a;ss(A,B,C,D) 假如 可以输入 A [-1.5,-2…

Java连接websocket优雅断线、重连功能

为了实现优雅重连和重试&#xff0c;您需要在代码中添加一些逻辑来处理连接失败或断开连接的情况。 实现代码如下&#xff1a; import javax.websocket.*; import java.io.IOException;ClientEndpoint public class WebSocketClientEndpoint {private Session userSession n…

【区块链 | IPFS】浅谈 | IPFS数据存储原理

IPFS在数据存储方面采用的是分散式的文件存储,区别于HTTP协议的位置寻址,IPFS是基于内容寻址,当文件上传到IPFS节点存储时,节点会对文件进行Merkle DAG(默克尔有向无环图)的格式组织分块存储,在存储完毕后,文件将以Merkle DAG的根哈希数来表示该文件,用户可以从IPFS构…