使用Qt实现图片拼接功能

时间:2024-03-14 15:32:06

                                                                                使用Qt实现图片拼接功能

   Qt可以在自带的界面编辑器Qt Creator下编写,也可以在VS下配置Qt 环境编写,本文将介绍在Creator下编写一个简单的界面用以实现图片拼接的功能。

图片拼接需要使用到opencv库,所以需要在Qt Creator 下配置一下opencv,此步骤可百度教程即可,本文就不再赘述,现给出pro文件内的环境配置代码(本文以2.4.13版本为例)。

INCLUDEPATH+=D:\opencv2413\opencv\build\include\

D:\opencv2413\opencv\build\include\opencv\

D:\opencv2413\opencv\build\include\opencv2

LIBS +=-LD:\opencv2413\opencv\build\x86\vc11\lib \

-lopencv_ml2413d\

-lopencv_calib3d2413d\

-lopencv_contrib2413d\

-lopencv_core2413d\

-lopencv_features2d2413d\

-lopencv_flann2413d\

-lopencv_gpu2413d\

-lopencv_highgui2413d\

-lopencv_imgproc2413d\

-lopencv_legacy2413d\

-lopencv_objdetect2413d\

-lopencv_ts2413d\

-lopencv_video2413d\

-lopencv_nonfree2413d\

-lopencv_ocl2413d\

-lopencv_photo2413d\

-lopencv_stitching2413d\

-lopencv_superres2413d\

-lopencv_videostab2413d

在环境配置完成后,开始ui设计,本次设计界面如下,在File下拉框内添加Open打开动作用以打开图片。

在Creator下的控件关联比较方便,可以不需要使用connect函数,直接“转到槽”即可。

先打开两张图片,并显示在label1、label2控件上,然后实现点击按钮进行拼接,并将拼接结果图片显示在label3控件上。

代码实现如下:

//头文件

#ifndef MAINWINDOW_H

#define MAINWINDOW_H

#include <QMainWindow>

#include <opencv2/opencv.hpp>

#include <iostream>

#include <QFileDialog>

#include <QString>

#include <QTextCodec>

#include <opencv2/nonfree/nonfree.hpp>

#include <opencv2/legacy/legacy.hpp>

#include <opencv2/stitching/stitcher.hpp>

#include <opencv2/core/core.hpp>

#include <opencv2/highgui/highgui.hpp>

#include <QPainter>

using namespace cv;

using namespace std;

namespace Ui {

class MainWindow;

}

class MainWindow : public QMainWindow

{

    Q_OBJECT

public:

    explicit MainWindow(QWidget *parent = 0);

    ~MainWindow();

    Mat image01,image02;

    QImage img01,img02,imgpano;

    typedef struct

    {

        Point2f left_top;

        Point2f left_bottom;

        Point2f right_top;

        Point2f right_bottom;

    }four_corners_t;

    four_corners_t corners;

    void paintEvent(QPaintEvent *);

    void MainWindow::OptimizeSeam(Mat& img1, Mat& trans, Mat& dst);

    void MainWindow::CalcCorners(const Mat& H, const Mat& src);

private slots:

    void on_actionOpen_triggered();

    void on_pushButton_stitch_clicked();

private:

    Ui::MainWindow *ui;

};

#endif // MAINWINDOW_H

 

  //源文件

#include "mainwindow.h"

#include "ui_mainwindow.h"

 

MainWindow::MainWindow(QWidget *parent) :

    QMainWindow(parent),

    ui(new Ui::MainWindow)

{

    ui->setupUi(this);

}

 

MainWindow::~MainWindow()

{

    delete ui;

}

 

//设置背景图

void MainWindow::paintEvent(QPaintEvent *)

{

    QPainter p(this);

    //画背景图

    p.drawPixmap(0,0,width(),height(),QPixmap("D:/lena.jpg"));

}

 

void MainWindow::on_actionOpen_triggered()

