【opencv】示例-image_alignment.cpp 利用ECC 算法进行图像对齐

news2024/10/6 16:21:14

4c10ad6b13fdc94a8674ef9ad9f5cbb9.png

444d4e512471b5f4232917db04381d54.png

affine

imshow("image", target_image);
imshow("template", template_image);
imshow("warped image", warped_image);
imshow("error (black: no error)", abs(errorImage) * 255 / max_of_error);

2778b45db8f3be9d9fb6bf48f672c073.png

homography

这段代码是一个利用ECC (Enhanced Correlation Coefficient) 算法进行图像对齐的示例。代码首先包含了OpenCV库的头文件,并且使用了OpenCV和标准库的命名空间。然后定义了几个函数和宏进行图像变换矩阵的操作,定义了一些用于解析命令行参数的关键字。

main 函数中,首先使用CommandLineParser解析命令行参数,之后加载输入图像和模板图像。如果只给出了输入图像,代码会生成一个随机的模板图像。如果给定了输入变换矩阵,则会使用该矩阵初始化。若变换类型为仿射或相似变换等,会初始化对应的变换矩阵。

代码接着执行findTransformECC函数来计算图像间的几何变换。计算完成后,保存变换矩阵和变换后的图像,并对结果进行显示(根据具体的verbose参数决定是否显示)。

整个过程包括了对图像的读取、几何变换矩阵的初始化与读取、使用ECC算法进行图像对齐、结果的保存与显示。通过这个示例,可以了解到图像处理中图像对齐的基本流程和方法

/*
* 本示例展示了使用findTransformECC函数实现图像对齐的ECC算法
* 
* Demo加载一个图像(默认为fruits.jpg),并基于给定的运动类型人工创建一个模板图像。
* 当给出两幅图像时,第一幅是输入图像,第二幅定义模板图像。在后一种情况下,
* 您还可以解析warp的初始化。
* 
* 输入和输出的warp文件由原始的warp(变换)元素组成。
* 
* 作者: G. Evangelidis, 法国国家信息与自动化研究所(INRIA), Grenoble,
*       M. Asbach, 德国Fraunhofer IAIS, St. Augustin
*/


// 导入opencv库的各个模块
#include <opencv2/imgcodecs.hpp> // 图像编解码
#include <opencv2/highgui.hpp>   // 图像显示
#include <opencv2/video.hpp>     // 视频处理
#include <opencv2/imgproc.hpp>   // 图像处理
#include <opencv2/core/utility.hpp> // OpenCV实用函数


#include <stdio.h>  // 包含基本输入输出函数
#include <string>   // 包含字符串相关功能
#include <time.h>   // 包含处理时间相关功能
#include <iostream> // 包含输入输出流功能
#include <fstream>  // 包含文件流操作功能


using namespace cv;  // 使用OpenCV命名空间
using namespace std; // 使用标准库命名空间


// 向前声明函数(函数将在下面定义)
static void help(const char** argv);
static int readWarp(string iFilename, Mat& warp, int motionType);
static int saveWarp(string fileName, const Mat& warp, int motionType);
static void draw_warped_roi(Mat& image, const int width, const int height, Mat& W);




// 用于向齐次变换矩阵赋值的宏定义(x和y是坐标,H是变换矩阵)
#define HOMO_VECTOR(H, x, y)\
    H.at<float>(0,0) = (float)(x);\
    H.at<float>(1,0) = (float)(y);\
    H.at<float>(2,0) = 1.;


//  用于从齐次坐标中读取值的宏定义(X是齐次坐标,x和y是提取的坐标)
#define GET_HOMO_VALUES(X, x, y)\
    (x) = static_cast<float> (X.at<float>(0,0)/X.at<float>(2,0));\
    (y) = static_cast<float> (X.at<float>(1,0)/X.at<float>(2,0));


