1. 程式人生 > >使用QT搭建點雲顯示框架系列五·基於QT的QML影象選點、動態繪製十字絲功能 ,以及紋理對映

使用QT搭建點雲顯示框架系列五·基於QT的QML影象選點、動態繪製十字絲功能 ,以及紋理對映

本文所有原始碼分享就看我最新的文章,歡迎各位大佬前來交流。

http://blog.csdn.net/qq_30547073/article/details/79092419

上一次利用QTeststream讀取了任意格式的點雲。

這一次我花了一天的時間學習並實現了一個基於QML的互動選點的功能,可以繪製十字絲,還可以刪除。

我們首先上效果:


因為我主要是為了實現一個紋理對映功能。簡單來說紋理對映就是將圖片貼到點雲或者三角網模型上面去。那麼首先必須分別在圖片上和點雲上選擇若干個控制點。點雲上的選點已經用QGLViewer實現了,那麼這一次就講一下怎麼利用QML在圖片上選點。那麼本人修改了來自qt官方的圖片瀏覽器。其原始碼如下:

import QtQuick 2.5
import QtQuick.Dialogs 1.0
import QtQuick.Window 2.1
import Qt.labs.folderlistmodel 1.0
Window
{
    id: root
    visible: true
    width: 1024; height: 600
    color: "black"
    property int highestZ: 0
    property real defaultSize: 200
    property var currentFrame: undefined
    property real surfaceViewportRatio2: 1.5
    property real frameBorderRatio: 0.08;
    FileDialog
    {
        id: fileDialog
        title: "Choose a folder with some images"
        selectFolder: true
        onAccepted: folderModel.folder = fileUrl + "/"
    }

    Flickable
    {
        //Flickable提供一個較小的視窗來顯示一個較大的內容給使用者,並且使用者可以對改內容進行拖拽和輕拂

        id: flick
        anchors.fill: parent;
        contentWidth: width * surfaceViewportRatio2;//上面定義的Ratio下面可以呼叫,主要是用來控制場景大小
        contentHeight: height * surfaceViewportRatio2;
        Repeater
        {
            //FolderListModel是QT提供的一個可以訪問本地系統資料夾內容的元件,它可以將獲取到的資訊提供給別的元件使用
            model: FolderListModel
            {
                id: folderModel
                objectName: "folderModel"
                //showDirs: false
                showDirs:true;//是否顯示資料夾。預設為真
                showDotAndDotDot: false;//如果為真,"." and ".."目錄被包含在model中,否則被排除。預設為假
                sortReversed: false;//如果為真,逆轉排序順序。預設為假
                nameFilters: ["*.png", "*.jpg", "*.gif"]
            }
            Rectangle
            {
                id: photoFrame
                objectName: "obj";
                color:"red";//這樣就控制邊框為紅色了
                width: image.width * (1 + frameBorderRatio * image.height / image.width)
                height: image.height * (1.0+frameBorderRatio);
                scale: defaultSize / Math.max(image.sourceSize.width, image.sourceSize.height)
                Behavior on scale { NumberAnimation { duration: 300 } }
                Behavior on x { NumberAnimation { easing.amplitude: 1.05; easing.type: Easing.InOutSine; duration: 2000 } }
                Behavior on y { NumberAnimation { easing.type: Easing.OutInQuad; duration: 2000 } }
                border.color: "black"
                border.width: 3
                smooth: true
                antialiasing: true
                Component.onCompleted: //組建完成後進行運動
                {
                    x = Math.random() * root.width - width / 2
                    y = Math.random() * root.height - height / 2;
                    rotation = Math.random() * 13; //- 6
                }
                Image
                {
                    id: image
                    anchors.centerIn: parent
                    fillMode: Image.PreserveAspectFit
                    //fillMode:Image.PreserveAspectCrop
                    source: folderModel.folder + fileName
                    antialiasing: true
                }
                PinchArea //PinchArea是一個不可見的物件,常用在與一個可見物件連線在一起,為對應的可見物件提供手勢操作
                {
                    anchors.fill: parent
                    pinch.target: photoFrame
                    pinch.minimumRotation: -360
                    pinch.maximumRotation: 360
                    pinch.minimumScale: 0.2
                    pinch.maximumScale: 4
                    pinch.dragAxis: Pinch.XAndYAxis
                    onPinchStarted: setFrameColor();
                    property real zRestore: 0

                    MouseArea
                    {
                        id: dragArea
                        hoverEnabled: true
                        anchors.fill: parent
                        drag.target: photoFrame

                        //scrollGestureEnabled: false  // 2-finger-flick gesture should pass through to the Flickable

                        onPressed:
                        {
                            photoFrame.z = ++root.highestZ;
                            parent.setFrameColor();
                        }
                        onEntered: parent.setFrameColor();
                        onWheel:
                        {
                            if (wheel.modifiers & Qt.ControlModifier)
                            {
                                photoFrame.rotation += wheel.angleDelta.y / 120 * 5;
                                if (Math.abs(photoFrame.rotation) < 4)
                                    photoFrame.rotation = 0;
                            }
                            else
                            {
                                photoFrame.rotation += wheel.angleDelta.x / 120;
                                if (Math.abs(photoFrame.rotation) < 0.6)
                                    photoFrame.rotation = 0;
                                var scaleBefore = photoFrame.scale;
                                photoFrame.scale += photoFrame.scale * wheel.angleDelta.y / 120 / 10;
                            }
                        }
                    }
                    function setFrameColor() {
                        if (currentFrame)
                            currentFrame.border.color = "black";
                        currentFrame = photoFrame;
                        currentFrame.border.color = "yellow";
                    }
                }//PinchArea
            }//Rectangle
        }//Repeater
    }//Flickable

    Rectangle {//右側滑動條
        id: verticalScrollDecorator
        anchors.right: parent.right
        anchors.margins: 2
        color: "cyan"
        border.color: "black"
        border.width: 1
        width: 5
        radius: 2
        antialiasing: true
        height: flick.height * (flick.height / flick.contentHeight) - (width - anchors.margins) * 2
        y:  flick.contentY * (flick.height / flick.contentHeight)
        NumberAnimation on opacity { id: vfade; easing.type: Easing.InOutQuint; to: 0; duration: 200 }
        onYChanged: { opacity = 1.0; fadeTimer.restart() }
    }

    Rectangle {//下方水平滑動條
        id: horizontalScrollDecorator
        anchors.bottom: parent.bottom
        anchors.margins: 2
        color: "cyan"
        border.color: "black"
        border.width: 1
        height: 5
        radius: 2
        antialiasing: true
        width: flick.width * (flick.width / flick.contentWidth) - (height - anchors.margins) * 2
        x:  flick.contentX * (flick.width / flick.contentWidth)
        NumberAnimation on opacity { id: hfade; to: 0; duration: 500 }
        onXChanged: { opacity = 1.0; fadeTimer.restart() }
    }

    Timer { id: fadeTimer; interval: 1000; onTriggered: { hfade.start(); vfade.start() } }

    Image {
    //一個影象按鈕用來開啟新影象
        id:imageBT;

        anchors.top: parent.top
        anchors.left: parent.left
        anchors.margins: 10
        source: "qrc:/icons/icos/ninViewerHover.png"
        MouseArea {
            hoverEnabled:true;//即使沒有滑鼠按下也會執行
            onEntered:
            {
                imageBT.source="qrc:/icons/icos/ninViewerpress.png";
            }
            onPressed:
            {
                imageBT.source="qrc:/icons/icos/ninViewerpress.png";
            }
            onExited:
            {
                imageBT.source="qrc:/icons/icos/ninViewerHover.png";
            }
            anchors.fill: parent
            anchors.margins: -10
            onClicked:
            {
                fileDialog.open();
            }
        }
    }

    Text {
        anchors.bottom: parent.bottom
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.margins: 10
        color: "darkgrey"
        wrapMode: Text.WordWrap
        font.pointSize: 8
        text: "On a touchscreen: use two fingers to zoom and rotate, one finger to drag\n" +
              "With a mouse: drag normally, use the vertical wheel to zoom, horizontal wheel to rotate, or hold Ctrl while using the vertical wheel to rotate"
    }

    Component.onCompleted: fileDialog.open()
}
這是一個將近兩百行的檔案瀏覽器,可以開啟多張影象,對每張影象進行旋轉平移縮放。具體效果如下:


