基于opencv的全景图像拼接

目录

全景图拼接

算法原理

柱面坐标转换

特征匹配

全景图拼接

stitcher.h

stitcher.cpp

main.cpp

参考文献


全景图拼接

全景图拼接是利用同一场景的多张图像通过重叠部分寻找匹配关系,从而生成整个场景图像的技术。 全景图的拼接方法有很多,如按场景和运动的种类可以分为单视点全景拼接和多视点全景拼接。

对于平面场景和只通过相机旋转拍摄的场景来说,可以使用求每两幅图像之间的一个Homography变换来映射到一张图像的方法,还可以使用恢复相机的旋转的方式得到最终的全景图。当相机固定只有水平方向旋转时,也可以使用柱面或球面坐标映射的方式求得全景图。

算法原理

柱面坐标转换

对于每一幅图像来说,我们都可以把它们投影到一个柱面上,得到柱面上的图像 。柱面图像的坐标变换为:

x′=rtan−1(xf)x′=rtan−1(fx​)

y′=ryx2+f2y′=x2+f2​ry​

其中(x′,y′)(x′,y′)为柱面上的坐标,(x,y)(x,y)为平面图像坐标,其坐标原点都已移至图像中心,rr 为柱面半径,ff为焦距。

然而为了得到柱面投影图像,我们往往需要将柱面图像上的点逆变换到平面图像上的对应像素点,进行插值,得到完整的柱面图像,逆变换的变换公式为:

x=ftan(x′r)x=ftan(rx′​)

y=y′rx2+f2y=ry′​x2+f2​

特征匹配

对每两幅相邻的柱面图像进行特征提取和匹配(特征可以选用SIFT、ORB等,可以使用OpenCV的函数实现),寻找两幅相邻图像的对应关系。

全景图拼接

使用上一步得到的匹配关系,求出每两幅柱面图像的一个平移变换,利用平移变换将所有图像拼接到一起。得到一幅全景图。

stitcher.h

复制代码
#pragma once
#include <opencv2/opencv.hpp>
#include <vector>

class PanoramaStitcher {
public:
    enum FeatureType { ORB_FEATURES=0, SIFT_FEATURES=1 };
    
    explicit PanoramaStitcher(FeatureType type=SIFT_FEATURES, 
                            float match_ratio=0.75f,
                            int min_matches=10);
    
    bool stitch(const std::vector<cv::Mat>& images, cv::Mat& result);
    
private:
    struct ImageFeatures {
        cv::Mat image;
        std::vector<cv::KeyPoint> keypoints;
        cv::Mat descriptors;
    };
    
    void extractFeatures(const cv::Mat& image, ImageFeatures& features);
    bool matchFeatures(const ImageFeatures& f1, 
                      const ImageFeatures& f2,
                      std::vector<cv::DMatch>& good_matches);
    cv::Mat findHomography(const ImageFeatures& f1,
                          const ImageFeatures& f2,
                          const std::vector<cv::DMatch>& matches);
    void blendImages(cv::Mat& panorama, const cv::Mat& new_image, 
                    const cv::Mat& H);
    
    FeatureType feature_type_;
    float match_ratio_;
    int min_matches_;
    cv::Ptr<cv::Feature2D> detector_;
    cv::Ptr<cv::DescriptorMatcher> matcher_;
};

stitcher.cpp

复制代码
#include "stitcher.h"
#include <opencv2/calib3d.hpp>
#include <opencv2/imgproc.hpp>

PanoramaStitcher::PanoramaStitcher(FeatureType type, float ratio, int matches)
    : feature_type_(type), match_ratio_(ratio), min_matches_(matches) {
    if(type == ORB_FEATURES) {
        detector_ = cv::ORB::create(2000);
        matcher_ = cv::DescriptorMatcher::create("BruteForce-Hamming");
    } else {
        detector_ = cv::SIFT::create();
        matcher_ = cv::DescriptorMatcher::create("FlannBased");
    }
}

bool PanoramaStitcher::stitch(const std::vector<cv::Mat>& images, cv::Mat& result) {
    if(images.empty()) return false;
    
    std::vector<ImageFeatures> features(images.size());
    for(size_t i = 0; i < images.size(); ++i) {
        extractFeatures(images[i], features[i]);
    }
    
    result = images[0].clone();
    cv::Mat accumulated_H = cv::Mat::eye(3, 3, CV_64F);
    
    for(size_t i = 1; i < features.size(); ++i) {
        std::vector<cv::DMatch> matches;
        if(!matchFeatures(features[i-1], features[i], matches)) {
            continue;
        }
        
        cv::Mat H = findHomography(features[i-1], features[i], matches);
        if(H.empty()) continue;
        
        accumulated_H = accumulated_H * H;
        blendImages(result, images[i], accumulated_H);
    }
    
    return !result.empty();
}