// 定义命令行参数,用于程序运行时接收用户输入或默认值
const std::string keys =
    "{@inputImage    | fruits.jpg    | input image filename }" // 输入图像文件名,默认为fruits.jpg
    "{@templateImage |               | template image filename (optional)}" // 模板图像文件名,可选参数
    "{@inputWarp     |               | input warp (matrix) filename (optional)}" // 输入变换矩阵(文件名),可选参数
    "{n numOfIter    | 50            | ECC's iterations }" // ECC算法迭代次数,默认值为50
    "{e epsilon      | 0.0001        | ECC's convergence epsilon }" // ECC算法收敛阈值,默认为0.0001
    "{o outputWarp   | outWarp.ecc   | output warp (matrix) filename }" // 输出变换矩阵的文件名,默认为outWarp.ecc
    "{m motionType   | affine        | type of motion (translation, euclidean, affine, homography) }" // 变换类型,默认为仿射变换(affine)
    "{v verbose      | 1             | display initial and final images }" // 是否显示处理前后的图像,默认为1(显示)
    "{w warpedImfile | warpedECC.png | warped input image }" // 输出变形后图像的文件名,默认为warpedECC.png
    "{h help | | print help message }" // 显示帮助信息
;


//该文件演示了 ECC 图像对齐算法的使用。 当给定一张图像时,
//模板图像是通过随机扭曲人为形成的。当给出两个图像时,可以
//通过命令行解析来初始化扭曲。如果缺少 inputWarp,则恒等变换
//会初始化算法。
// 提供程序帮助信息和命令行示例的函数
static void help(const char** argv)
{
    // 输出ECC图像对齐算法使用说明
    cout << "\nThis file demonstrates the use of the ECC image alignment algorithm. When one image"
        " is given, the template image is artificially formed by a random warp. When both images"
        " are given, the initialization of the warp by command line parsing is possible. "
        "If inputWarp is missing, the identity transformation initializes the algorithm. \n" << endl;


    // 输出仅用一个输入图像时命令行使用示例
    cout << "\nUsage example (one image): \n" // 使用示例(一个图像)
         << argv[0]  // 程序名
         << " fruits.jpg -o=outWarp.ecc "  // 输入图像,输出变换矩阵文件
            "-m=euclidean -e=1e-6 -N=70 -v=1 \n" << endl; // 运动类型,收敛阈值,迭代次数,是否显示结果


    // 输出用两个图像以及初始变换矩阵时命令行使用示例
    cout << "\nUsage example (two images with initialization): \n" // 使用示例(两个图像,带初始化)
         << argv[0]  // 程序名
         << " yourInput.png yourTemplate.png "  // 输入图像和模板图像
        "yourInitialWarp.ecc -o=outWarp.ecc -m=homography -e=1e-6 -N=70 -v=1 -w=yourFinalImage.png \n" << endl; // 输入变换矩阵,输出变换矩阵文件,变换类型,收敛阈值,迭代次数,是否显示结果,输出对齐后的图像文件
}


// 从文件中读取变换矩阵的函数
static int readWarp(string iFilename, Mat& warp, int motionType){
    // 根据不同的运动类型(motionType)确定需要从文件中读取的元素数量
    // 如果是透视变换(Homography),读取9个值,否则读取6个值
    CV_Assert(warp.type()==CV_32FC1); // 确保warp矩阵是单通道浮点型
    int numOfElements; // 存储元素的数量
    if (motionType==MOTION_HOMOGRAPHY)
        numOfElements=9; // 透视变换有9个参数
    else
        numOfElements=6; // 其它变换类型只需要6个参数


    int i; // 循环计数变量
    int ret_value; // 返回值,表示是否成功读取文件


    ifstream myfile(iFilename.c_str()); // 打开文件
    if (myfile.is_open()){ // 如果文件成功打开
        float* matPtr = warp.ptr<float>(0); // 获取矩阵的指针
        for(i=0; i<numOfElements; i++){
            myfile >> matPtr[i]; // 从文件读取每个值
        }
        ret_value = 1; // 读取成功,设置返回值为1
    }
    else { // 如果文件打开失败
        cout << "Unable to open file " << iFilename.c_str() << endl; // 输出错误信息
        ret_value = 0; // 设置返回值为0
    }
    return ret_value; // 返回结果
}


