从零开始学C++之对象的使用(三):static 与单例模式、auto_ptr与单例模式、const 用法小结、mutable修饰符

本文介绍了C++中的单例模式,讲解了如何利用static解决对象生命周期问题,以及单例模式在多线程环境下的线程安全性。此外,还探讨了const成员函数、const对象的特性,并对const用法进行了总结,包括mutable修饰符的作用。

一、static 与单例模式

单例模式也就是简单的一种设计模式,它需要:

保证一个类只有一个实例,并提供一个全局访问点

禁止拷贝

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
using  namespace std;

class Singleton
{
public:
     static Singleton *GetInstance()
    {
         if (instance_ ==  NULL)
        {
            instance_ =  new Singleton;
        }
         return instance_;
    }

    ~Singleton()
    {
        cout <<  "~Singleton ..." << endl;
    }
private:
    Singleton( const Singleton &other);
    Singleton & operator=( const Singleton &other);
    Singleton()
    {
        cout <<  "Singleton ..." << endl;
    }
     static Singleton *instance_;
};

Singleton *Singleton::instance_;

int main( void)
{
     //Singleton s1;
     //Singleton s2;

    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();

     //Singleton s3(*s1);        // 调用拷贝构造函数

     return  0;
}


上述程序虽然调用了两个GetInstance函数,但只调用一次构造函数,即创建一个对象。将赋值运算符和拷贝构造函数声明为私有,禁止拷贝。但程序存在一个问题就是对象生存期到时不会被析构。


为了解决对象不会被析构的问题,可以使用一个静态的嵌套类对象来解决:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
using  namespace std;

class Singleton
{
public:
     static Singleton *GetInstance()
    {
         if (instance_ ==  NULL)
        {
            instance_ =  new Singleton;
        }
         return instance_;
    }

    ~Singleton()
    {
        cout <<  "~Singleton ..." << endl;
    }

     //static void Free()
     //{
     //  if (insta n ce_ != NULL)
     //  {
     //      delete insta n ce_;
     //  }
     //}

     class Garbo
    {
     public:
        ~Garbo()
        {
             if (Singleton::instance_ !=  NULL)
            {
                 delete instance_;
            }
        }
    };
private:
    Singleton( const Singleton &other);
    Singleton & operator=( const Singleton &other);
    Singleton()
    {
        cout <<  "Singleton ..." << endl;
    }
     static Singleton *instance_;

     static Garbo garbo_;     // 利用对象的确定性析构
};

Singleton::Garbo Singleton::garbo_;
Singleton *Singleton::instance_;

int main( void)
{
     //Singleton s1;
     //Singleton s2;

    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();

     //Singleton s3(*s1);        // 调用拷贝构造函数


     return  0;
}

利用静态嵌套对象的确定性析构会调用Garbo类的析构函数,在析构函数内delete 单例类的指针。


上面办法比较繁琐,也可以返回局部静态对象的引用来解决:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
using  namespace std;

class Singleton
{
public:
     static Singleton &GetInstance()
    {
         static Singleton instance;       // 局部静态对象
         return instance;
    }

    ~Singleton()
    {
        cout <<  "~Singleton ..." << endl;
    }

private:
    Singleton( const Singleton &other);
    Singleton & operator=( const Singleton &other);
    Singleton()
    {
        cout <<  "Singleton ..." << endl;
    }
};

int main( void)
{
    Singleton &s1 = Singleton::GetInstance();
    Singleton &s2 = Singleton::GetInstance();



     return  0;
}

局部静态对象只会初始化一次,所以调用多次GetInstance函数得到的是同一个对象。由于函数内使用了静态对象,故不是线程安全的。实际上也可以使用auto_ptr 智能指针 来解决,程序如下,更详细的对auto_ptr 的讨论参见这里


 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include<memory>
using  namespace std;

