#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFileDialog>
#include <QMessageBox>
#include <opencv2/opencv.hpp>
#include <fstream>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setWindowTitle("核线影像匹配实验");
}
MainWindow::~MainWindow() {
delete ui;
}
// 加载左影像
void MainWindow::on_loadLeftButton_clicked() {
QString filename = QFileDialog::getOpenFileName(this, "选择左核线影像", "", "图像文件 (*.png *.jpg *.bmp)");
if (!filename.isEmpty()) {
leftImage = cv::imread(filename.toStdString());
cv::cvtColor(leftImage, leftGray, cv::COLOR_BGR2GRAY);
cv::Mat rgb;
cv::cvtColor(leftImage, rgb, cv::COLOR_BGR2RGB);
QImage img(rgb.data, rgb.cols, rgb.rows, rgb.step, QImage::Format_RGB888);
ui->leftImageLabel->setPixmap(QPixmap::fromImage(img).scaled(ui->leftImageLabel->size(), Qt::KeepAspectRatio));
}
}
// 加载右影像
void MainWindow::on_loadRightButton_clicked() {
QString filename = QFileDialog::getOpenFileName(this, "选择右核线影像", "", "图像文件 (*.png *.jpg *.bmp)");
if (!filename.isEmpty()) {
rightImage = cv::imread(filename.toStdString());
cv::cvtColor(rightImage, rightGray, cv::COLOR_BGR2GRAY);
cv::Mat rgb;
cv::cvtColor(rightImage, rgb, cv::COLOR_BGR2RGB);
QImage img(rgb.data, rgb.cols, rgb.rows, rgb.step, QImage::Format_RGB888);
ui->rightImageLabel->setPixmap(QPixmap::fromImage(img).scaled(ui->rightImageLabel->size(), Qt::KeepAspectRatio));
}
}
// Moravec特征点提取
void MainWindow::extractFeatures() {
const int windowSize = 5; // 窗口大小
const int threshold = 5000; // 特征点阈值
leftFeatures.clear();
cv::Mat varianceMap = cv::Mat::zeros(leftGray.size(), CV_32F);
// 计算每个像素的Moravec响应值
for (int y = windowSize; y < leftGray.rows - windowSize; y++) {
for (int x = windowSize; x < leftGray.cols - windowSize; x++) {
float minVariance = FLT_MAX;
// 计算4个方向的灰度差平方和
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dx == 0 && dy == 0) continue;
float sum = 0;
for (int wy = -windowSize/2; wy <= windowSize/2; wy++) {
for (int wx = -windowSize/2; wx <= windowSize/2; wx++) {
uchar p1 = leftGray.at<uchar>(y+wy, x+wx);
uchar p2 = leftGray.at<uchar>(y+wy+dy, x+wx+dx);
sum += (p1 - p2)*(p1 - p2);
}
}
if (sum < minVariance) minVariance = sum;
}
}
varianceMap.at<float>(y, x) = minVariance;
}
}
// 非极大值抑制提取特征点
const int nmsSize = 7;
for (int y = nmsSize; y < varianceMap.rows - nmsSize; y++) {
for (int x = nmsSize; x < varianceMap.cols - nmsSize; x++) {
if (varianceMap.at<float>(y, x) < threshold) continue;
bool isMax = true;
for (int ny = -nmsSize/2; ny <= nmsSize/2 && isMax; ny++) {
for (int nx = -nmsSize/2; nx <= nmsSize/2; nx++) {
if (nx == 0 && ny == 0) continue;
if (varianceMap.at<float>(y+ny, x+nx) >= varianceMap.at<float>(y, x)) {
isMax = false;
break;
}
}
}
if (isMax) leftFeatures.push_back(cv::Point(x, y));
}
}
}
// 相关系数匹配
void MainWindow::matchFeatures() {
const int windowSize = 15; // 匹配窗口大小
const int searchRange = 50; // 右影像搜索范围
const double nccThreshold = 0.7; // NCC阈值
matchedPoints.clear();
matchedPoints.resize(leftFeatures.size(), cv::Point(-1, -1));
for (size_t i = 0; i < leftFeatures.size(); i++) {
cv::Point p = leftFeatures[i];
cv::Rect leftROI(p.x - windowSize/2, p.y - windowSize/2, windowSize, windowSize);
cv::Mat leftPatch = leftGray(leftROI).clone();
double maxNCC = -1.0;
int bestX = -1;
// 在右影像同一行上搜索
int startX = std::max(windowSize/2, p.x - searchRange);
int endX = std::min(rightGray.cols - windowSize/2 - 1, p.x + searchRange);
for (int x = startX; x <= endX; x++) {
cv::Rect rightROI(x - windowSize/2, p.y - windowSize/2, windowSize, windowSize);
cv::Mat rightPatch = rightGray(rightROI).clone();
// 计算NCC
cv::Mat meanLeft, meanRight;
cv::Mat stdDevLeft, stdDevRight;
cv::meanStdDev(leftPatch, meanLeft, stdDevLeft);
cv::meanStdDev(rightPatch, meanRight, stdDevRight);
double covar = 0;
for (int wy = 0; wy < windowSize; wy++) {
for (int wx = 0; wx < windowSize; wx++) {
double lVal = leftPatch.at<uchar>(wy, wx) - meanLeft.at<double>(0);
double rVal = rightPatch.at<uchar>(wy, wx) - meanRight.at<double>(0);
covar += lVal * rVal;
}
}
double ncc = covar / ((stdDevLeft.at<double>(0)*stdDevRight.at<double>(0)) * windowSize*windowSize);
if (ncc > maxNCC) {
maxNCC = ncc;
bestX = x;
}
}
if (maxNCC > nccThreshold) {
matchedPoints[i] = cv::Point(bestX, p.y);
}
}
}
// 执行匹配操作
void MainWindow::on_matchButton_clicked() {
if (leftGray.empty() || rightGray.empty()) {
QMessageBox::warning(this, "错误", "请先加载左右影像!");
return;
}
extractFeatures(); // 特征提取
matchFeatures(); // 相关系数匹配
saveMatchesToFile(); // 保存匹配结果
// 在左影像上显示特征点
cv::Mat leftDisplay = leftImage.clone();
for (const auto& p : leftFeatures) {
cv::circle(leftDisplay, p, 3, cv::Scalar(0, 0, 255), -1);
}
cv::cvtColor(leftDisplay, leftDisplay, cv::COLOR_BGR2RGB);
QImage img(leftDisplay.data, leftDisplay.cols, leftDisplay.rows, leftDisplay.step, QImage::Format_RGB888);
ui->leftImageLabel->setPixmap(QPixmap::fromImage(img).scaled(ui->leftImageLabel->size(), Qt::KeepAspectRatio));
QMessageBox::information(this, "匹配完成", "特征匹配完成!同名点已保存到matches.txt");
}
// 保存匹配点到文件
void MainWindow::saveMatchesToFile() {
std::ofstream outFile("matches.txt");
if (!outFile) {
QMessageBox::critical(this, "错误", "无法创建输出文件!");
return;
}
outFile << "left_x,left_y,right_x,right_y" << std::endl;
for (size_t i = 0; i < leftFeatures.size(); i++) {
if (matchedPoints[i].x != -1) {
outFile << leftFeatures[i].x << "," << leftFeatures[i].y << ","
<< matchedPoints[i].x << "," << matchedPoints[i].y << std::endl;
}
}
outFile.close();
}
// 显示匹配结果
void MainWindow::on_showMatchesButton_clicked() {
if (matchedPoints.empty() || leftImage.empty() || rightImage.empty()) {
QMessageBox::warning(this, "错误", "请先执行匹配操作!");
return;
}
visualizeMatches();
}
void MainWindow::visualizeMatches() {
// 1. 创建合成图像(左图在上,右图在下)
const int padding = 20; // 添加边距方便观察
int maxWidth = std::max(leftImage.cols, rightImage.cols) + 2*padding;
int totalHeight = leftImage.rows + rightImage.rows + 3*padding;
cv::Mat resultImage(totalHeight, maxWidth, CV_8UC3, cv::Scalar(40, 40, 40)); // 深灰背景
// 2. 放置图像(水平居中)
int leftX = padding + (maxWidth - 2*padding - leftImage.cols)/2;
int rightX = padding + (maxWidth - 2*padding - rightImage.cols)/2;
int rightY = leftImage.rows + 2*padding;
// 左图区域(添加边框)
cv::rectangle(resultImage,
cv::Rect(leftX-1, padding-1, leftImage.cols+2, leftImage.rows+2),
cv::Scalar(200, 200, 200), 1);
leftImage.copyTo(resultImage(cv::Rect(leftX, padding, leftImage.cols, leftImage.rows)));
// 右图区域(添加边框)
cv::rectangle(resultImage,
cv::Rect(rightX-1, rightY-1, rightImage.cols+2, rightImage.rows+2),
cv::Scalar(200, 200, 200), 1);
rightImage.copyTo(resultImage(cv::Rect(rightX, rightY, rightImage.cols, rightImage.rows)));
// 3. 计算坐标偏移量
cv::Point leftOffset(leftX, padding);
cv::Point rightOffset(rightX, rightY);
// 4. 绘制匹配点连线(添加校验和抗锯齿)
for (size_t i = 0; i < leftFeatures.size(); i++) {
if (matchedPoints[i].x < 0) continue; // 跳过无效点
// 计算实际绘制位置
cv::Point leftPt = leftFeatures[i] + leftOffset;
cv::Point rightPt = matchedPoints[i] + rightOffset;
// 校验点是否在图像区域内
cv::Rect leftROI(leftX, padding, leftImage.cols, leftImage.rows);
cv::Rect rightROI(rightX, rightY, rightImage.cols, rightImage.rows);
if (!leftROI.contains(leftPt - leftOffset) ||
!rightROI.contains(rightPt - rightOffset)) {
continue; // 跳过越界点
}
// ************ 新增红色连接线 ************
// 绘制红色直线连接同名点(在箭头下方)
cv::line(resultImage, leftPt, rightPt,
cv::Scalar(0, 0, 255), // 纯红色 (BGR格式)
3, // 线宽3像素
cv::LINE_AA); // 抗锯齿
// 绘制抗锯齿连线(带箭头指示方向)- 保持原有绿色箭头
cv::arrowedLine(resultImage, leftPt, rightPt,
cv::Scalar(0, 255, 0), // 绿色箭头
2, // 线宽2像素
cv::LINE_AA, // 抗锯齿
0, // 箭头尖端无偏移
0.01); // 箭头长度比例
// 绘制同名点(使用相同颜色)
cv::Scalar color = cv::Scalar(
rand() % 256, rand() % 256, rand() % 256); // 随机颜色区分不同点对
cv::circle(resultImage, leftPt, 6, color, -1, cv::LINE_AA);
cv::circle(resultImage, rightPt, 6, color, -1, cv::LINE_AA);
// 添加点编号(显示匹配误差)
double error = cv::norm(leftFeatures[i] - matchedPoints[i]);
std::string label = "#" + std::to_string(i+1) + " (err:" +
std::to_string(error).substr(0,4) + ")";
cv::putText(resultImage, label, leftPt + cv::Point(10, -10),
cv::FONT_HERSHEY_SIMPLEX, 0.5, color, 1);
}
// 5. 添加参考网格(辅助视觉对齐)
for (int x = 0; x < maxWidth; x += 50) {
cv::line(resultImage, cv::Point(x, 0), cv::Point(x, totalHeight),
cv::Scalar(100, 100, 100), 1);
}
for (int y = 0; y < totalHeight; y += 50) {
cv::line(resultImage, cv::Point(0, y), cv::Point(maxWidth, y),
cv::Scalar(100, 100, 100), 1);
}
// 6. 添加图例说明
cv::rectangle(resultImage, cv::Rect(10, totalHeight-100, 250, 90),
cv::Scalar(80, 80, 80), -1);
cv::putText(resultImage, "Legend:", cv::Point(20, totalHeight-80),
cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(200, 200, 0), 1);
cv::putText(resultImage, "Red Line: Direct Connection", cv::Point(20, totalHeight-60),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 255), 1);
cv::putText(resultImage, "Green Arrow: Matching Direction", cv::Point(20, totalHeight-40),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 1);
cv::putText(resultImage, "Colored Dot: Matched Point Pair", cv::Point(20, totalHeight-20),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 255), 1);
// 7. 显示结果
cv::cvtColor(resultImage, resultImage, cv::COLOR_BGR2RGB);
QImage img(resultImage.data, resultImage.cols, resultImage.rows,
resultImage.step, QImage::Format_RGB888);
ui->resultLabel->setPixmap(QPixmap::fromImage(img).scaled(
ui->resultLabel->size(), Qt::KeepAspectRatio));
}这是.cpp的代码我希望修改
void MainWindow::visualizeMatches()的代码并实现将左影像和右影像按照上下的顺序排列并连接合并后的影像上的同名点,其它代码保持不变
最新发布