// 将变换矩阵保存到文件中的函数
static int saveWarp(string fileName, const Mat& warp, int motionType)
{
    // 确保warp矩阵是单通道浮点型
    CV_Assert(warp.type()==CV_32FC1);


    const float* matPtr = warp.ptr<float>(0); // 获取矩阵的指针
    int ret_value; // 返回值,表示是否成功保存到文件


    ofstream outfile(fileName.c_str()); // 打开或创建文件来写入
    if( !outfile ) { // 如果文件打开失败
        cerr << "error in saving "
            << "Couldn't open file '" << fileName.c_str() << "'!" << endl; // 输出错误信息到错误流
        ret_value = 0; // 设置返回值为0
    }
    else {// 如果文件成功打开, 保存warp矩阵的元素
        outfile << matPtr[0] << " " << matPtr[1] << " " << matPtr[2] << endl;
        outfile << matPtr[3] << " " << matPtr[4] << " " << matPtr[5] << endl;
        if (motionType==MOTION_HOMOGRAPHY){ // 如果是透视变换,需要保存额外3个参数
            outfile << matPtr[6] << " " << matPtr[7] << " " << matPtr[8] << endl;
        }
        ret_value = 1; // 保存成功,设置返回值为1
    }
    return ret_value; // 返回结果
}


// 在图像上绘制变换后区域边界的函数
static void draw_warped_roi(Mat& image, const int width, const int height, Mat& W)
{
    // 定义四个角点
    Point2f top_left, top_right, bottom_left, bottom_right;


    Mat H = Mat(3, 1, CV_32F); // 定义齐次坐标向量
    Mat U = Mat(3, 1, CV_32F); // 存储变换后的坐标值


    Mat warp_mat = Mat::eye(3, 3, CV_32F); // 创建一个单位矩阵


    // 将变换矩阵 W 的值赋给 warp_mat
    for (int y = 0; y < W.rows; y++)
        for (int x = 0; x < W.cols; x++)
            warp_mat.at<float>(y,x) = W.at<float>(y,x);


    // 对矩形的四个角点进行变换


    // 左上角
    HOMO_VECTOR(H, 1, 1); // 将点设置为齐次坐标形式
    gemm(warp_mat, H, 1, 0, 0, U); // 使用矩阵乘法进行变换
    GET_HOMO_VALUES(U, top_left.x, top_left.y); // 获取变换后的值


    // 右上角
    HOMO_VECTOR(H, width, 1);
    gemm(warp_mat, H, 1, 0, 0, U);
    GET_HOMO_VALUES(U, top_right.x, top_right.y);


    // 左下角
    HOMO_VECTOR(H, 1, height);
    gemm(warp_mat, H, 1, 0, 0, U);
    GET_HOMO_VALUES(U, bottom_left.x, bottom_left.y);


    // 右下角
    HOMO_VECTOR(H, width, height);
    gemm(warp_mat, H, 1, 0, 0, U);
    GET_HOMO_VALUES(U, bottom_right.x, bottom_right.y);


    // 在图像上绘制变形后的矩形边界
    line(image, top_left, top_right, Scalar(255));     // 上边界
    line(image, top_right, bottom_right, Scalar(255)); // 右边界
    line(image, bottom_right, bottom_left, Scalar(255)); // 下边界
    line(image, bottom_left, top_left, Scalar(255));  // 左边界
}