class Singleton
{
public:
     static Singleton *GetInstance()
    {
         if (instance_.get() ==  NULL)
        {
            instance_ = auto_ptr<Singleton>( new Singleton);
        }
         return instance_.get();
    }

    ~Singleton()
    {
        cout <<  "~Singleton ..." << endl;
    }
private:
    Singleton( const Singleton &other);
    Singleton & operator=( const Singleton &other);
    Singleton()
    {
        cout <<  "Singleton ..." << endl;
    }
     static auto_ptr<Singleton> instance_;
};

auto_ptr<Singleton> Singleton::instance_;

int main( void)
{
     //Singleton s1;
     //Singleton s2;

    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();

     //Singleton s3(*s1);        // 调用拷贝构造函数

     return  0;
}

实际上,上述所有的单例模式例子都不是线程安全的,设想如果两个线程同时运行到语句if (instance == null),而此时该实例的确没有创建,那么两个线程都会创建一个实例。如果不希望加锁实现线程安全,可以使用饿汉模式(即在main函数之前先生成一个实例):



或者通过加锁方式实现,请参考这里


二、const成员函数、const 对象、mutable修饰符

(一)、const 成员函数

const成员函数不会修改对象的状态

const成员函数只能访问数据成员的值,而不能修改它

(二)、const 对象

如果把一个对象指定为const,就是告诉编译器不要修改它
const对象的定义:

const 类名 对象名(参数表);

const对象不能调用非const成员函数


用mutable修饰的数据成员即使在const对象或在const成员函数中都可以被修改。


 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
using  namespace std;

class Test
{
public:
    Test( int x) : x_(x), outputTimes_( 0)
    {

    }
     int GetX()  const
    {
        cout <<  "const GetX ..." << endl;
         //x_ = 100;
         return x_;
    }

     int GetX()
    {
        cout <<  "GetX ..." << endl;
         return x_;
    }

     void Output()  const
    {
        cout <<  "x=" << x_ << endl;
        outputTimes_++;
    }

     int GetOutputTimes()  const
    {
         return outputTimes_;
    }
private:
     int x_;

     mutable  int outputTimes_;
};

int main( void)
{
     const Test t( 10);
    t.GetX();

    Test t2( 20);
    t2.GetX();

    t.Output();
    t.Output();
    cout << t.GetOutputTimes() << endl;
     return  0;
}



三、const 用法总结

可以对const 的用法做个小总结:



参考:

C++ primer 第四版
Effective C++ 3rd
C++编程规范