{

    //从路径中导入第一张图片

    QString fileName = QFileDialog::getOpenFileName(this,"Open Image",

    "../","Image Files (*.png *.jpg *.bmp)");

    QTextCodec *code = QTextCodec::codecForName("GB2312");

    string name = code->fromUnicode(fileName).data();

    image01=imread(name,1);

    cvtColor(image01,image01,CV_BGR2RGB);

    //将Mat转为QImage

    img01 = QImage((const unsigned char*)(image01.data),

                   image01.cols,image01.rows,image01.cols*image01.channels(),

                   QImage::Format_RGB888);

    ui->label->clear();

    //将图片以Label控件大小显示

    img01img01.scaled(ui->label->width(),ui->label->height());

    ui->label->setPixmap(QPixmap::fromImage(img01));

 

    //自定义路径中导入第二张图片

    QString fileName2 = QFileDialog::getOpenFileName(this,"Open Image",

    "../","Image Files (*.png *.jpg *.bmp)");

    QTextCodec *code2 = QTextCodec::codecForName("GB2312");

    string name2 = code2->fromUnicode(fileName2).data();

    image02=imread(name2,1);

    //下面的操作主要为格式的转换

    cvtColor(image02,image02,CV_BGR2RGB);//保证颜色通道

    img02 = QImage((const unsigned char*)(image02.data),

                   image02.cols,image02.rows,image02.cols*image02.channels(),

                   QImage::Format_RGB888);

    ui->label_right->clear();

    img02img02.scaled(ui->label_right->width(),ui->label_right->height());

    ui->label_right->setPixmap(QPixmap::fromImage(img02));

}

void MainWindow::on_pushButton_stitch_clicked()

{

    Mat image1, image2;

            cvtColor(image01, image1, CV_RGB2GRAY);

            cvtColor(image02, image2, CV_RGB2GRAY);

            //提取特征点

            SurfFeatureDetector Detector(10000);

            vector<KeyPoint> keyPoint1, keyPoint2;

            Detector.detect(image1, keyPoint1);

            Detector.detect(image2, keyPoint2);

 

        /*特征点描述,为下边的特征点匹配做准备   */

            cv::SurfDescriptorExtractor Descriptor;

            Mat imageDesc1, imageDesc2;

            Descriptor.compute(image1, keyPoint1, imageDesc1);

            Descriptor.compute(image2, keyPoint2, imageDesc2);

 

            cv::FlannBasedMatcher matcher;

            cv::vector<vector<DMatch> > matchePoints;

            cv::vector<DMatch> GoodMatchePoints;

 

            cv::vector<Mat> train_desc(1, imageDesc1);

            matcher.add(train_desc);

            matcher.train();

 

            matcher.knnMatch(imageDesc2, matchePoints, 2);

 

            Mat first_match;

            drawMatches(image02, keyPoint2, image01, keyPoint1, matchePoints, first_match);

            // Lowe's algorithm,获取优秀匹配点

            for (int i = 0; i < matchePoints.size(); i++)

            {

                if (matchePoints[i][0].distance < 0.8* matchePoints[i][1].distance)

                {

                    GoodMatchePoints.push_back(matchePoints[i][0]);

                }

            }

            Mat good_match;

            drawMatches(image02, keyPoint2, image01, keyPoint1, GoodMatchePoints, good_match);

 

//            imshow("goodmatch",good_match);

           cv::vector<Point2f> imagePoints1, imagePoints2;

 

            for (int i = 0; i<GoodMatchePoints.size(); i++)

            {

                imagePoints2.push_back(keyPoint2[GoodMatchePoints[i].queryIdx].pt);

                imagePoints1.push_back(keyPoint1[GoodMatchePoints[i].trainIdx].pt);

            }

            //获取图像1到图像2的投影映射矩阵 尺寸为3*3

            Mat homo = findHomography(imagePoints1, imagePoints2, CV_RANSAC);

            //图像配准

        Mat imageTransform1;

        warpPerspective(image01, imageTransform1, homo, Size(MAX(corners.right_top.xcorners.right_bottom.x), image02.rows));

 

        //创建拼接后的图,需提前计算图的大小

        int dst_width = imageTransform1.cols;  //取最右点的长度为拼接图的长度

        int dst_height = image02.rows;

 

        Mat dst(dst_height, dst_width, CV_8UC3);

        dst.setTo(0);

 

        imageTransform1.copyTo(dst(Rect(00, imageTransform1.cols, imageTransform1.rows)));

        //imshow("", image02);

        //waitKey();

        image02.copyTo(dst(Rect(00image02.colsimage02.rows)));

        OptimizeSeam(image02, imageTransform1, dst);

        cvtColor(dst,dst,CV_BGR2RGB);//保证颜色通道,这一步很重要!!!!

 

//        namedWindow("pano");

//        imwrite("D:/pano.jpg",dst);

 

//        cvtColor(dst,dst,CV_BGR2RGB);//保证颜色通道,这一步很重要!!!!

        imgpano = QImage((const unsigned char*)(dst.data),

                       dst.cols,dst.rows,dst.cols*dst.channels(),

                       QImage::Format_RGB888);

        ui->label_pano->clear();

        imgpanoimgpano.scaled(ui->label_pano->width(),ui->label_pano->height());

        ui->label_pano->setPixmap(QPixmap::fromImage(imgpano));

 

 

}

