使用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;
}
最終效果如下圖所示:
完整的工程專案已經全部分享,在開頭已經說過了。到此為止。