// 主函数入口
int main (const int argc, const char * argv[])
{
    // 解析命令行参数
    CommandLineParser parser(argc, argv, keys);
    parser.about("ECC demo"); // 关于本程序


    parser.printMessage(); // 打印解析的信息
    help(argv); // 显示帮助信息


    // 获取命令行参数
    string imgFile = parser.get<string>(0); // 输入图像文件名
    string tempImgFile = parser.get<string>(1); // 模板图像文件名
    string inWarpFile = parser.get<string>(2); // 初始变换矩阵文件名


    // 获取其他参数
    int number_of_iterations = parser.get<int>("n"); // 迭代次数
    double termination_eps = parser.get<double>("e"); // 精度阈值,停止条件
    string warpType = parser.get<string>("m"); // 变换类型
    int verbose = parser.get<int>("v"); // 是否显示详细信息
    string finalWarp = parser.get<string>("o"); // 最终变换矩阵保存文件
    string warpedImFile = parser.get<string>("w"); // 对齐后的图像保存文件
    if (!parser.check()) // 检查解析的参数是否合理
    {
        parser.printErrors();
        return -1; // 参数不合理则返回-1
    }
    // 确保传入的变换类型是有效的 平移、欧几里得、仿射、单应性
    if (!(warpType == "translation" || warpType == "euclidean"
        || warpType == "affine" || warpType == "homography"))
    {
        cerr << "Invalid motion transformation" << endl;
        return -1; // 无效变换类型,返回-1
    }


    // 根据变换类型设定变换模式
    int mode_temp;
    if (warpType == "translation")
        mode_temp = MOTION_TRANSLATION;
    else if (warpType == "euclidean")
        mode_temp = MOTION_EUCLIDEAN;
    else if (warpType == "affine")
        mode_temp = MOTION_AFFINE;
    else
        mode_temp = MOTION_HOMOGRAPHY;


    // 读取输入图像
    Mat inputImage = imread(samples::findFile(imgFile), IMREAD_GRAYSCALE);
    if (inputImage.empty()) // 检查图像是否成功加载
    {
        cerr << "Unable to load the inputImage" <<  endl;
        return -1; // 加载失败,返回-1
    }


    // 初始化目标图像和模板图像
    Mat target_image;
    Mat template_image;


    // 如果提供了模板图像,则读取模板图像
    if (tempImgFile != "") {
        // 将输入图像复制给目标图像
        inputImage.copyTo(target_image);
        // 加载模板图像(灰度图)
        template_image = imread(samples::findFile(tempImgFile), IMREAD_GRAYSCALE);
        // 如果模板图像加载失败,则返回错误
        if (template_image.empty()) {
            cerr << "Unable to load the template image" << endl;
            return -1;
        }
    }
    else { // 如果没有指定模板图像文件名,对输入图像应用随机变换以生成模板
        // 对输入图像进行尺寸调整,新的尺寸为216 x 216像素
        resize(inputImage, target_image, Size(216, 216), 0, 0, INTER_LINEAR_EXACT);
        
        // 声明一个Mat类型变量用于存放变换矩阵
        Mat warpGround;
        
        // 创建一个随机数生成器,种子为当前时间戳
        RNG rng(getTickCount());
        
        // 声明一个double类型变量用于存放计算得到的角度
        double angle;
        
        // 根据mode_temp来决定如何生成变换矩阵并对图像应用变换
        switch (mode_temp) {
            case MOTION_TRANSLATION:
                // 生成一个随机的平移变换矩阵
                warpGround = (Mat_<float>(2, 3) << 1, 0, (rng.uniform(10.f, 20.f)),
                    0, 1, (rng.uniform(10.f, 20.f)));
                // 应用平移变换矩阵到目标图像,获取模板图像
                warpAffine(target_image, template_image, warpGround,
                    Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP);
                break;
            case MOTION_EUCLIDEAN:
                // 生成一个随机的欧几里得变换矩阵(含旋转和平移)
                angle = CV_PI / 30 + CV_PI * rng.uniform((double)-2.f, (double)2.f) / 180;
                warpGround = (Mat_<float>(2, 3) << cos(angle), -sin(angle), (rng.uniform(10.f, 20.f)),
                    sin(angle), cos(angle), (rng.uniform(10.f, 20.f)));
                // 应用欧几里得变换矩阵到目标图像,获取模板图像
                warpAffine(target_image, template_image, warpGround,
                    Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP);
                break;
            case MOTION_AFFINE:
                // 生成一个随机的仿射变换矩阵
                warpGround = (Mat_<float>(2, 3) << (1 - rng.uniform(-0.05f, 0.05f)),
                    (rng.uniform(-0.03f, 0.03f)), (rng.uniform(10.f, 20.f)),
                    (rng.uniform(-0.03f, 0.03f)), (1 - rng.uniform(-0.05f, 0.05f)),
                    (rng.uniform(10.f, 20.f)));
                // 应用仿射变换矩阵到目标图像,获取模板图像
                warpAffine(target_image, template_image, warpGround,
                    Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP);
                break;
            case MOTION_HOMOGRAPHY:
                // 生成一个随机的单应性变换矩阵
                warpGround = (Mat_<float>(3, 3) << (1 - rng.uniform(-0.05f, 0.05f)),
                    (rng.uniform(-0.03f, 0.03f)), (rng.uniform(10.f, 20.f)),
                    (rng.uniform(-0.03f, 0.03f)), (1 - rng.uniform(-0.05f, 0.05f)), (rng.uniform(10.f, 20.f)),
                    (rng.uniform(0.0001f, 0.0003f)), (rng.uniform(0.0001f, 0.0003f)), 1.f);
                // 应用单应性变换矩阵到目标图像,获取模板图像
                warpPerspective(target_image, template_image, warpGround,
                    Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP);
                break;
        }
    }
    
    // 根据变换类型创建适当大小的warp_matrix
    const int warp_mode = mode_temp;
    
    // 初始化或加载变换矩阵
    Mat warp_matrix;
    if (warpType == "homography")
        // 如果是单应性变换,则warp_matrix为3x3的单位矩阵
        warp_matrix = Mat::eye(3, 3, CV_32F);
    else
        // 如果是其他变换,则warp_matrix为2x3的单位矩阵
        warp_matrix = Mat::eye(2, 3, CV_32F);
    
    // 如果提供了变换矩阵文件名,尝试读取变换矩阵
    if (inWarpFile != ""){
        int readflag = readWarp(inWarpFile, warp_matrix, warp_mode);
        // 如果读取失败,打印错误信息,并退出程序
        if ((!readflag) || warp_matrix.empty())
        {
            cerr << "-> Check warp initialization file" << endl << flush;
            return -1;
        }
    }
    else {
        // 如果没有提供变换矩阵文件,发出警告:假定使用单位矩阵作为初始化可能不佳,
        // 尤其是当图像尺寸不相同时或者形变较大时
        printf("\n ->Performance Warning: Identity warp ideally assumes images of "
            "similar size. If the deformation is strong, the identity warp may not "
            "be a good initialization. \n");
    }
    
    // 检查迭代次数是否过多
    if (number_of_iterations > 200)
        cout << "-> Warning: too many iterations " << endl;
    
    // 如果不是单应性变换,确保变换矩阵仅有两行
    if (warp_mode != MOTION_HOMOGRAPHY)
        warp_matrix.rows = 2;
    
    //开始计时
    const double tic_init = (double) getTickCount();
    // 调用findTransformECC寻找最佳变换矩阵
    double cc = findTransformECC(template_image, target_image, warp_matrix, warp_mode,
        TermCriteria(TermCriteria::COUNT + TermCriteria::EPS, number_of_iterations, termination_eps));
    
    // 如果findTransformECC返回错误(即-1),则打印错误信息
    if (cc == -1)
    {
        cerr << "The execution was interrupted. The correlation value is going to be minimized." << endl;
        cerr << "Check the warp initialization and/or the size of images." << endl << flush;
    }
    
    // 停止计时
    const double toc_final = (double)getTickCount();
    const double total_time = (toc_final - tic_init) / (getTickFrequency());
    //如果设置了verbose,打印对齐时间
    if (verbose) {
        cout << "Alignment time (" << warpType << " transformation): "
            << total_time << " sec" << endl << flush;
        //  cout << "Final correlation: " << cc << endl << flush;
    }
    
    // 保存最终的变换矩阵
    saveWarp(finalWarp, warp_matrix, warp_mode);
    
    if (verbose) {
        //如果设置了verbose,打印保存变换矩阵的文件名
        cout << "\nThe final warp has been saved in the file: " << finalWarp << endl << flush;
    }
    
    // 保存最终的对齐图像
    Mat warped_image = Mat(template_image.rows, template_image.cols, CV_32FC1);
    if (warp_mode != MOTION_HOMOGRAPHY)
        warpAffine(target_image, warped_image, warp_matrix, warped_image.size(),
        INTER_LINEAR + WARP_INVERSE_MAP);
    else
        warpPerspective(target_image, warped_image, warp_matrix, warped_image.size(),
        INTER_LINEAR + WARP_INVERSE_MAP);
    
    imwrite(warpedImFile, warped_image); // 保存变形后的图像
    
    // 如果设置了verbose,显示结果图像
    if (verbose)
    {
        cout << "The warped image has been saved in the file: " << warpedImFile << endl << flush;
    
        //创建可视化窗口
        namedWindow("image", WINDOW_AUTOSIZE);
        namedWindow("template", WINDOW_AUTOSIZE);
        namedWindow("warped image", WINDOW_AUTOSIZE);
        namedWindow("error (black: no error)", WINDOW_AUTOSIZE);
    
        //移动窗口,用于可视化布局
        moveWindow("image", 20, 300);
        moveWindow("template", 300, 300);
        moveWindow("warped image", 600, 300);
        moveWindow("error (black: no error)", 900, 300);
    
        // 绘制变换后的区域边界
        Mat identity_matrix = Mat::eye(3, 3, CV_32F);
    
        draw_warped_roi(target_image, template_image.cols - 2, template_image.rows - 2, warp_matrix);
        draw_warped_roi(template_image, template_image.cols - 2, template_image.rows - 2, identity_matrix);
    
        Mat errorImage;
        subtract(template_image, warped_image, errorImage);
        double max_of_error;
        minMaxLoc(errorImage, NULL, &max_of_error);
    
        // 显示图像
        cout << "Press any key to exit the demo (you might need to click on the images before)." << endl << flush;
    
        imshow("image", target_image);
        waitKey(200);
        imshow("template", template_image);
        waitKey(200);
        imshow("warped image", warped_image);
        waitKey(200);
        imshow("error (black: no error)", abs(errorImage) * 255 / max_of_error);
        waitKey(0);
    }
    
    // 完成程序
    return 0;
}
// 使用resize函数调整inputImage的大小,存入target_image中
resize(
    inputImage,               // 源图像
    target_image,             // 目标图像,输出的大小调整后的图像将被存储在这里
    Size(216, 216),           // 目标图像的新大小,此处指定为宽216像素,高216像素
    0,                        // x方向上的缩放比例,在这里缩放比例由Size参数决定,因此设置为0
    0,                        // y方向上的缩放比例,同上设置为0
    INTER_LINEAR_EXACT        // 插值方式,此处使用精确线性插值算法
);

