[C++] 如何使用opencv对多个图像进行横向或者纵向拼接

什么是图像拼接?

图像拼接是指将多张图片按照一定的规则和算法进行组合,形成一张大图的过程。在实际应用中,常常需要将多张拍摄的图片拼接成一幅完整的场景或物体照片。

图像拼接是一个图像处理过程中常见的应用场景。我们可以通过opencv库非常容易实现多个图像的横向或者纵向的拼接。

使用的opencv函数或者类

Mat类

[C++] opencv - Mat类的介绍和使用场景-CSDN博客

copyTo函数

[C++] opencv - copyTo函数介绍和使用案例-CSDN博客

convertTo函数

[C++] opencv - convertTo函数介绍和使用场景-CSDN博客

imwrite函数

[C++] opencv - imwrite函数介绍和使用场景_c++ opencv imwrite-CSDN博客

imread函数

[C++] opencv - imwrite函数介绍和使用场景_c++ opencv imwrite-CSDN博客

源代码

cpp 复制代码
/**
 * 处理结果,可用于表示某个方法的处理状态。
*/
struct ProcResult
{
    int StatusCode; // 状态码
    std::string StatusDetail; // 状态详细信息,可以用来提供与对应状态的详细信息
};


struct ImageCombinerParam
{
    std::vector<std::string> subImagePaths; // 要进行拼接的图像路径的列表
    std::vector<cv::Mat> subImageMats;  // 要进行拼接的图像的列表
    bool byPath = true;  // 按照路径subImagePaths来进行拼接,如果为false,则自己来设置subImageMats
    std::string saveImagePath;  // 拼接后图像保存的路径
    bool enableImgBoarder = false;  // 是否给要拼接的图像,进行边框绘制,方便观察原始图像在拼接完之后图像中的位置
    int direction = 1; // 1 - 横向, 2- 纵向
};

std::vector<std::string> Utils::findFilesByExt(const std::string dir, const std::string ext){
    std::vector<std::string> foundFiles;
    for (const auto& entry : fs::directory_iterator(dir)) {
        if (entry.is_regular_file() && entry.path().extension() == ext) {
            foundFiles.push_back(entry.path().string());
        }
    }
    return foundFiles;
};


cv::Vec3b CVHelper::getRandColor(){
    // 获取当前时间点
    auto now = std::chrono::system_clock::now();
    
    // 将时间点转换为time_t类型
    std::time_t currentTime = std::chrono::system_clock::to_time_t(now);

    // 初始化随机数种子
    // srand(static_cast<unsigned int>(currentTime)); 
    // srand(static_cast<unsigned int>(time(0)));

    // 生成随机颜色
    cv::Vec3b randomColor(rand() % 256, rand() % 256, rand() % 256);
    // cv::Scalar randomColor(rand() % 256, rand() % 256, rand() % 256);

    return randomColor;
};

ProcResult ImageCombiner::combineSubImages(ImageCombinerParam param){
    ProcResult result;
    if((param.byPath && param.subImagePaths.size() <= 0) || (!param.byPath && param.subImageMats.size() <=0)){
        std::cerr << "there is no images." << std::endl;
        result.StatusCode = 1;
        result.StatusDetail = "there is no images.";
        return result;
    }
    std::vector<cv::Mat> imageMats;
    if(param.byPath){
        for(size_t i = 0; i < param.subImagePaths.size(); i++){
            cv::Mat img = cv::imread(param.subImagePaths[i]);
            if(!img.empty()){
                if(imageMats.size() > 0 && img.type()!= imageMats[0].type()){
                    img.convertTo(img, imageMats[0].type());
                }
                imageMats.push_back(img);
            }else{
                std::string errMsg = "fail to read image. img_path:" + param.subImagePaths[i];
                SPDLOG_ERROR(errMsg);
            }
        }
    }else{
        imageMats = param.subImageMats;
    }

    if(param.byPath && imageMats.size() != param.subImagePaths.size()){
        result.StatusCode = 2;
        std::string errorMsg = "fail to read all images.";
        result.StatusDetail = errorMsg;
        return result;
    }else{
        if(fs::exists(param.saveImagePath)){
            fs::remove(param.saveImagePath);
        }
        fs::path savePath(param.saveImagePath);
        if(!fs::exists(savePath.parent_path())){
            fs::create_directories(savePath.parent_path());
        }
        if (param.direction == 1){ // 横向合并
            int combinedWidth = 0;
            int combinedHeight = 0;
            for(size_t i = 0; i < imageMats.size(); i++){
                cv::Mat img = imageMats[i];
                combinedWidth += img.cols;
                if(img.rows > combinedHeight){
                    combinedHeight = img.rows;
                }
                if(param.enableImgBoarder){
                    cv::Vec3b randomColor = CVHelper::getRandColor();
                    cv::rectangle(img, cv::Point(0, 0), cv::Point(img.cols, img.rows),  randomColor, 5, cv::LINE_8);
                }
            }
    
            cv::Mat combinedImg = cv::Mat(combinedHeight, combinedWidth , imageMats[0].type(), cv::Scalar(0, 0, 0));
            int startX = 0;
            int startY = 0;
            for(size_t i = 0; i < imageMats.size(); i++){
                cv::Mat img = imageMats[i];
                imageMats[i].copyTo(combinedImg(cv::Rect(startX, startY, img.cols, img.rows)));
                startX += img.cols;
            }
            cv::imwrite(param.saveImagePath, combinedImg);
            result.StatusCode = common::STATUS_CODE_OK;
            result.StatusDetail = "combine success by cols";
            return result;
        }else{ // 纵向合并
            int combinedWidth = 0;
            int combinedHeight = 0;
            for(size_t i = 0; i < imageMats.size(); i++){
                cv::Mat img = imageMats[i];
                combinedHeight += img.rows;
                if(img.cols > combinedWidth){
                    combinedWidth = img.cols;
                }
                
                if(param.enableImgBoarder){
                    cv::Vec3b randomColor = CVHelper::getRandColor();
                    cv::rectangle(img, cv::Point(0, 0), cv::Point(img.cols, img.rows),  randomColor, 5, cv::LINE_8);
                }
            }
    
            cv::Mat combinedImg = cv::Mat(combinedHeight, combinedWidth , imageMats[0].type(), cv::Scalar(0, 0, 0));
            int startX = 0;
            int startY = 0;
            for(size_t i = 0; i < imageMats.size(); i++){
                cv::Mat img = imageMats[i];
                imageMats[i].copyTo(combinedImg(cv::Rect(startX, startY, img.cols, img.rows)));
                startY += img.rows;
            }
            
            cv::imwrite(param.saveImagePath, combinedImg);
            result.StatusCode = common::STATUS_CODE_OK;
            result.StatusDetail = "combine success by rows";
            return result;
        }
    }
};