然而我們不需要那麼複雜的效果,我們只需要開啟一張影象就行了。然後我們必須要能在上面選點。

我們首先必須獲取滑鼠的位置。必須修改PinchArea中的程式碼。

首先我們把它變成只能選擇一張圖片,改成這個樣子:

import QtQuick 2.5
import QtQuick.Dialogs 1.0
import QtQuick.Window 2.1
import Qt.labs.folderlistmodel 1.0
Window
{
    id: root
    objectName: "viewerRoot";
    visible: true
    width: 1024; height: 600
    color: "darkred"

    //now we try to send some double
    MouseArea
    {
        anchors.fill: parent
        onClicked:
        {
            
        }
    }
    FileDialog
    {
        id: fileDialog
        objectName: "filedialog1";
        title: "Choose a folder with some images"
        // selectFolder: true //if we select folder or file
        onAccepted:
        {
            //folderModel.folder = fileUrl + "/";
            console.log("You chose: " + fileDialog.fileUrl)
            image.source = fileDialog.fileUrl;
        }
    }

    onScarletSignal_PointSelected:
    {
        console.log("photoFrame Width:",photoFrame.width);
        console.log("SelectX:",mouse_X);
        console.log("SelectY:",mouse_Y);
    }

    //(double mouse_X,double mouse_Y);
    //Provide a small window to display a larger content to the user,
    //and the user can drag and flick on it
    Flickable
    {
        id: flick
        anchors.fill: parent;
        contentWidth: width * surfaceViewportRatio2;//上面定義的Ratio下面可以呼叫,主要是用來控制場景大小
        contentHeight: height * surfaceViewportRatio2;

        Rectangle
        {
            property Component component: null;
            property real count: 0;
            property var dynamicObjects: new Array();

            id: photoFrame
            objectName: "obj";
            color:"yellow";//control the bolder to red
            width: image.width * (1 + frameBorderRatio * image.height / image.width)
            height: image.height * (1.0+frameBorderRatio);
            x:0;
            y:0;
            //scale: defaultSize / Math.max(image.sourceSize.width, image.sourceSize.height)
            scale: 1.0;//defaultSize / Math.max(image.sourceSize.width, image.sourceSize.height)
            Behavior on scale { NumberAnimation { duration: 200 } }
            //Behavior on x { NumberAnimation { easing.amplitude: 1.05; easing.type: Easing.InOutSine; duration: 2000 } }
            Behavior on x { NumberAnimation { easing.amplitude: 1.05; easing.type: Easing.InCirc; duration: 1500 } }
            Behavior on y { NumberAnimation { easing.type: Easing.OutInQuad; duration: 1500 } }
            border.color: "#0ef5da"
            border.width: 3
            smooth: true
            antialiasing: true
            Component.onCompleted: //組建完成後進行運動
            {
                x =0; //root.width/ 2 - width / 2
                y =0;// root.height/ 2 - height / 2;
                rotation = 0; //- 6

                //x = Math.random() * root.width - width / 2
                //y = Math.random() * root.height - height / 2;
                //rotation = Math.random() * 13; //- 6
            }
            Image
            {
                id: image
                anchors.centerIn: parent
                fillMode: Image.PreserveAspectFit
                source:fileDialog.fileUrl
                antialiasing: true
            }
//PinchArea is an invisible object, often used to connect with a //visible object to provide gestures for the corresponding visible objects. PinchArea { anchors.fill: parent pinch.target: photoFrame pinch.minimumRotation: -360 pinch.maximumRotation: 360 pinch.minimumScale: 0.2 pinch.maximumScale: 4 pinch.dragAxis: Pinch.XAndYAxis onPinchStarted: setFrameColor(); property real zRestore: 0 MouseArea { id: dragArea hoverEnabled: true anchors.fill: parent drag.target: photoFrame //scrollGestureEnabled: false // 2-finger-flick gesture should pass through to the Flickable onPressed: { photoFrame.z = ++root.highestZ; parent.setFrameColor(); } onEntered: { parent.setFrameColor(); } onClicked: { } onWheel: { if (wheel.modifiers & Qt.ControlModifier) { photoFrame.rotation += wheel.angleDelta.y / 120 * 5; if (Math.abs(photoFrame.rotation) < 4) photoFrame.rotation = 0; } else { photoFrame.rotation += wheel.angleDelta.x / 120; if (Math.abs(photoFrame.rotation) < 0.6) photoFrame.rotation = 0; var scaleBefore = photoFrame.scale; photoFrame.scale += photoFrame.scale * wheel.angleDelta.y / 120 / 10; } } } function setFrameColor() { if (currentFrame) currentFrame.border.color = "black"; currentFrame = photoFrame; currentFrame.border.color = "yellow"; } }//PinchArea }//Rectangle } Rectangle //slider on the right { id: verticalScrollDecorator objectName: "rightslider"; anchors.right: parent.right anchors.margins: 2 color: "#ecf310" border.color: "#ca0a0a" border.width: 1 width: 10 radius: 2 antialiasing: true height: flick.height * (flick.height / flick.contentHeight) - (width - anchors.margins) * 2 y: flick.contentY * (flick.height / flick.contentHeight) NumberAnimation on opacity { id: vfade; easing.type: Easing.InOutQuint; to: 0; duration: 200 } onYChanged: { opacity = 1.0; fadeTimer.restart() } } Rectangle //slider on the bottom { id: horizontalScrollDecorator objectName: "bottomslider"; anchors.bottom: parent.bottom anchors.margins: 2 color: "#ecf310" border.color: "#ca0a0a" border.width: 1 height: 10 radius: 2 antialiasing: true width: flick.width * (flick.width / flick.contentWidth) - (height - anchors.margins) * 2 x: flick.contentX * (flick.width / flick.contentWidth) NumberAnimation on opacity { id: hfade; to: 0; duration: 500 } onXChanged: { opacity = 1.0; fadeTimer.restart() } } Timer { id: fadeTimer; interval: 800; onTriggered: { hfade.start(); vfade.start() } } //an image button on the left to open the image Image { id:imageBT; anchors.top: parent.top anchors.left: parent.left anchors.margins: 10 source: "qrc:/icons/icos/ninViewerHover.png" MouseArea { hoverEnabled:true; onPressed: { imageBT.source="qrc:/icons/icos/ninViewerpress.png"; } onEntered: { imageBT.source="qrc:/icons/icos/ninViewerpress.png"; } onExited: { imageBT.source="qrc:/icons/icos/ninViewerHover.png"; } anchors.fill: parent anchors.margins: -10 onClicked: { fileDialog.open(); } } } Text { anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right anchors.margins: 10 color: "darkgrey" wrapMode: Text.WordWrap font.pointSize: 8 text: "On a touchscreen: use two fingers to zoom and rotate, one finger to drag\n" + "With a mouse: drag normally, use the vertical wheel to zoom, horizontal wheel to rotate, or hold Ctrl while using the vertical wheel to rotate" } Component.onCompleted: { root.clearSelectedPoint(); } function clearSelectedPoint() { scarletSignal_ClearPoint(); } }
我們去掉了重複器repeater,然後修改了開啟按鈕裡面的程式碼,這樣就只能開啟一張圖片了。具體部分已經用紅色標註了。

然後我們必須獲取影象中滑鼠的位置。後來發現mouseX和mouseY就是滑鼠在影象上的位置,而且不會隨著影象旋轉而改變。這讓我非常高興。然後我們必須在滑鼠的位置上畫一個十字絲,而且是動態新增的。每當我們單擊滑鼠的時候,就在滑鼠位置新增一個十字絲。這在QML中就必須用Loader來實現。必須動態建立物件然後刪除。下面這篇博文已經講的很清楚了。

http://blog.csdn.net/foruok/article/details/32730911

經過學習之後我終於可以利用Loader動態載入十字絲,繪製是自私的程式碼在另外一個QML檔案中實現了。十字絲繪製使用的是QT的Canvas元件。關於Canvas元件請看下面這個部落格:

http://blog.csdn.net/lmhuanying1012/article/details/78178687

看完這些部落格之後你已經有助夠多的知識畫一個十字絲了,下面就是我的十字絲:

import QtQuick 2.0
Item
{
    Canvas
    {
        QtObject
        {
            id: prop //some of the property
            property real crossWirePosX: 0.0;
            property real crossWirePosY: 0.0;
            property real crossWireWidth: 2;
            property real crossWireLength: 15;
        }
        id:transverseCross
        x:prop.crossWirePosX
        y:prop.crossWirePosY
        width: prop.crossWireLength
        height: prop.crossWireLength
        contextType: "2d"
        visible: true
        onPaint:
        {
            context.lineWidth=1;
            context.strokeStyle="red";
            context.fillStyle="red";
            context.beginPath();
            //It is important to notice that the area of the drawing
            //cannot be more than the size of the canvas,
            //or it will not be seen or only a part of it can be seen.
            context.rect((prop.crossWireLength-prop.crossWireWidth)/2,0,
                         prop.crossWireWidth,prop.crossWireLength);
            context.rect(0,(prop.crossWireLength-prop.crossWireWidth)/2,
                         prop.crossWireLength,prop.crossWireWidth);
            context.fill();
            context.stroke();
        }
    }

}
然後我們必須用Loader對十字絲進行動態載入。就用到了Loader。最終的完整QML程式碼如下,只有一個QML檔案:
import QtQuick 2.5
import QtQuick.Dialogs 1.0
import QtQuick.Window 2.1
import Qt.labs.folderlistmodel 1.0
Window
{
    id: root
    objectName: "viewerRoot";
    visible: true
    width: 1024; height: 600
    color: "darkred"

    property int highestZ: 0
    property real defaultSize: 600
    property var currentFrame: undefined
    property real surfaceViewportRatio2: 1.2
    property real frameBorderRatio: 0.0;

    signal scarletSignal_ClearPoint();
    signal scarletSignal_PointSelected(double mouse_X,double mouse_Y);
    signal scarletSignal_SetPath(string ImagePath);
    signal scarletSignal_string(string message)
    signal scarletSignal_int(int message_int)
    signal scarletSignal_double(double message_double)
    //now we try to send some double
    MouseArea
    {
        anchors.fill: parent
        onClicked:
        {
            //interactive1.signal_Signal1();
            //interactive1.function1();
            scarletSignal_string("Ninja Scarlet from QML!");
            scarletSignal_int(666);
            scarletSignal_double(77.77);
        }
    }
    FileDialog
    {
        id: fileDialog
        objectName: "filedialog1";
        title: "Choose a folder with some images"
        // selectFolder: true //if we select folder or file
        onAccepted:
        {
            //folderModel.folder = fileUrl + "/";
            console.log("You chose: " + fileDialog.fileUrl)
            image.source = fileDialog.fileUrl;
            scarletSignal_SetPath(fileDialog.fileUrl);
            //scarletSignal("Ninja Scarlet from QML!");
        }
    }

    function createColorPicker(pos_X,pos_Y)
    {
        if(photoFrame.component == null)
        {
            //photoFrame.component = Qt.createComponent("qrc:/qml/InteractiveTest.qml");
            photoFrame.component = Qt.createComponent("qrc:/qml/Scarlet_Crosshair.qml");
        }
        var crosshair;
        if(photoFrame.component.status == Component.Ready)
        {
            crosshair = photoFrame.component.createObject(
                        photoFrame,
                        {
                            //this is the fixed parameters
                            //"color" : "red",
                            "x" : pos_X-7.5,
                            "y" : pos_Y-7.5
                        });
            photoFrame.dynamicObjects[photoFrame.dynamicObjects.length] = crosshair;
            console.log("add, rootItem.dynamicObject.length = ", photoFrame.dynamicObjects.length);
        }
        photoFrame.count++;
    }

    onScarletSignal_PointSelected:
    {
        console.log("photoFrame Width:",photoFrame.width);
        console.log("SelectX:",mouse_X);
        console.log("SelectY:",mouse_Y);
        createColorPicker(mouse_X,mouse_Y);
        //we load the cross wire here
    }

    //(double mouse_X,double mouse_Y);
    //Provide a small window to display a larger content to the user,
    //and the user can drag and flick on it
    Flickable
    {
        id: flick
        anchors.fill: parent;
        contentWidth: width * surfaceViewportRatio2;//上面定義的Ratio下面可以呼叫,主要是用來控制場景大小
        contentHeight: height * surfaceViewportRatio2;

        Rectangle
        {
            property Component component: null;
            property real count: 0;
            property var dynamicObjects: new Array();

            id: photoFrame
            objectName: "obj";
            color:"yellow";//control the bolder to red
            width: image.width * (1 + frameBorderRatio * image.height / image.width)
            height: image.height * (1.0+frameBorderRatio);
            x:0;
            y:0;
            //scale: defaultSize / Math.max(image.sourceSize.width, image.sourceSize.height)
            scale: 1.0;//defaultSize / Math.max(image.sourceSize.width, image.sourceSize.height)
            Behavior on scale { NumberAnimation { duration: 200 } }
            //Behavior on x { NumberAnimation { easing.amplitude: 1.05; easing.type: Easing.InOutSine; duration: 2000 } }
            Behavior on x { NumberAnimation { easing.amplitude: 1.05; easing.type: Easing.InCirc; duration: 1500 } }
            Behavior on y { NumberAnimation { easing.type: Easing.OutInQuad; duration: 1500 } }
            border.color: "#0ef5da"
            border.width: 3
            smooth: true
            antialiasing: true
            Component.onCompleted: //組建完成後進行運動
            {
                x =0; //root.width/ 2 - width / 2
                y =0;// root.height/ 2 - height / 2;
                rotation = 0; //- 6

                //x = Math.random() * root.width - width / 2
                //y = Math.random() * root.height - height / 2;
                //rotation = Math.random() * 13; //- 6
            }
            Image
            {
                id: image
                anchors.centerIn: parent
                fillMode: Image.PreserveAspectFit
                source:fileDialog.fileUrl
                antialiasing: true
            }
            //PinchArea is an invisible object, often used to connect with a
            //visible object to provide gestures for the corresponding visible objects.
            PinchArea
            {
                anchors.fill: parent
                pinch.target: photoFrame
                pinch.minimumRotation: -360
                pinch.maximumRotation: 360
                pinch.minimumScale: 0.2
                pinch.maximumScale: 4
                pinch.dragAxis: Pinch.XAndYAxis
                onPinchStarted: setFrameColor();
                property real zRestore: 0
                MouseArea
                {
                    id: dragArea
                    hoverEnabled: true
                    anchors.fill: parent
                    drag.target: photoFrame
                    //scrollGestureEnabled: false  // 2-finger-flick gesture should pass through to the Flickable
                    onPressed:
                    {
                        photoFrame.z = ++root.highestZ;
                        parent.setFrameColor();
                    }
                    onEntered:
                    {
                        parent.setFrameColor();
                    }
                    onClicked:
                    {
                        scarletSignal_PointSelected(mouseX,mouseY);
                        //scarletSignal_double(mouseX);
                        //scarletSignal_double(mouseY);
                    }
                    onWheel:
                    {
                        if (wheel.modifiers & Qt.ControlModifier)
                        {
                            photoFrame.rotation += wheel.angleDelta.y / 120 * 5;
                            if (Math.abs(photoFrame.rotation) < 4)
                                photoFrame.rotation = 0;
                        }
                        else
                        {
                            photoFrame.rotation += wheel.angleDelta.x / 120;
                            if (Math.abs(photoFrame.rotation) < 0.6)
                                photoFrame.rotation = 0;
                            var scaleBefore = photoFrame.scale;
                            photoFrame.scale += photoFrame.scale * wheel.angleDelta.y / 120 / 10;
                        }
                    }
                }
                function setFrameColor()
                {
                    if (currentFrame)
                        currentFrame.border.color = "black";
                    currentFrame = photoFrame;
                    currentFrame.border.color = "yellow";
                }
            }//PinchArea
        }//Rectangle

    }

    Rectangle //slider on the right
    {
        id: verticalScrollDecorator
        objectName: "rightslider";
        anchors.right: parent.right
        anchors.margins: 2
        color: "#ecf310"
        border.color: "#ca0a0a"
        border.width: 1
        width: 10
        radius: 2
        antialiasing: true
        height: flick.height * (flick.height / flick.contentHeight) - (width - anchors.margins) * 2
        y:  flick.contentY * (flick.height / flick.contentHeight)
        NumberAnimation on opacity { id: vfade; easing.type: Easing.InOutQuint; to: 0; duration: 200 }
        onYChanged: { opacity = 1.0; fadeTimer.restart() }
    }

    Rectangle //slider on the bottom
    {
        id: horizontalScrollDecorator
        objectName: "bottomslider";
        anchors.bottom: parent.bottom
        anchors.margins: 2
        color: "#ecf310"
        border.color: "#ca0a0a"
        border.width: 1
        height: 10
        radius: 2
        antialiasing: true
        width: flick.width * (flick.width / flick.contentWidth) - (height - anchors.margins) * 2
        x:  flick.contentX * (flick.width / flick.contentWidth)
        NumberAnimation on opacity { id: hfade; to: 0; duration: 500 }
        onXChanged: { opacity = 1.0; fadeTimer.restart() }
    }

    Timer
    {
        id: fadeTimer;
        interval: 800;
        onTriggered:
        {
            hfade.start();
            vfade.start()
        }
    }


    //an image button on the left to open the image
    Image
    {
        id:imageBT;
        anchors.top: parent.top
        anchors.left: parent.left
        anchors.margins: 10
        source: "qrc:/icons/icos/ninViewerHover.png"
        MouseArea
        {
            hoverEnabled:true;
            onPressed:
            {
                imageBT.source="qrc:/icons/icos/ninViewerpress.png";
            }
            onEntered:
            {
                imageBT.source="qrc:/icons/icos/ninViewerpress.png";
            }
            onExited:
            {
                imageBT.source="qrc:/icons/icos/ninViewerHover.png";
            }
            anchors.fill: parent
            anchors.margins: -10
            onClicked:
            {
                fileDialog.open();
            }
        }
    }

    //button on the right to clear the point
    Image
    {
        id:clearPointBT;
        anchors.right: parent.right
        anchors.top: parent.top
        anchors.margins: 10
        source: "qrc:/icons/icos/Clear_Hover.png"
        MouseArea
        {
            hoverEnabled:true;
            onPressed:
            {
                clearPointBT.source="qrc:/icons/icos/Clear_Press.png";
            }
            onEntered:
            {
                clearPointBT.source="qrc:/icons/icos/Clear_Press.png";
            }
            onExited:
            {
                clearPointBT.source="qrc:/icons/icos/Clear_Hover.png";
            }
            anchors.fill: parent
            anchors.margins: -10
            onClicked:
            {
                root.clearSelectedPoint();
                console.log("rootItem.dynamicObject.length = ", photoFrame.dynamicObjects.length);
                while(photoFrame.dynamicObjects.length != 0)
                {
                    var deleted = photoFrame.dynamicObjects.splice(-1, 1);
                    deleted[0].destroy();
                }
                if(photoFrame.dynamicObjects.length > 0)
                {

                }
            }
        }
    }

    Text
    {
        anchors.bottom: parent.bottom
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.margins: 10
        color: "darkgrey"
        wrapMode: Text.WordWrap
        font.pointSize: 8
        text: "On a touchscreen: use two fingers to zoom and rotate, one finger to drag\n" +
              "With a mouse: drag normally, use the vertical wheel to zoom, horizontal wheel to rotate, or hold Ctrl while using the vertical wheel to rotate"
    }

    Component.onCompleted:
    {
        root.clearSelectedPoint();
    }

    function clearSelectedPoint()
    {
        scarletSignal_ClearPoint();
    }
}

很長很長。主要是利用Loader載入complete元件然後在指定位置上繪製出來。然後我們必須把這個嵌入到我們的中心窗體中去:
void Scarlet_CenterWindow::do_Init2DView()
{
    QQmlEngine *engine = new QQmlEngine();
    QQmlComponent *component = new QQmlComponent(engine, QUrl(QStringLiteral("qrc:/qml/Scarlet_ImageViewerTest.qml")));
    //QQmlComponent *component = new QQmlComponent(engine, QUrl(QStringLiteral("qrc:/qml/Scarlet_OrigionViewer.qml")));
    QObject *object = component->create();

    //we find the child and try to get the property
    AnalysistheChild_(object,"rightslider");
    if (QWindow *qml_PhotoPlayerWindow = qobject_cast<QWindow*>(object))
    {
       QWidget *WidgetFromWindow = QWidget::createWindowContainer(qml_PhotoPlayerWindow);
       QGridLayout *GridGLLayout = new QGridLayout();
       GridGLLayout->addWidget(WidgetFromWindow, 0, 0);
       ui->tab_2DView->setLayout(GridGLLayout);
    }
    else
    {
       qDebug()<<"Show qml Wrong!";
    }
    //now we should connect the interactor
    m_nin2DQmlViewInteractor = new Scarlet_2DQMLViewer();
    QObject::connect(object, SIGNAL(scarletSignal_string(QString)),m_nin2DQmlViewInteractor, SLOT(slot_qml_string(QString)));
    QObject::connect(object, SIGNAL(scarletSignal_int(int)),m_nin2DQmlViewInteractor, SLOT(slot_qml_int(int)));
    QObject::connect(object, SIGNAL(scarletSignal_double(double)),m_nin2DQmlViewInteractor, SLOT(slot_qml_double(double)));
    QObject::connect(object, SIGNAL(scarletSignal_PointSelected(double,double)),m_nin2DQmlViewInteractor,SLOT(slot_qml_PointSelected(double,double)));
    QObject::connect(object, SIGNAL(scarletSignal_SetPath(QString)),m_nin2DQmlViewInteractor, SLOT(slot_qml_SetIMGPath(QString)));
}
然後就可以變成這個樣子了:

我們讀取影象的時候也要隨時記錄影象字尾中的焦距資訊。以構成內參矩陣。

分別在點雲和影象上選擇四個點之後利用opencv中的位姿估計演算法完成紋理對映:

double Comput_Reprojection(std::vector<cv::Point3f> object_points, std::vector<cv::Point2f> image_points, cv::Mat K, cv::Mat nin_R, cv::Mat T)
{
    //cout << "轉置也對,狗鈤的很準あ!" << endl;
    double reProjection = 0;
    for (int i = 0; i < object_points.size(); i++)
    {
        cv::Mat SingleP(cv::Matx31d(//這裡必須是Matx31d,如果是f則會報錯。opencv還真是嬌氣
            object_points[i].x,
            object_points[i].y,
            object_points[i].z));
        cv::Mat change = K*(nin_R*SingleP + T);
        change /= change.at<double>(2, 0);
        cout << change.t() << endl;

        cv::Point2f OrigionIMGPoint = image_points[i];
        reProjection += pow(OrigionIMGPoint.x - change.at<double>(0, 0), 2);
        reProjection += pow(OrigionIMGPoint.y - change.at<double>(1, 0), 2);
    }
    reProjection /= (float)object_points.size();
    reProjection = sqrt(reProjection);
    return reProjection;
}

bool Scarlet_CenterWindow::slot_TextureMappingBetween2D_3D()
{
    QVector<Vector2d> Pt2DVec = m_nin2DQmlViewInteractor->get_PtList();
    QVector<Vector3d> Pt3DVec = m_ninGLViewer->get_3DPtList();
    QList<Q_ScarletCloudIO*> StationList = m_ninGLViewer->get_CloudStationList();
    Q_ScarletCloudIO * Station0 = StationList[0];
    QString ImagePath = m_nin2DQmlViewInteractor->get_ImagePath();
    cout<<"Pt2DVec Size:"<<Pt2DVec.size()<<endl;
    cout<<"Pt3DVec Size:"<<Pt3DVec.size()<<endl;
    QFileInfo file(ImagePath);
    if(file.isFile())
    {
        double fmm = Getfmm_FromPath(ImagePath);
        qDebug()<<"now we get the fmm:"<<fmm;
        if(Pt2DVec.size() == Pt3DVec.size()&&
           Pt2DVec.size()>=4&&file.isFile())
        {
            //Dont forget that there is no Chinese path here!
            string imgName = std::string(ImagePath.toStdString());
            cv::Mat IMG = cv::imread(imgName);
            double IMGW = IMG.cols;
            double IMGH = IMG.rows;
            double RealWidth = 35.9;
            double CCDWidth = RealWidth / (IMGW >= IMGH ? IMGW : IMGH);//it must be the max lenth
            double fpixel = fmm / CCDWidth;
            cv::Mat K_intrinsic(cv::Matx33d(
                    fpixel, 0, IMGW / 2.0,
                    0, fpixel, IMGH / 2.0,
                    0, 0, 1));
            //Now we Reload the 3D~2D point;
            std::vector<cv::Point3f> ConP3DVec;
            std::vector<cv::Point2f> ConP2DVec;
            cv::Point3f ConP3D;
            cv::Point2f ConP2D;
            for(int i=0;i<Pt2DVec.size();i++)
            {
                ConP3D.x = Pt3DVec[i](0);
                ConP3D.y = Pt3DVec[i](1);
                ConP3D.z = Pt3DVec[i](2);
                ConP2D.x = Pt2DVec[i](0);
                ConP2D.y = Pt2DVec[i](1);
                ConP3DVec.push_back(ConP3D);
                ConP2DVec.push_back(ConP2D);
                cout << setprecision(10)
                    << ConP3D.x << " " << ConP3D.y << " " << ConP3D.z << " "
                    << ConP2D.x << " " << ConP2D.y << endl;
            }
            cout << "BaseInformation: " << endl;
            cout << "imgName: " << imgName<< endl;
            cout << "width: " << IMGW << endl;
            cout << "height: " << IMGH << endl;
            cout << "CCDWidth: " << CCDWidth << endl;
            cout << "f: " << fmm << endl;
            cout << "fpixel: " << fpixel << endl;
            cout << "KMatrix: " << K_intrinsic << endl;
            //Now we solve the Epnp:
            cv::Mat Rod_r ,TransMatrix ,RotationR;
            bool success = cv::solvePnP(ConP3DVec, ConP2DVec, K_intrinsic,
                                        cv::noArray(), Rod_r, TransMatrix,false, CV_ITERATIVE);

            cv::Rodrigues(Rod_r, RotationR);//Transform the rotation vector into a Rodrigo rotation matrix
            cout << "r:" << endl << Rod_r << endl;
            cout << "R:" << endl << RotationR << endl;
            cout << "T:" << endl << TransMatrix << endl;
            cout << "C(Camera center:):" << endl << -RotationR.inv()*TransMatrix << endl;
            double Reprojection = Comput_Reprojection(ConP3DVec, ConP2DVec, K_intrinsic, RotationR, TransMatrix);
            qDebug()<<"We solve the Epnp , and the reprojection is:"<<Reprojection;

            //next we should Re colorful the cloud
            QPt*ptCloud = Station0->PtCloud().PtCloud;//the cloud
            int ptNum = Station0->PtCloud().PtNum;
            for(int i=0;i<ptNum;i++)
            {
                //Calculate the projection position
                //There must be / / Matx31d
                cv::Mat SingleP(cv::Matx31d(
                    ptCloud[i].x,
                    ptCloud[i].y,
                    ptCloud[i].z));
                cv::Mat change = K_intrinsic*(RotationR*SingleP + TransMatrix);
                change /= change.at<double>(2, 0);
                //cout << change.t() << endl;
                int xPixel = cvRound(change.at<double>(0, 0));
                int yPixel = cvRound(change.at<double>(1, 0));
                //越界判斷,當畫素值在影象範圍內的時候進行賦值,否則按照原始顏色輸出
                if (yPixel < IMG.rows && xPixel < IMG.cols && yPixel >= 0 && xPixel >= 0)
                {
                    uchar* data = IMG.ptr<uchar>(yPixel);//取得顏色
                    int Blue = data[xPixel * 3 + 0]; //第row行的第col個畫素點的第一個通道值 Blue
                    int Green = data[xPixel * 3 + 1]; // Green
                    int Red = data[xPixel * 3 + 2]; // Red
                    Station0->PtCloud().PtCloudColor[i].r = Red;
                    Station0->PtCloud().PtCloudColor[i].g = Green;
                    Station0->PtCloud().PtCloudColor[i].b = Blue;
                }
            }
            QPtColor* PtCloudColor;//the color
            QPtNorm* PtCloudNorm;//the norm
        }
        else
        {
            qDebug()<<"Size is different! Cannot do the TextureMapping!";
            for(int i=0;i<Pt2DVec.size();i++)
                cout<<Pt2DVec[i](0)<<" "<<Pt2DVec[i](1)<<endl;
            for(int i=0;i<Pt3DVec.size();i++)
                cout<<" "<<Pt3DVec[i](0)<<" "<<Pt3DVec[i](1)<<" "<<Pt3DVec[i](2)<<endl;
            return false;
        }

    }


    return true;
}
最終效果如下圖所示:


完整的工程專案已經全部分享,在開頭已經說過了。到此為止。