935067315c37bee2059d3c47f750f7e4.png

如果没有指定模板图像文件名,对输入图像应用随机变换以生成模板

e8b9049b400636096be2ef445f93e9f5.png

warpAffine(target_image, template_image, warpGround,
    Size(200, 200), INTER_LINEAR + WARP_INVERSE_MAP);

66122b00a5942cd826917b4de2fcd1ea.png

// 使用增强的相关系数(ECC)算法,寻找最佳的仿射变换矩阵
double cc = findTransformECC(
    template_image,        // 模板图像
    target_image,          // 需要对齐到模板图像的对象图像
    warp_matrix,           // 可以指定初始估计,函数将优化这个矩阵以获得最佳变换
    warp_mode,             // 规定变换模型的类型,如仿射变换
    TermCriteria(          // 优化时的迭代终止准则,可以是最大迭代次数,变换估计的精确度,或它们的组合
        TermCriteria::COUNT + TermCriteria::EPS, // 使用最大迭代次数和变换估计的精确度两个条件
        number_of_iterations,                     // 迭代的最大次数
        termination_eps                            // 迭代的终止精确度
    )
);

ac8369ae3d026334727b5ed3b867e4d1.png

// 对target_image应用透视变换,结果输出到warped_image中
warpPerspective(
    target_image,     // 源图像
    warped_image,     // 目标图像,存储变换后的图像
    warp_matrix,      // 透视变换矩阵,描述了变换的参数
    warped_image.size(), // 最终输出图像的大小
    INTER_LINEAR + WARP_INVERSE_MAP // 插值方式加上变换方向
);