// 引入必要的头文件 #include <opencv2/opencv.hpp> // OpenCV 核心库,用于图像处理 #include <opencv2/features2d.hpp> // OpenCV 特征检测库 #include<opencv2/xfeatures2d/xfeatures2d.hpp> // OpenCV 额外特征检测库(SURF) #include <opencv2/stitching.hpp> // OpenCV 图像拼接库 #include <iostream> // 输入输出流,用于控制台输出 #include <vector> // 向量容器,用于存储多个图像 #include <string> // 字符串处理 #include <algorithm> // 算法库,用于排序等操作 #include <regex> // 正则表达式库,用于文件名解析 #include <sstream> // 字符串流,用于字符串处理 #include <iomanip> // 输入输出格式化 #include <chrono> // 时间库,用于性能测量 #include <thread> // 线程库,用于实时显示 #include <atomic> // 原子操作库 #include <mutex> // 互斥锁库 #include <opencv2/stitching/detail/util.hpp> // 可能包含 resultRoi 的定义 // 配置参数 - 这些是程序的设置,可以根据需要修改 const std::string IMAGE_DIR = "C:/Users/zhangqt203387/images"; // 图像文件夹路径 const std::string OUTPUT_DIR = "output"; // 输出文件夹名称 const int RESIZE_THRESHOLD = 1024; // 图像最大尺寸限制 const std::string FEATURE_DETECTOR = "SIFT"; // 使用的特征检测算法 const double RANSAC_THRESHOLD = 5.0; // RANSAC算法的容错阈值 const double MATCH_RATIO = 0.6; // 特征匹配的质量阈值 const int MIN_MATCHES = 10; // 最小匹配点数量 const bool SHOW_PROGRESS = true; // 是否显示拼接过程 const double DISPLAY_SCALE = 0.3; // 显示图像的缩放比例 // 图像预处理参数 const bool ENABLE_PREPROCESSING = true; // 是否启用图像预处理 const int MEDIAN_BLUR_SIZE = 3; // 中值滤波核大小 const double INDANE_CLIP_LIMIT = 2.0; // INDANE算法对比度限制 const int INDANE_TILE_SIZE = 8; // INDANE算法瓦片大小 // 特征提取参数 const int MAX_FEATURES = 5000; // 最大特征点数量 const double CONTRAST_THRESHOLD = 0.04; // 对比度阈值 // 图像融合参数 const bool USE_MULTI_BAND_BLENDING = true; // 是否使用多频带融合 const int BLEND_WIDTH = 5; // 融合宽度 // 定义图像拼接器类 class AdvancedImageStitcher { private: // 类的私有成员变量 std::string detectorType; // 特征检测器类型 cv::Ptr<cv::Feature2D> detector; // OpenCV特征检测器对象 cv::Ptr<cv::DescriptorMatcher> matcher; // 特征匹配器对象 std::vector<cv::Mat> images; // 存储加载的图像 std::vector<std::string> imagePaths; // 存储图像文件路径 cv::Mat panorama; // 存储拼接后的全景图 std::atomic<bool> stopDisplay; // 停止显示标志 std::mutex panoramaMutex; // 全景图互斥锁 // 设置特征检测器和匹配器 void setupDetectors() { // 根据选择的类型创建特征检测器 if (detectorType == "SIFT") { detector = cv::SIFT::create(MAX_FEATURES, 3, CONTRAST_THRESHOLD); } else if (detectorType == "SURF") { detector = cv::xfeatures2d::SURF::create(400); // Hessian阈值 } else if (detectorType == "AKAZE") { detector = cv::AKAZE::create(); } else { // 默认使用ORB detector = cv::ORB::create(MAX_FEATURES); } // 根据特征检测器类型创建匹配器 if (detectorType == "SIFT" || detectorType == "SURF") { matcher = cv::FlannBasedMatcher::create(); // SIFT和SURF使用FLANN匹配器 } else { matcher = cv::BFMatcher::create(cv::NORM_HAMMING, false); // 其他使用暴力匹配器 } } // INDANE图像增强算法 cv::Mat applyINDANE(const cv::Mat& input) { cv::Mat lab; cv::cvtColor(input, lab, cv::COLOR_BGR2Lab); std::vector<cv::Mat> labChannels; cv::split(lab, labChannels); // 对L通道应用CLAHE cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE(); clahe->setClipLimit(INDANE_CLIP_LIMIT); clahe->setTilesGridSize(cv::Size(INDANE_TILE_SIZE, INDANE_TILE_SIZE)); clahe->apply(labChannels[0], labChannels[0]); cv::Mat enhancedLab; cv::merge(labChannels, enhancedLab); cv::Mat result; cv::cvtColor(enhancedLab, result, cv::COLOR_Lab2BGR); return result; } // 逆滤波去模糊 cv::Mat applyInverseFilter(const cv::Mat& input) { // 创建模糊核(假设轻微高斯模糊) cv::Mat kernel = cv::getGaussianKernel(15, 1.5); kernel = kernel * kernel.t(); // 将图像转换为浮点型 cv::Mat floatInput; input.convertTo(floatInput, CV_32F); // 应用维纳滤波(逆滤波的稳定版本) cv::Mat result; cv::filter2D(floatInput, result, -1, kernel); // 将结果转换回8位 result.convertTo(result, CV_8U); return result; } // 图像预处理函数 cv::Mat preprocessImage(const cv::Mat& input) { if (!ENABLE_PREPROCESSING) { return input.clone(); } cv::Mat result = input.clone(); // 步骤1: INDANE图像增强 result = applyINDANE(result); // 步骤2: 逆滤波去模糊 result = applyInverseFilter(result); // 步骤3: 中值滤波去噪 cv::medianBlur(result, result, MEDIAN_BLUR_SIZE); return result; } // 自然排序比较函数 - 使文件名中的数字按数值大小排序 static bool naturalCompare(const std::string& a, const std::string& b) { // 将字符串转换为小写,实现不区分大小写的比较 std::string aLower = a; std::string bLower = b; std::transform(aLower.begin(), aLower.end(), aLower.begin(), ::tolower); std::transform(bLower.begin(), bLower.end(), bLower.begin(), ::tolower); // 使用正则表达式分割字符串中的数字和非数字部分 std::regex re("(\\d+)|(\\D+)"); std::sregex_iterator it1(aLower.begin(), aLower.end(), re); std::sregex_iterator it2(bLower.begin(), bLower.end(), re); std::sregex_iterator end; // 逐个比较分割后的部分 while (it1 != end && it2 != end) { std::smatch match1 = *it1; std::smatch match2 = *it2; // 如果两部分都是数字,则按数值比较 if (std::regex_match(match1.str(), std::regex("\\d+")) && std::regex_match(match2.str(), std::regex("\\d+"))) { int num1 = std::stoi(match1.str()); // 将字符串转换为整数 int num2 = std::stoi(match2.str()); if (num1 != num2) return num1 < num2; // 数值比较 } // 否则按字符串比较 else if (match1.str() != match2.str()) { return match1.str() < match2.str(); // 字符串比较 } // 移动到下一个部分 ++it1; ++it2; } // 如果前面的部分都相同,则按长度比较 return aLower.length() < bLower.length(); } // 特征匹配函数 std::vector<cv::DMatch> matchFeatures(const cv::Mat& des1, const cv::Mat& des2) { // 使用KNN匹配(K近邻匹配)找到最佳匹配 std::vector<std::vector<cv::DMatch>> knnMatches; matcher->knnMatch(des1, des2, knnMatches, 2); // k=2表示每个点找两个最佳匹配 // 筛选优质匹配点 std::vector<cv::DMatch> goodMatches; for (const auto& matchPair : knnMatches) { if (matchPair.size() == 2) { // 应用比率测试:最佳匹配的距离应小于次佳匹配距离的一定比例 if (matchPair[0].distance < MATCH_RATIO * matchPair[1].distance) { goodMatches.push_back(matchPair[0]); // 保留优质匹配 } } } // 输出匹配点数量信息 std::cout << "良好匹配点: " << goodMatches.size() << std::endl; return goodMatches; } // 计算单应性矩阵函数 cv::Mat findHomography(const std::vector<cv::KeyPoint>& kp1, const std::vector<cv::KeyPoint>& kp2, const std::vector<cv::DMatch>& matches, int& inliers) { // 检查是否有足够的匹配点 if (matches.size() < MIN_MATCHES) { return cv::Mat(); // 返回空矩阵 } // 提取匹配点的坐标 std::vector<cv::Point2f> srcPoints, dstPoints; for (const auto& match : matches) { srcPoints.push_back(kp2[match.trainIdx].pt); // 第二幅图像中的点 dstPoints.push_back(kp1[match.queryIdx].pt); // 第一幅图像中的点 } // 使用RANSAC算法计算单应性矩阵 cv::Mat mask; // 掩码,标记哪些点是内点(正确匹配) cv::Mat H = cv::findHomography(srcPoints, dstPoints, cv::RANSAC, RANSAC_THRESHOLD, mask); // 计算内点数量(正确匹配的点) inliers = 0; if (!mask.empty()) { inliers = cv::countNonZero(mask); // 统计非零元素数量 } // 输出内点信息 std::cout << "内点数量: " << inliers << "/" << matches.size() << std::endl; return H; // 返回单应性矩阵 } // 多频带融合 cv::Mat multiBandBlend(const std::vector<cv::Mat>& images, const std::vector<cv::Mat>& transforms) { if (images.empty()) return cv::Mat(); // 创建多频带混合器 cv::Ptr<cv::detail::MultiBandBlender> blender = cv::makePtr< cv::detail::MultiBandBlender >(); blender->setNumBands(static_cast<int>(ceil(log2(BLEND_WIDTH)) - 1)); // 准备混合器 cv::Rect dst_roi = cv::detail::resultRoi(transforms, images); blender->prepare(dst_roi); // 混合所有图像 for (size_t i = 0; i < images.size(); i++) { cv::Mat warped; cv::warpPerspective(images[i], warped, transforms[i], dst_roi.size()); // 创建掩码 cv::Mat mask = cv::Mat::ones(images[i].size(), CV_8U) * 255; cv::Mat warped_mask; cv::warpPerspective(mask, warped_mask, transforms[i], dst_roi.size()); // 混合图像 blender->feed(warped, warped_mask, cv::Point(0, 0)); } // 完成混合 cv::Mat result, result_mask; blender->blend(result, result_mask); return result; } // 加权平均融合 cv::Mat weightedBlend(const cv::Mat& img1, const cv::Mat& img2, const cv::Mat& H) { // 计算变换后图像的大小 std::vector<cv::Point2f> corners = { cv::Point2f(0, 0), cv::Point2f(img2.cols, 0), cv::Point2f(img2.cols, img2.rows), cv::Point2f(0, img2.rows) }; std::vector<cv::Point2f> warpedCorners; cv::perspectiveTransform(corners, warpedCorners, H); // 计算边界 float x_min = 0, x_max = img1.cols; float y_min = 0, y_max = img1.rows; for (const auto& pt : warpedCorners) { x_min = std::min(x_min, pt.x); x_max = std::max(x_max, pt.x); y_min = std::min(y_min, pt.y); y_max = std::max(y_max, pt.y); } // 创建结果图像 cv::Mat result(cv::Size(std::ceil(x_max - x_min), std::ceil(y_max - y_min)), img1.type()); // 应用变换 cv::Mat translation = cv::Mat::eye(3, 3, CV_64F); translation.at<double>(0, 2) = -x_min; translation.at<double>(1, 2) = -y_min; cv::Mat H_adjusted = translation * H; cv::warpPerspective(img2, result, H_adjusted, result.size()); // 叠加第一幅图像(使用加权平均) for (int y = 0; y < img1.rows; y++) { for (int x = 0; x < img1.cols; x++) { int result_x = x - x_min; int result_y = y - y_min; if (result_x >= 0 && result_x < result.cols && result_y >= 0 && result_y < result.rows) { cv::Vec3b pixel1 = img1.at<cv::Vec3b>(y, x); cv::Vec3b pixel2 = result.at<cv::Vec3b>(result_y, result_x); // 计算权重(简单线性混合) double weight = 0.5; if (x < img1.cols * 0.1) weight = 0.9; // 边缘区域权重更高 else if (x > img1.cols * 0.9) weight = 0.1; cv::Vec3b blended; for (int c = 0; c < 3; c++) { blended[c] = cv::saturate_cast<uchar>(weight * pixel1[c] + (1 - weight) * pixel2[c]); } result.at<cv::Vec3b>(result_y, result_x) = blended; } } } return result; } // 显示图像函数(用于中间过程) void showImage(const std::string& title, const cv::Mat& image) { if (image.empty()) return; // 检查图像是否为空 // 计算缩放比例,确保图像适合屏幕显示 int origH = image.rows; int origW = image.cols; double scale = std::min(DISPLAY_SCALE, 800.0 / origW); scale = std::min(scale, 600.0 / origH); scale = std::max(scale, 0.1); // 确保最小缩放比例 // 缩放图像 cv::Mat displayImg; cv::resize(image, displayImg, cv::Size(), scale, scale); // 显示图像 cv::imshow(title, displayImg); cv::waitKey(1); // 非阻塞等待,允许图像更新 } // 实时显示线程函数 void displayThreadFunc() { while (!stopDisplay) { { std::lock_guard<std::mutex> lock(panoramaMutex); if (!panorama.empty()) { showImage("实时拼接进度", panorama); } } std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 每100ms更新一次 } cv::destroyWindow("实时拼接进度"); } public: // 构造函数:初始化特征检测器类型并设置检测器 AdvancedImageStitcher(const std::string& detectorType = "SIFT") : detectorType(detectorType), stopDisplay(false) { setupDetectors(); } // 析构函数 ~AdvancedImageStitcher() { stopDisplay = true; } // 加载图像函数 void loadImages(const std::string& imageDir) { // 支持的图像扩展名 std::vector<std::string> extensions = { ".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif" }; // 使用OpenCV的glob函数查找图像文件 std::vector<cv::String> filePaths; cv::glob(imageDir + "/*", filePaths); std::vector<std::string> imagePaths; // 筛选图像文件 for (const auto& path : filePaths) { std::string ext = path.substr(path.find_last_of(".")); std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); if (std::find(extensions.begin(), extensions.end(), ext) != extensions.end()) { imagePaths.push_back(path); } } // 检查是否找到图像文件 if (imagePaths.empty()) { std::cerr << "在 '" << imageDir << "' 目录下未找到任何图像文件" << std::endl; return; } // 使用自然排序对图像路径进行排序 std::sort(imagePaths.begin(), imagePaths.end(), naturalCompare); // 输出找到的图像信息 std::cout << "找到 " << imagePaths.size() << " 张图像:" << std::endl; for (size_t i = 0; i < imagePaths.size(); i++) { std::string fileName = imagePaths[i].substr(imagePaths[i].find_last_of("\\/") + 1); std::cout << " " << i + 1 << ": " << fileName << std::endl; } // 保存图像路径 this->imagePaths = imagePaths; // 加载并预处理图像 for (const auto& path : imagePaths) { // 读取图像 cv::Mat img = cv::imread(path); if (img.empty()) { std::cout << "无法加载图像: " << path << std::endl; continue; } // 图像预处理 cv::Mat processedImg = preprocessImage(img); // 缩减图像分辨率(如果太大) int h = processedImg.rows; int w = processedImg.cols; if (w > RESIZE_THRESHOLD || h > RESIZE_THRESHOLD) { double scale = RESIZE_THRESHOLD / static_cast<double>(std::max(w, h)); int newW = static_cast<int>(w * scale); int newH = static_cast<int>(h * scale); cv::resize(processedImg, processedImg, cv::Size(newW, newH), 0, 0, cv::INTER_AREA); std::cout << "已缩放: " << w << "x" << h << " -> " << newW << "x" << newH << std::endl; } // 将图像添加到向量中 images.push_back(processedImg); } // 检查是否成功加载了图像 if (images.empty()) { std::cerr << "未能成功加载任何图像" << std::endl; } } // 拼接图像函数 cv::Mat stitch() { // 如果只有一张或没有图像,直接返回 if (images.size() <= 1) { panorama = images.empty() ? cv::Mat() : images[0].clone(); return panorama; } // 启动实时显示线程 std::thread displayThread; if (SHOW_PROGRESS) { displayThread = std::thread(&AdvancedImageStitcher::displayThreadFunc, this); } // 从第一张图像开始 panorama = images[0].clone(); // 存储所有变换矩阵 std::vector<cv::Mat> transforms; transforms.push_back(cv::Mat::eye(3, 3, CV_64F)); // 第一张图像的变换矩阵是单位矩阵 // 依次拼接后续图像 for (size_t i = 1; i < images.size(); i++) { std::cout << "\n拼接图像 " << i + 1 << "/" << images.size() << std::endl; // 转换为灰度图(特征检测需要灰度图像) cv::Mat panoramaGray, newImgGray; cv::cvtColor(panorama, panoramaGray, cv::COLOR_BGR2GRAY); cv::cvtColor(images[i], newImgGray, cv::COLOR_BGR2GRAY); // 提取特征点和特征描述符 std::vector<cv::KeyPoint> kp1, kp2; cv::Mat des1, des2; detector->detectAndCompute(panoramaGray, cv::noArray(), kp1, des1); detector->detectAndCompute(newImgGray, cv::noArray(), kp2, des2); // 检查是否有足够的特征点 if (des1.empty() || des2.empty() || des1.rows < MIN_MATCHES || des2.rows < MIN_MATCHES) { std::cout << "特征点不足" << std::endl; continue; } // 特征匹配 std::vector<cv::DMatch> matches = matchFeatures(des1, des2); if (matches.size() < MIN_MATCHES) { std::cout << "匹配点不足: " << matches.size() << " < " << MIN_MATCHES << std::endl; continue; } // 计算单应性矩阵 int inliers; cv::Mat H = findHomography(kp1, kp2, matches, inliers); // 检查单应性矩阵是否有效 if (H.empty() || inliers < MIN_MATCHES) { std::cout << "单应性矩阵计算失败或内点不足: " << inliers << std::endl; continue; } // 保存变换矩阵 transforms.push_back(H); // 应用变换并融合图像 if (USE_MULTI_BAND_BLENDING && i == images.size() - 1) { // 如果是最后一张图像,使用多频带融合 std::cout << "使用多频带融合..." << std::endl; std::vector<cv::Mat> allImages(images.begin(), images.begin() + i + 1); cv::Mat newPanorama = multiBandBlend(allImages, transforms); if (!newPanorama.empty()) { std::lock_guard<std::mutex> lock(panoramaMutex); panorama = newPanorama; } } else { // 否则使用加权平均融合 cv::Mat newPanorama = weightedBlend(panorama, images[i], H); if (!newPanorama.empty()) { std::lock_guard<std::mutex> lock(panoramaMutex); panorama = newPanorama; } } // 如果启用进度显示,更新当前拼接状态 if (SHOW_PROGRESS) { std::lock_guard<std::mutex> lock(panoramaMutex); // 显示线程会自动处理显示 } } // 停止显示线程 if (SHOW_PROGRESS) { stopDisplay = true; displayThread.join(); } return panorama; // 返回最终的全景图 } // 显示最终结果 void showResult() { if (panorama.empty()) { std::cout << "没有可显示的结果" << std::endl; return; } // 计算缩放比例,确保图像适合屏幕显示 int h = panorama.rows; int w = panorama.cols; double scale = std::min(DISPLAY_SCALE, 1200.0 / w); scale = std::min(scale, 800.0 / h); scale = std::max(scale, 0.1); // 确保最小缩放比例 // 缩放图像 cv::Mat displayImg; cv::resize(panorama, displayImg, cv::Size(), scale, scale); // 创建窗口并显示结果 cv::namedWindow("拼接结果", cv::WINDOW_NORMAL); cv::imshow("拼接结果", displayImg); // 等待用户按键 std::cout << "按任意键关闭窗口..." << std::endl; cv::waitKey(0); cv::destroyAllWindows(); // 关闭所有OpenCV窗口 } // 保存结果到文件 bool saveResult(const std::string& filename = "panorama.png") { if (panorama.empty()) { std::cout << "没有可保存的结果" << std::endl; return false; } // 保存图像 std::string path = OUTPUT_DIR + "/" + filename; bool success = cv::imwrite(path, panorama); if (success) { std::cout << "结果已保存到: " << path << std::endl; } else { std::cout << "保存失败: " << path << std::endl; } return success; } }; // 主函数 int main() { // 创建拼接器对象使用配置的特征检测器类型 AdvancedImageStitcher stitcher(FEATURE_DETECTOR); try { // 加载图像 std::cout << "加载图像..." << std::endl; stitcher.loadImages(IMAGE_DIR); // 拼接图像 std::cout << "开始拼接..." << std::endl; auto start_time = std::chrono::high_resolution_clock::now(); cv::Mat result = stitcher.stitch(); auto end_time = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time); std::cout << "拼接完成,耗时: " << duration.count() << "ms" << std::endl; // 如果有结果,显示并保存 if (!result.empty()) { // 显示结果 std::cout << "显示结果..." << std::endl; stitcher.showResult(); // 保存结果 std::string filename = "industrial_panorama_" + FEATURE_DETECTOR + ".png"; std::transform(filename.begin(), filename.end(), filename.begin(), ::tolower); stitcher.saveResult(filename); } else { std::cout << "拼接失败" << std::endl; } } catch (const std::exception& e) { // 捕获并输出异常 std::cerr << "错误: " << e.what() << std::endl; } return 0; // 程序正常退出 }代码改正
最新发布
08-27
在 OpenCV 的图像拼接流程中,`cv::detail::resultRoi` 是 `cv::detail::Blender` 类的一个成员函数,用于获取拼接结果的有效区域。直接使用 `cv::detail::resultRoi(transforms, images)` 的写法是错误的,因为该函数并不存在于全局命名空间,也不接受 `transforms` 和 `images` 作为参数[^1]。 为了正确使用 `resultRoi` 函数,需要先创建一个 `cv::detail::Blender` 实例,并调用其 `prepare` 方法设置图像的投影变换信息,通常包括图像的角点(`corners`)和尺寸(`sizes`)[^1]。然后通过 `blender->resultRoi()` 获取一个 `cv::detail::ResultRoi` 结构体,其中包含两个关键的矩形区域: - `final`:表示拼接结果图像的完整区域; - `valid`:表示经过融合处理后的有效图像区域,通常用于裁剪以去除黑边或无效像素。 ### 示例代码 ```cpp std::vector<cv::Point> corners; // 图像投影后的角点 std::vector<cv::Size> sizes; // 图像尺寸 // 假设 corners 和 sizes 已经被正确填充 // 创建 Blender 实例 cv::Ptr<cv::detail::Blender> blender = cv::detail::Blender::createDefault(cv::detail::Blender::FEATHER, true); blender->prepare(corners, sizes); // 获取拼接结果的 ROI cv::detail::ResultRoi roi = blender->resultRoi(); cv::Rect dst_roi_final = roi.final; // 最终图像的完整区域 cv::Rect dst_roi_valid = roi.valid; // 融合后的有效区域 ``` 如果使用的是羽化融合(`FEATHER`)或多波段融合(`MULTI_BAND`),则 `valid` 区域通常比 `final` 更小,表示实际图像内容的边界。可以通过 `cv::Mat` 的区域选择操作提取该部分图像: ```cpp cv::Mat result; blender->blend(result, cv::noArray()); cv::Mat valid_result = result(dst_roi_valid); // 提取有效区域 ``` ### 注意事项 - `corners` 和 `sizes` 需要根据每张图像的变换矩阵(如单应性矩阵)进行计算,通常通过 `warpPerspective` 或 `warpAffine` 投影图像后获得角点坐标; - 如果拼接后图像存在大量黑边,建议使用 `resultRoi().valid` 来自动裁剪图像,以获得更紧凑的结果; - `cv::detail::Blender::createDefault` 支持多种融合方式,如 `NO`(无融合)、`FEATHER`(羽化融合)、`MULTI_BAND`(多波段融合)等。 ###
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值