使用QT搭建点云显示框架系列五·基于QT的QML图像选点、动态绘制十字丝功能 ,以及纹理映射

13 篇文章 2 订阅
4 篇文章 1 订阅

本文所有源码分享就看我最新的文章,欢迎各位大佬前来交流。

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;
}
最终效果如下图所示:


完整的工程项目已经全部分享,在开头已经说过了。到此为止。






  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值