5c70a60275c2ca4bfb1e207a9e1cd0fa.png

仿射变换和透视变换详细对比

d1d0b2bee67381c625190ec93e47b361.png

// 使用通用矩阵乘法函数gemm计算变换矩阵和透视矩阵的乘积
gemm(
    warp_mat,   // 第一个矩阵A,这里指的是仿射变换矩阵或相应的变换矩阵
    H,          // 第二个矩阵B,透视变换矩阵
    1,          // alpha系数,用于缩放第一个矩阵A
    Mat(),      // 第三个矩阵C,在这个函数调用中未使用,所以传递一个空矩阵
    0,          // beta系数,由于矩阵C未被使用,beta系数实际不起作用
    U,          // 输出矩阵D,存储A和B(M1和M2)乘积的结果
    0           // 标志位,在此为0,默认表示正常的矩阵乘法
);

3c406d104881a86b2922f5a9ef7b0b4c.png

The End

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

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

相关文章

MS7336MA高清 HD/全高清 FHD 可选择视频运放与视频同轴线控解码

产品简述 MS7336MA 是一颗集成单通道视频放大器与视频同轴线控解 码为一体的芯片&#xff0c;它内部集成 6dB 增益轨到轨输出驱动器以及 10 阶滤波器&#xff0c;允许同一个输入信号在 -3dB 带宽 35MHz 和 55MHz 之间进行选择控制。视频同轴线控解码内部集成一颗高…

