// 引入必要的头文件
#include <opencv2/opencv.hpp> // OpenCV 核心库,用于图像处理
#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> // 互斥锁库
// 配置参数 - 这些是程序的设置,可以根据需要修改
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 == "AKAZE") {
detector = cv::AKAZE::create();
}
else { // 默认使用ORB
detector = cv::ORB::create(MAX_FEATURES);
}
// 根据特征检测器类型创建匹配器
if (detectorType == "SIFT" ) {
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 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);
// 应用变换并融合图像
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; // 程序正常退出
}拼接完图像为什么都是黑影虚图,重新修改下
最新发布