void PanoramaStitcher::extractFeatures(const cv::Mat& image, ImageFeatures& features) {
    features.image = image;
    detector_->detectAndCompute(image, cv::noArray(), 
                              features.keypoints, features.descriptors);
}

bool PanoramaStitcher::matchFeatures(const ImageFeatures& f1, 
                                   const ImageFeatures& f2,
                                   std::vector<cv::DMatch>& good_matches) {
    if(f1.descriptors.empty() || f2.descriptors.empty()) return false;
    
    std::vector<std::vector<cv::DMatch>> knn_matches;
    matcher_->knnMatch(f1.descriptors, f2.descriptors, knn_matches, 2);
    
    for(auto &pair : knn_matches) {
        if(pair[0].distance < match_ratio_ * pair[1].distance) {
            good_matches.push_back(pair[0]);
        }
    }
    return good_matches.size() > min_matches_;
}

cv::Mat PanoramaStitcher::findHomography(const ImageFeatures& f1,
                                        const ImageFeatures& f2,
                                        const std::vector<cv::DMatch>& matches) {
    std::vector<cv::Point2f> pts1, pts2;
    for(const auto &m : matches) {
        pts1.push_back(f1.keypoints[m.queryIdx].pt);
        pts2.push_back(f2.keypoints[m.trainIdx].pt);
    }
    return cv::findHomography(pts2, pts1, cv::RANSAC, 3.0);
}

void PanoramaStitcher::blendImages(cv::Mat& panorama, const cv::Mat& new_image, 
                                 const cv::Mat& H) {
    cv::Mat warped;
    cv::warpPerspective(new_image, warped, H, 
                       cv::Size(panorama.cols + new_image.cols, 
                               panorama.rows));
    
    cv::Mat mask = cv::Mat::zeros(warped.size(), CV_8U);
    cv::rectangle(mask, cv::Point(0,0), 
                 cv::Point(panorama.cols, panorama.rows), 
                 255, cv::FILLED);
    
    cv::Mat blended;
    cv::addWeighted(panorama, 0.5, warped, 0.5, 0, blended);
    blended.copyTo(panorama(cv::Rect(0,0,blended.cols, blended.rows)));
}

main.cpp

复制代码
#include "stitcher.h"
#include <iostream>

int main() {
    std::vector<cv::Mat> images;
    for(int i = 1; i <= 10; ++i) {
        cv::Mat img = cv::imread("image" + std::to_string(i) + ".jpg");
        if(!img.empty()) images.push_back(img);
    }
    
    PanoramaStitcher stitcher(PanoramaStitcher::SIFT_FEATURES);
    cv::Mat panorama;
    if(stitcher.stitch(images, panorama)) {
        cv::imwrite("panorama_result.jpg", panorama);
        cv::imshow("Panorama Result", panorama);
        cv::waitKey(0);
    } else {
        std::cerr << "Stitching failed!" << std::endl;
    }
    return 0;
}

参考文献

Microsoft Research -- Emerging Technology, Computer, and Software Research
http://faculty.cs.tamu.edu/jchai/CPSC641/szeliskiShum97.pdf

相关推荐
深圳市快瞳科技有限公司31 分钟前
小场景大市场:猫狗识别算法在宠物智能设备中的应用
算法·计算机视觉·宠物
SEO_juper3 小时前
大型语言模型SEO(LLM SEO)完全手册:驾驭搜索新范式
人工智能·语言模型·自然语言处理·chatgpt·llm·seo·数字营销
攻城狮7号3 小时前
腾讯混元翻译模型Hunyuan-MT-7B开源,先前拿了30个冠军
人工智能·hunyuan-mt-7b·腾讯混元翻译模型·30个冠军
zezexihaha3 小时前
从“帮写文案”到“管生活”:个人AI工具的边界在哪?
人工智能
算家云3 小时前
nano banana官方最强Prompt模板来了!六大场景模板详解
人工智能·谷歌·ai大模型·算家云·ai生图·租算力,到算家云·nano banana 提示词
暴躁的大熊3 小时前
AI助力决策:告别生活与工作中的纠结,明析抉择引领明智选择
人工智能
Gyoku Mint3 小时前
提示词工程(Prompt Engineering)的崛起——为什么“会写Prompt”成了新技能?
人工智能·pytorch·深度学习·神经网络·语言模型·自然语言处理·nlp
AndrewHZ3 小时前
【图像处理基石】图像在频域处理和增强时,如何避免频谱混叠?
图像处理·计算机视觉·傅里叶分析·图像增强·频域处理·摩尔纹·频谱混叠
梁小憨憨3 小时前
zotero扩容
人工智能·笔记
大数据张老师4 小时前
AI架构师的思维方式与架构设计原则
人工智能·架构师·ai架构·后端架构