在word中将公式复制后变成了图片怎么解决

是由于文件复制后格式不兼容造成的&#xff0c;需要转化一下。 然后确定就好了

Android T多屏多显——应用双屏间拖拽移动功能(更新中)

功能以及显示效果简介 需求&#xff1a;在双屏显示中&#xff0c;把启动的应用从其中一个屏幕中移动到另一个屏幕中。 操作&#xff1a;通过双指按压应用使其移动&#xff0c;如果移动的距离过小&#xff0c;我们就不移动到另一屏幕&#xff0c;否则移动到另一屏。 功能分析…

考华为数通方向的HCIE认证经验分享

大家好&#xff0c;我是来自安阳工学院20级网络工程的楚同学。在2023年12月6日&#xff0c;我顺利通过了华为数通方向的HCIE认证&#xff0c;在此&#xff0c;我想分享一些备考心得给正在努力备考的小伙伴们。 关于考证的契机 在大一上学期&#xff0c;我们的课程安排在实验…

halcon domain和region总结

1.domain是什么 在halcon中&#xff0c;ROI(Region Of Interest)被称为图像的域(domain)&#xff08;参考《solution_guide_i.pdf》&#xff09;。这个术语来自数学中的定义域&#xff0c;而图像就是函数&#xff0c;本函数负责将坐标映射到像素值&#xff0c;即f(x) gray这样…

记录一次Java中使用P12证书访问https,nginx返回403的问题

目录 1、先使用浏览器导入证书访问&#xff0c;测试证书和密钥是否正确2、编写初始java代码3、结果响应 403 Forbidden4、解决方案 1、先使用浏览器导入证书访问&#xff0c;测试证书和密钥是否正确 成功返回&#xff0c;说明p12证书和密钥是没问题的。 2、编写初始java代码 …

【U8+】用友固定资产卡片拆分提示:未设置对象变量或With block变量。

【问题描述】 用友U8软件中&#xff0c; 操作固定资产模块&#xff0c;针对资产进行卡片拆分的时候&#xff0c; 提示&#xff1a;未设置对象变量或With block变量。 确定后仍然能打开卡片拆分界面&#xff0c;但是界面显示异常看不到拆分明细信息&#xff0c;并且保存后拆分结…

Bug及异常:unity场景角色移动卡墙壁的问题

场景是一个小的杠铃形状封闭空间&#xff0c;美术没有给包围盒&#xff0c;我自己用blender做了一个&#xff08;属于兴趣爱好&#xff09;&#xff0c;如下&#xff1a; 导入场景中使用meshcollider做成空气墙&#xff0c;发现角色移动到角落继续行走会卡角落处&#x…

基于flutter3.x+window_manager+getx桌面端仿macOS系统

flutter3_macui桌面端仿macOS系统实战项目完结啦&#xff01; 原创研发flutter3.19dart3.3window_managergetx等技术构建桌面版macOS系统。支持自定义毛玻璃虚化背景、Dock菜单多级嵌套自由拖拽排序、可拖拽弹窗等功能。 支持macOS和windows11两种风格。 使用技术 编辑器&…

深入理解Apache ZooKeeper与Kafka的协同工作原理

目录 引言 一、ZooKeeper基础概念 &#xff08;一&#xff09;ZooKeeper简介 &#xff08;二&#xff09;ZooKeeper数据结构 &#xff08;三&#xff09;ZooKeeper特点 &#xff08;四&#xff09;应用场景 二、ZooKeeper工作模式 &#xff08;一&#xff09;工作机制 …