测试代码

测试代码中使用了命令行参数,如何配置cxxopts,可以阅读 [C++] 第三方库命令行解析库argparse和cxxopts介绍和使用-CSDN博客

cpp 复制代码
#include <filesystem>
#include <iostream>
#include <opencv2/opencv.hpp>
#include <algorithm.hpp>
#include <cxxopts.hpp>

using namespace cv;
using namespace std;
namespace fs = std::filesystem;
namespace fdect = flaw_detect;

int main(int argc,char **argv){
    try{
        cxxopts::Options options("combine_image", "Cut multiple images to a big by a image list");
        options.add_options()
            ("imgdir", "要合并的图像所在目录", cxxopts::value<std::string>()->default_value("D:/LocalTest/ResizeImages/img_for_combine"))
            ("img_bd", "是否给要合并的图像添加边框", cxxopts::value<bool>()->default_value("true"))
            ("reverse", "按图像名称进行倒序", cxxopts::value<bool>()->default_value("true"))
            ("savepath", "合并之后的图像保存的路径", cxxopts::value<std::string>()->default_value("D:/LocalTest/ResizeImages/resize_combined.bmp"))
            ("help", "使用帮助");
        
        auto cliArgs = options.parse(argc, argv);
        if (cliArgs.count("help"))
        {
            std::cout << options.help() << std::endl;
            exit(0);
        }
        std::string imgDir = cliArgs["imgdir"].as<std::string>();
        bool enableImgBorder =  cliArgs["img_bd"].as<bool>();
        bool reverse =  cliArgs["reverse"].as<bool>();
        std::string savePath = cliArgs["savepath"].as<std::string>();
        
        std::cout << "imgDir:" << imgDir << ", enableImgBorder:" << enableImgBorder << ", savePath:" << savePath 
        << std::endl;

        std::vector<std::string> imageFiles = fdect::common::Utils::findFilesByExt(imgDir, ".bmp");
        if (reverse){
            sort(imageFiles.rbegin(), imageFiles.rend());
        }else{
            sort(imageFiles.begin(), imageFiles.end());
        }
        fdect::algorithm::ImageCombinerParam param;
        param.subImagePaths = imageFiles;
        param.enableImgBoarder = enableImgBorder;
        param.saveImagePath = savePath;
        param.direction = 1;
        fdect::algorithm::ImageCombiner combiner;
        fdect::common::ProcResult result = combiner.combineSubImages(param);
        std::cout << "处理结果: (" << result.StatusCode << ", " << result.StatusDetail << ")" << std::endl;

        return 0;
    }catch( Exception e){
        std::cerr << "发生异常. 异常信息:" << e.what() << std::endl;
        return -1;
    }
}
相关推荐
蜀黍@猿32 分钟前
【C++ 基础】从C到C++有哪些变化
c++
Am心若依旧40933 分钟前
[c++11(二)]Lambda表达式和Function包装器及bind函数
开发语言·c++
zh路西法43 分钟前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(一):从电梯出发的状态模式State Pattern
c++·决策树·状态模式
轩辰~1 小时前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
lxyzcm1 小时前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
蜀黍@猿2 小时前
C/C++基础错题归纳
c++
雨中rain2 小时前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++
游客5202 小时前
opencv中的常用的100个API
图像处理·人工智能·python·opencv·计算机视觉
ALISHENGYA3 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(实战项目二)
数据结构·c++·算法
arong_xu3 小时前
现代C++锁介绍
c++·多线程·mutex