void MainWindow::CalcCorners(const Mat& H, const Mat& src)

{

 

    double v2[] = { 001 };//左上角

    double v1[3];//变换后的坐标值

    Mat V2 = Mat(31CV_64FC1, v2);  //列向量

    Mat V1 = Mat(31CV_64FC1, v1);  //列向量

 

    V1 = H * V2;

    //左上角(0,0,1)

    /* cout << "V2: " << V2 << endl;

    cout << "V1: " << V1 << endl;*/

    corners.left_top.x = v1[0] / v1[2];

    corners.left_top.y = v1[1] / v1[2];

 

    //左下角(0,src.rows,1)

    v2[0] = 0;

    v2[1] = src.rows;

    v2[2] = 1;

    V2 = Mat(31CV_64FC1, v2);  //列向量

    V1 = Mat(31CV_64FC1, v1);  //列向量

    V1 = H * V2;

    corners.left_bottom.x = v1[0] / v1[2];

    corners.left_bottom.y = v1[1] / v1[2];

 

    //右上角(src.cols,0,1)

    v2[0] = src.cols;

    v2[1] = 0;

    v2[2] = 1;

    V2 = Mat(31CV_64FC1, v2);  //列向量

    V1 = Mat(31CV_64FC1, v1);  //列向量

    V1 = H * V2;

    corners.right_top.x = v1[0] / v1[2];

    corners.right_top.y = v1[1] / v1[2];

 

    //右下角(src.cols,src.rows,1)

    v2[0] = src.cols;

    v2[1] = src.rows;

    v2[2] = 1;

    V2 = Mat(31CV_64FC1, v2);  //列向量

    V1 = Mat(31CV_64FC1, v1);  //列向量

    V1 = H * V2;

    corners.right_bottom.x = v1[0] / v1[2];

    corners.right_bottom.y = v1[1] / v1[2];

 

}

void MainWindow::OptimizeSeam(Mat& img1, Mat& trans, Mat& dst)

{

    int start = MIN(corners.left_top.xcorners.left_bottom.x);//开始位置,即重叠区域的左边界

 

    double processWidth = img1.cols - start;//重叠区域的宽度

    int rows = dst.rows;

    int cols = img1.cols//注意,是列数*通道数

    double alpha = 1;//img1中像素的权重,初始化权重值

    for (int i = 0; i < rows; i++)

    {

        uchar* p = img1.ptr<uchar>(i);  //获取第i行的首地址

        uchar* t = trans.ptr<uchar>(i);

        uchar* d = dst.ptr<uchar>(i);

        for (int j = start; j < cols; j++)

        {

            //如果遇到图像trans中无像素的黑点,则完全拷贝img1中的数据

            if (t[j * 3] == 0 && t[j * 3 + 1] == 0 && t[j * 3 + 2] == 0)

            {

                alpha = 1;

            }

            else

            {

                //img1中像素的权重,与当前处理点距重叠区域左边界的距离成正比,实验证明,这种方法确实好

                alpha = (processWidth - (j - start)) / processWidth;

                if (alpha>=0.5)

                {

                    alpha = 1;

                 }

                else

                {

                    alpha = 0;

                }

            }

 

            d[j * 3] = p[j * 3] * alpha + t[j * 3] * (1 - alpha);

            d[j * 3 + 1] = p[j * 3 + 1] * alpha + t[j * 3 + 1] * (1 - alpha);

            d[j * 3 + 2] = p[j * 3 + 2] * alpha + t[j * 3 + 2] * (1 - alpha);

 

        }

    }

 

}

 

结果如下:

使用Qt实现图片拼接功能