装饰器模式:动态扩展对象功能的设计艺术

在面向对象设计中&#xff0c;装饰器模式是一种灵活的结构型模式&#xff0c;用于在不修改对象的基础上&#xff0c;动态地给一个对象添加额外的职责。这种模式通过创建一个包含原始对象的包装对象来实现功能的扩展&#xff0c;是继承关系的一个替代方案。本文将详细介绍装饰器…

循序渐进丨MogDB 数据库带级联从库的集群切换后如何保持原有架构?

生产数据库运行过程中可能会涉及到升级或者打补丁&#xff0c;导致各节点的角色有计划的发生改变。如果集群内角色发生改变&#xff0c;是否还能保持原有架构继续对外提供服务呢&#xff1f;我们来做一下测试。 采用22模式模拟同城两机房部署4节点 MogDB 数据库集群&#xff0c…

FFmpeg: 简易ijkplayer播放器实现--06封装打开和关闭stream

文章目录 流程图stream openstream close 流程图 stream open 初始化SDL以允许⾳频输出&#xff1b;初始化帧Frame队列初始化包Packet队列初始化时钟Clock初始化音量创建解复用读取线程read_thread创建视频刷新线程video_refresh_thread int FFPlayer::stream_open(const cha…

Linux/Tenten

Tenten Enumeration Nmap 扫描发现对外开放了22和80端口&#xff0c;使用nmap详细扫描这两个端口 ┌──(kali㉿kali)-[~/vegetable/HTB/Tenten] └─$ nmap -sC -sV -p 22,80 -oA nmap 10.10.10.10 Starting Nmap 7.93 ( https://nmap.org ) at 2023-12-25 00:52 EST Nmap …

【Python】什么是pip,conda,pycharm,jupyter notebook?conda基本教程

pip--conda--pycharm--jupyter notebook &#x1f343;pip&#x1f343;conda&#x1f343;Pycharm&#x1f343;jupyter notebook&#x1f343;Conda基本教程☘️进入base环境☘️创建一个新的环境☘️激活环境☘️退出环境☘️查看电脑上都安装了哪些环境☘️删除已创建的项目…

Zookeeper实现分布式锁的分析和理解

Zookeeper实现分布式锁 创建临时顺序节点执行create -e -s /znode即可实现分布式锁。 zk中锁的分类 读锁&#xff08;读锁共享&#xff09;&#xff1a;大家都可以读。上锁前提&#xff1a;之前的锁没有写锁写锁&#xff08;写锁排他&#xff09;&#xff1a;只有得到写锁的…

Project Euler_Problem 172_Few Repeated Digits_动态规划

原题目&#xff1a; 题目大意&#xff1a;18位数里头&#xff0c;有多少个数&#xff0c;对于每个数字0-9&#xff0c;在这18位里面出现均不超过3次 111222333444555666 布星~~ 112233445566778899 可以~~ 解题思路&#xff1a; 动态规划 代码: ll F[19][3000000];void …

【python】在pycharm创建一个新的项目

双击打开pycharm,选择create new project 选择create,后进入项目 右键项目根目录,选择new一个新的python file 随意命名一下 输入p 然后后面就会出现智能补全提示,此时轻敲一下tab,代码就写好了,非常的方便 右键执行一下代码,下面两个直接运行和debug运行都是可以的 小结 …

使用Python批量将PDF转Word

简述 以下全部代码无法完美对图片、表格等非文字形式的内容转化。要较好的效果需要使用光学字符分析等方法进行转化 我懒&#xff0c;不想将代码模块拆分出来写注释 除代码1中有详细注释外&#xff0c;剩下的代码仅在关键部分进行注释 代码1&#xff1a;小规模文件的转换 代码…

C++设计模式|创建型 1.单例模式

1.什么是单例模式&#xff1f; 单例模式的的核⼼思想在创建类对象的时候保证⼀个类只有⼀个实例&#xff0c;并提供⼀个全局访问点来访问这个实例。 只有⼀个实例的意思是&#xff0c;在整个应⽤程序中&#xff0c;只存在该类的⼀个实例对象&#xff0c;⽽不是创建多个相同类…