本文所有源码分享就看我最新的文章,欢迎各位大佬前来交流。
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;
}
最终效果如下图所示:
完整的工程项目已经全部分享,在开头已经说过了。到此为止。