分类目录归档:QWidget

如何将Qt pro工程文件 改成CMakeLists.txt

Qt pro工程管理文件,本人认为是很好用的,语法简洁易懂,但是只能在QtCreator中使用,想用使用其它IDE比如Clion或者vs,CMakeLists是种通用的选择,另外QtCreator的调试功能跟粑粑一样。

一,思路

C++ 中编译,无外乎代码本身的头文件,源文件。三方库的头文件,库文件。Qt本身自带了UI文件和qrc资源文件。再就是宏定义或者路径的配置。

二,Qt pro文件

#需要的Qt模块
QT       += core gui concurrent

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

#C++版本
CONFIG += c++17

#源文件
SOURCES += \
    main.cpp \
    util/DarkStyle.cpp 

#头文件
HEADERS += \
    3rd/snap7/include/snap7.h \
    util/DarkStyle.h 

#UI文件
FORMS += \
    widget.ui \
    widget/configwidget.ui

#翻译文件
TRANSLATIONS += \
    consistency_zh_CN.ts

CONFIG += lrelease
CONFIG += embed_translations

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

#第三方库
INCLUDEPATH += $$PWD/3rd/snap7/include
LIBS += -L$$PWD/3rd/snap7/lib -lsnap7

win32:LIBS += -luser32

#资源文件
RESOURCES += \
    app.qrc \
    resources/darkstyle.qrc

#图标文件
RC_ICONS = app.ico

三,CMakeLists 文件

cmake_minimum_required(VERSION 3.20)
project(Consistency VERSION 1.0.0)

#C++ 版本
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_INCLUDE_CURRENT_DIR ON)

#用来编译ui文件
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

#指定输出目录
set(OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${OUTPUT_PATH})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${OUTPUT_PATH})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${OUTPUT_PATH})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${OUTPUT_PATH})

#需要加载的Qt模块
set(CMAKE_PREFIX_PATH "D:\\Qt\\5.15.2\\msvc2019_64\\lib\\cmake\\Qt5")
find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets Network Concurrent)

#头文件
set(HEADERS
    3rd/snap7/include/snap7.h 
    util/DarkStyle.h 
)

#源文件
set(SOURCES
    main.cpp 
    util/DarkStyle.cpp 
)

#UI文件
set(FORMS
    widget.ui 
    widget/configwidget.ui
)

#资源文件
set(QRC_FILES
    app.qrc
	resources/darkstyle.qrc
)
qt5_add_resources(QRC_HEADERS ${QRC_FILES})

#第三方库
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rd/snap7/include)
file(GLOB Snap7_LIB ${CMAKE_CURRENT_SOURCE_DIR}/3rd/snap7/lib/*.lib)

#生成可执行文件
add_executable(${PROJECT_NAME}
	${HEADERS}
	${SOURCES}
	${FORMS}
	${QRC_HEADERS}
		logo.rc
)

#链接对应的库
target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network Qt5::Concurrent)
target_link_libraries(${PROJECT_NAME} User32.Lib)
target_link_libraries(${PROJECT_NAME}  ${Snap7_LIB})

四,备注

CMake 无法像pro那样处理图标文件,需要先新建一个rc文件,rc文件中指明icon文件,然后加载rc文件。

#logo.rc 文件
IDI_ICON1           ICON   DISCARDABLE  "app.ico"

利用pybullet 实现ur5 机械臂仿真

想做一个机械臂仿真软件,以前利用C++ 结合FCL和OSG实现了一个半成品,但是这种方案需要根据机械臂DH参数,解析出机械臂各关节位姿然后利用OSG渲染,并且需要自己实现逆解算法,过于麻烦。而使用pybullet,上述得大部分功能都有现成的API接口。

仿真软件得几个要素:

(1),渲染引擎 ,需要将模型渲染可视化出来。VTK,OSG或者原生OpenGL。

(2),动力学引擎,根据物体的物理属性计算运动、旋转和碰撞。Bullet或者ODE,或者只用于碰撞检测的FCL。

(3),模型描述,负责机器人模型的建模,描述各机器人的连杆和关节,还有质量属性,颜色等。比如常用的URDF文件和SDF文件。

pybullet 具备了上述所有的要素,动力学引擎使用bullet,渲染使用OpenGL,并可以直接加载URDF文件,而且提供了 正逆算法。

实现逻辑:

1,利用URDF文件,加载模型。

2,给出目标,也就是抓取点位置。

3,利用逆解算法,求出各关节角度。

4,设置各关节角度,并判判断否碰撞。

5,理想情况下,将此组角度过滤,选择下一组不碰撞的数据 发送真实的给机械臂。

代码:

import pybullet as p
import pybullet_data
import time
import math
from collections   import namedtuple
import numpy as np

# 启动仿真引擎的GUI
p.connect(p.GUI)

# 设置重力加速度
p.setGravity(0, 0, -9.81)

# 加载URDF模型路径
p.setAdditionalSearchPath(pybullet_data.getDataPath())

# 设置视角
p.resetDebugVisualizerCamera(cameraDistance=3,
                                    cameraYaw=120,
                                    cameraPitch=-45,
                                    cameraTargetPosition=[0,0,0])

# 加载平面模型作为地面
planeId = p.loadURDF("plane.urdf")

#托盘
trayId=p.loadURDF("tray/traybox.urdf",basePosition=[0.6,0,0.63])

#桌子
tableId=p.loadURDF("table/table.urdf",basePosition=[0.5,0,0])

# 加载ur5 机械臂
robotId  = p.loadURDF("D:\\keiler\\sim\\urdfs\\ur5.urdf",useFixedBase=True )  #basePosition=[0.0,0,0.62]


initial_position = [0, 0, 0.62]  # 设置初始位置为 [0, 0, 0.62] 米
initial_orientation = p.getQuaternionFromEuler([0, 0, 0])  # 设置初始方向为欧拉角 [0, 0, 0] 对应的四元数
p.resetBasePositionAndOrientation(robotId, initial_position, initial_orientation)

# 获取机械臂末端执行器的索引
endEffectorIndex =6

# 获取机械臂的关节数量
numJoints = p.getNumJoints(robotId)
print("关节数量:"+str(numJoints))

# 打印每个关节的信息
for joint_index in range(numJoints):
    joint_info = p.getJointInfo(robotId, joint_index)
    print(f"Joint {joint_index}: {joint_info}")

# 机械臂的初始位置
restingPosition = [0,3.14, -1.57, 1.57, 1.57, 1.57, -1.57, 0]
for jointNumber in range(numJoints):
    p.resetJointState(robotId, jointNumber, restingPosition[jointNumber])
    
    
# 设置圆形的参数
circle_radius = 3
circle_center = [0, 0]
numPoints = 50
angles = [2 * math.pi * float(i) / numPoints for i in range(numPoints)]

#调参控件
target_x = p.addUserDebugParameter("Target X", -10, 10, 0)
target_y = p.addUserDebugParameter("Target Y", -10, 10, 0)
target_z = p.addUserDebugParameter("Target Z", -10, 10, 0)

#开始按钮
button_start = p.addUserDebugParameter("Start", 1, 0, 1)
# 初始状态变量
button_state_prev = 0


#画个球
sphere_visual = p.createVisualShape(shapeType=p.GEOM_SPHERE, radius=0.03, rgbaColor=[1, 0, 0, 1])
sphere_id = p.createMultiBody(baseMass=0, baseCollisionShapeIndex=-1, baseVisualShapeIndex=sphere_visual, basePosition=[0, 0, 1])


#多组解
def calculate_ik_multiple_solutions(robot_id, end_effector_index, target_position, target_orientation, num_solutions=5):
    solutions = []
    for i in range(num_solutions):
        # 生成一个随机的初始关节状态
        random_joint_positions = [np.random.uniform(-np.pi, np.pi) for _ in range(numJoints)]
        # 计算逆运动学
        ik_solution = p.calculateInverseKinematics(robot_id, end_effector_index, target_position, target_orientation, jointDamping=[0.01]*numJoints, lowerLimits=[-np.pi]*numJoints, upperLimits=[np.pi]*numJoints, jointRanges=[2*np.pi]*numJoints, restPoses=random_joint_positions)
        solutions.append(ik_solution)
    return solutions


try:
    while True:
        x = p.readUserDebugParameter(target_x)
        y = p.readUserDebugParameter(target_y)
        z = p.readUserDebugParameter(target_z)
    
        # 更新球的位置
        p.resetBasePositionAndOrientation(sphere_id, [x, y, z], [0, 0, 0, 1])
        
        #判断按钮状态
        button_state = p.readUserDebugParameter(button_start)
        if button_state != button_state_prev:
            print("Button pressed")
            # 使用逆向运动学计算关节角度
            jointPoses = p.calculateInverseKinematics(robotId, endEffectorIndex, [x, y, z],[0, 0, 0, 1])
            
            # 移动机械臂
            p.setJointMotorControl2(bodyIndex=robotId,jointIndex=1,controlMode=p.POSITION_CONTROL,targetPosition=jointPoses[0],targetVelocity=0,force=500,positionGain=0.03,velocityGain=1)
            p.setJointMotorControl2(bodyIndex=robotId,jointIndex=2,controlMode=p.POSITION_CONTROL,targetPosition=jointPoses[1],targetVelocity=0,force=500,positionGain=0.03,velocityGain=1)
            p.setJointMotorControl2(bodyIndex=robotId,jointIndex=3,controlMode=p.POSITION_CONTROL,targetPosition=jointPoses[2],targetVelocity=0,force=500,positionGain=0.03,velocityGain=1)
            p.setJointMotorControl2(bodyIndex=robotId,jointIndex=4,controlMode=p.POSITION_CONTROL,targetPosition=jointPoses[3],targetVelocity=0,force=500,positionGain=0.03,velocityGain=1)
            p.setJointMotorControl2(bodyIndex=robotId,jointIndex=5,controlMode=p.POSITION_CONTROL,targetPosition=jointPoses[4],targetVelocity=0,force=500,positionGain=0.03,velocityGain=1)
            p.setJointMotorControl2(bodyIndex=robotId,jointIndex=6,controlMode=p.POSITION_CONTROL,targetPosition=jointPoses[5],targetVelocity=0,force=500,positionGain=0.03,velocityGain=1)
        button_state_prev = button_state

        
            
        contact_points  = p.getContactPoints(bodyA=trayId,bodyB=robotId)
        isColl =  bool(contact_points)
        if isColl:
            print("撞上了!")
        
                

        p.stepSimulation()
        time.sleep(0.01)

except KeyboardInterrupt:
    # 用户中断程序时,退出循环
    print("Circle drawing interrupted by user.")
# 断开与仿真引擎的连接
p.disconnect()

问题:

1,逆解 只能返回一组关节角度,没法根据碰撞,去规划路径。

2,返回的各关节角度 貌似有偏差。

3,界面二次开发有点困难,控件太少,想封装成一个产品需要结合其它界面框架,比如Qt,有个想法是用pybullet当服务器,PyQt+VTK当客户端,因为这套东西可以无UI运行。

效果: ​

源码:

https://gitcode.com/keiler20181/sim.git

pyqt集成vtk框架

C++环境下,Qt+VTK是一个比较成熟的方案,那python下也可以使用同样的方式也就是PyQt+VTK。

1,VTK安装

pip install vtk

2,Qt安装

参考:

http://qthello.com/index.php/2024/05/01/pyqt/

3,代码

#!/usr/bin/env python3
 
import sys
import vtkmodules.vtkInteractionStyle
import vtkmodules.vtkRenderingOpenGL2
import vtk
from PyQt5 import QtCore, QtGui, QtWidgets
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
from vtkmodules.vtkFiltersSources import vtkCubeSource
from vtkmodules.vtkRenderingCore import (
    vtkActor,
    vtkPolyDataMapper,
    vtkRenderer
)
class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        QtWidgets.QMainWindow.__init__(self, parent)
        self.frame = QtWidgets.QFrame()
        self.vl = QtWidgets.QVBoxLayout()
        self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
        self.vl.addWidget(self.vtkWidget)
        self.ren = vtkRenderer()
        self.vtkWidget.GetRenderWindow().AddRenderer(self.ren)
        self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
        # Create source
        source = vtkCubeSource()
        source.SetCenter(0, 0, 0)
        source.SetXLength(1.0)
        source.SetYLength(1.0)
        source.SetZLength(1.0)
        # Create a mapper
        mapper = vtkPolyDataMapper()
        mapper.SetInputConnection(source.GetOutputPort())
        # Create an actor
        actor = vtkActor()
        actor.SetMapper(mapper)
        self.ren.AddActor(actor)
        self.ren.ResetCamera()
        self.frame.setLayout(self.vl)
        self.setCentralWidget(self.frame)
        self.show()
        self.iren.Initialize()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec_())

4,效果

Qt 在windows下显示中文

Qt在windows平台上显示中文,简直是一门玄学,经过测试,有如下发现:

1,

环境:Qt 5.15.2  vs2019 64位  win11系统

默认用Qt 创建的文件使用utf-8编码格式,此环境下 中文没有问题

ui->textEdit->append("中文测试");

2

某些  低于此版本的qt,上述方式会出现乱码的情况,此时需要将utf-8编码方式的文件转成utf-8 带bom 。比如使用txt进行转换,但是win7 系统的某些版本 txt 不带此格式。

3,总结

1,简单起见,在windows平台,如果只是windwos平台系统,直接将所有文件转成utf-8 带bom 格式,然后中文使用QStringLiteral(“中文”) 即可。

2,全部使用英文 如,tr(“en”),然后再去翻译文件中翻译。

4,转码工具

分享一个自己写得utf-8 和utf-8 带bom 得转码工具。

源码:

https://gitcode.com/keiler20181/codeConvert/overview

PyQt 入门

Python体系下GUI框架也多了去了,PyQt算是比较受欢迎的一个。如果对Qt框架熟悉,那掌握这套框架是很简单的。

一,安装

1.PyQt5

pip3 install PyQt5

2.Designer UI工具

注:这个工具对python版本有要求,貌似只支持到3.9 所以如果python高于这个版本,可以单独安装 QtDesigner工具 (pip3 install PyQt5Designer)

pip3 install PyQt5-tools

3.UI文件转py文件工具。

python下UI文件无法直接使用,需要使用这个工具转成py文件。

注:windows 下我的python版本为3.12 ,自带了这个工具,无需单独安装。

sudo apt-get install pyqt5-dev-tools

二,使用

1.创建UI文件。

使用designer工具创建ui文件。随便放几个控件,然后保存到工程目录下,取名为widget.ui。

我的designer工具位于这里:

 /home/keiler/.local/lib/python3.11/site-packages/qt5_applications/Qt/bin/designer 

2.UI文件转py文件。

pyuic5 -o widget.py   widget.ui

3,主程序加载py文件。

import sys
from PyQt5.QtWidgets import QApplication, QWidget

from widget import Ui_Form

#Ui_Form 为 Ui文件中的类
class MyApp(Ui_Form, QWidget):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

        #信号槽的连接
        self.pushButton.clicked.connect(lambda:print("hello"))
        self.pushButton_2.clicked.connect(self.fun)
        self.show()

    def fun(self):
        self.textEdit.append("good")
 
# 应用程序入口
if __name__ == "__main__":
    app = QApplication(sys.argv)
    my_app = MyApp()
    sys.exit(app.exec_())

4,效果

windows下其它软件的自动打开与点击

一,需求

项目要用到第三方软件,这个软件没有提供SDK,只提供了两个exe,每次开机后需要启动这两个exe,并且还要点击上边的两个按钮。这样的用户体验怎么能让人接受呢,如果查资料,发现windows提供了接口,可以自动化操作。

二,逻辑

三,实现

窗体标题 或者句柄 可以使用 Spy++ 这个工具获取。


#include <iostream>
#include <Windows.h>

//杀死窗口所在的进程
void killEXE(LPCWSTR title) {
HWND hwnd = FindWindow(NULL, title);
if (hwnd != NULL) {
DWORD processId;
GetWindowThreadProcessId(hwnd, &processId);

HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, processId);
if (hProcess != NULL) {
if (TerminateProcess(hProcess, 0)) {
std::cout << "Process terminated successfully!" << std::endl;
}
else {
std::cerr << "Failed to terminate process! Error code: " << GetLastError() << std::endl;
}
CloseHandle(hProcess);
}
else {
std::cerr << "Failed to open process handle!" << std::endl;
}
}
else {
std::cerr << "Window not found!" << std::endl;
}
}

//打开可执行文件
void openEXE(LPCWSTR applicationName) {
LPWSTR commandLine = NULL;
STARTUPINFOW startupInfo;
PROCESS_INFORMATION processInfo;
ZeroMemory(&startupInfo, sizeof(startupInfo));
startupInfo.cb = sizeof(startupInfo);

if (CreateProcessW(applicationName, commandLine, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo)) {
std::cout << "Process created successfully!" << std::endl;
}
else {
std::cerr << "Failed to create process! Error code: " << GetLastError() << std::endl;
}
}

//最小化窗口
void minimizeDialog(LPCWSTR title) {
HWND hwnd = FindWindow(NULL, title);
if (hwnd != NULL) {
ShowWindow(hwnd, SW_MINIMIZE);
std::cout << "Window minimized successfully!" << std::endl;
}
else {
std::cerr << "Window not found!" << std::endl;
}
}

//点击窗口上的按钮
void clickBtn(LPCWSTR title, LPCWSTR btnName) {
HWND hwnd = FindWindow(NULL, title);
if (hwnd != NULL) {
HWND btnHandle = FindWindowEx(hwnd, NULL, L"Button", btnName);
if (btnHandle != NULL) {
SendMessage(btnHandle, BM_CLICK, 0, 0);
SendMessage(btnHandle, BM_CLICK, 0, 0);
}
else {
std::cerr << "Button not found!" << std::endl;
}
}
else {
std::cerr << "Window not found!" << std::endl;
}
}
int main()
{
//关闭进程
killEXE(L"Geomagic Control X Automation (Client)");
killEXE(L"Geomagic Control X Automation (Server)");

//打开客户端进程
openEXE(L"D:\\Program Files\\Oqton\\Geomagic Control X 2024.1\\Geomagic Control X Automation (Client).exe");
Sleep(1000);
//最小化客户端
minimizeDialog(L"Geomagic Control X Automation (Client)");

//打开服务器进程
openEXE(L"D:\\Program Files\\Oqton\\Geomagic Control X 2024.1\\Geomagic Control X Automation (Server).exe");
Sleep(1000);
//点击OK按钮
clickBtn(L"Geomagic Control X Automation (Server) - Mode Select",L"OK");
Sleep(1000);
//点击 Start Server按钮
clickBtn(L"Geomagic Control X Automation (Server)", L"Start Server");
Sleep(1000);
//最小化服务器客户端
minimizeDialog(L"Geomagic Control X Automation (Server)");


}

三,效果(需要管理员权限运行程序)

QWidget qss

QML 中风格和主题的设计可以通过配置文件选择现有几种中的一种,或者直接在控件定义时,指定其属性,如背景颜色或者字体大小。在QWidget框架中,则通过了一种叫做qss样式表的东西来进行描述,跟CSS逻辑上类似。

这个qss抽象出来就两个元素,一个叫做选择器,一个叫做属性。简单的说就是谁长什么样子。那这个谁,有好多种写法,比如所有按钮,某个按钮,按下的按钮。而这个样子又有好多,比如背景色,边框,圆角等。

一,实现

1,新建一个qss文件,并添加到资源里。然后在main函数中,进行全局的设置。

2,字体 一般是一个全局的设置,所以可以在此处设置全局字体。微软雅黑 是一个常用的字体

3,我们以如下的窗口来举例说明。

原始窗口如下:

qss 如下:

//所有QPushButton 背景色为蓝色,宽高为 180 80
QPushButton{
background-color: rgb(0, 0, 255);
width:180px;
height:80px;
}

//所有QPushButton 鼠标hover时 背景为黄色
QPushButton:hover {
background-color: rgb(255, 255, 0);
}

//所有QPushButton flat属性为true 时 背景为绿色
QPushButton[flat="true"] {
background-color: rgb(0, 255, 0);
}

//指定这两个按钮 背景为红色
#pushButton_5,#pushButton_6{
background-color: rgb(255, 0, 0);
}

效果如下:

4,还可以用自己的图片 替换子控件。

/*combobox 的下拉箭头*/
QComboBox::down-arrow {
image: url(:/img/axis_return.png);
}

二,常用属性

三,QSS支持的类型、属性、伪状态、子控件

https://doc.qt.io/qt-5/stylesheet-reference.html

四,总结

属性和控件很多,全背下来没有必要。书写时记住一个逻辑,谁 长什么样子。这个谁可以是全部控件,也可以是某个控件,或者控件的某个状态。哪些样子可以设置,参考文档就行了。

代码:

https://gitcode.com/keiler20181/WidgetStyle.git

五,qt帮助查询

1、“Qt Style Sheets Reference” Qt样式表的用法。

2、“Qt Style Sheets Examples” Qt样式表的实例

3、“The Style Sheet Syntax” Qt样式表语法


C#通过Qt使用VTK

需求:

一个项目,界面是C# 开发的,但是业务上有三维可视化的需求,VTK基于C#的绑定版本需要收费,并且资料很少。因此将VTK嵌入到Qt里,并封装成一个dll,通过接口提供给C#访问。

实现:

一,Qt程序的配置

这里用到了一第三方库(https://github.com/qtproject/qt-solutions/tree/master/qtwinmigrate),它可以将Qt的窗口给C#使用。

1,首先看pro文件,主要是dll编译配置,和第三方库引用及VTK的依赖库。

2,main.cpp

#include "widget.h"
#include <QApplication>

#include <windows.h>
#include <qmfcapp.h>
#include <qwinwidget.h>

// int main(int argc, char *argv[])
// {
// QApplication a(argc, argv);
// Widget w;
// w.show();
// return a.exec();
// }

BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpvReserved*/ )
{
static bool ownApplication = FALSE;

if ( dwReason == DLL_PROCESS_ATTACH )
ownApplication = QMfcApp::pluginInstance( hInstance );
if ( dwReason == DLL_PROCESS_DETACH && ownApplication )
delete qApp;

return TRUE;
}

QWinWidget *win=nullptr;
extern "C" __declspec(dllexport) bool initWindow( HWND parent )
{
if(parent==nullptr)
return false;
win = new QWinWidget(parent);
Widget *widget = new Widget(win);
widget->show();
win->move(0,0);
win->show();

return TRUE;
}

extern "C" __declspec(dllexport) bool destroyWindow()
{
if(win!=0){
win->close();
delete win;
}

return TRUE;
}

3,Widget 写法,可以参考这篇文章(http://qthello.com/index.php/2024/04/11/vtk/)或者看仓库上的源码。

二,C#端

1,引用dll 并调用接口

using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp
{
public partial class Form1 : Form
{
[DllImport("VTKNet.dll", EntryPoint = "initWindow", CharSet = CharSet.Ansi)]
public extern static bool initWindow(IntPtr parent);

[DllImport("VTKNet.dll", EntryPoint = "destroyWindow", CharSet = CharSet.Ansi)]
public extern static bool destroyWindow();

public Form1()
{
InitializeComponent();
//打开窗口
initWindow(this.Handle);
}
}
}

2,exe路径下 需要把自己的dll ,Qt的dll 以及VTK的dll 全部放进去。

效果:

代码:

https://gitcode.com/keiler20181/QtNetVTK.git

利用FCL实现更加精准的碰撞检测

一,需求

利用OSG结合FCL实现实现精准的碰撞检测。

二,效果

三,分析

我们看如下这张图,碰撞的逻辑就是,在一个三维场景中,构造一个实体,比如下边的BoxA,然后在物理引擎比如bullet中,或者专用的碰撞检测库中也构造一个对应的实体,比如BoxB。之后在BoxA位姿改变时后,将BoB的位姿也做相应的更新。之后发生碰撞时,物理引擎或者FCL就会给出信号。而这个场景,可以是VTK或者OSG。而碰撞检测可以用Bullet也可以用FCL。

之前用bullet做个尝试,基本的图形能满足需求,比如球,盒子,但是项目中涉及到点云的碰撞,而bullet中处理点云,没有找到好的处理方式。但是FCL可以将点云转变成fcl中对应的实体,因此最终选择了FCL进行碰撞检测,这里列出FCL中大概的步骤。

1,FCL中构造实体。这里构造了一个 盒子。

auto box_geometry = std::make_shared<fcl::Boxf>(w, d, h);
auto ob = new fcl::CollisionObjectf(box_geometry);

2,更新FCL实体的位姿矩阵。

void FCLManager::updateTrans(const std::string &name, const fcl::Transform3f &trans)
{
fcl::CollisionObjectf *ob=getCollisionObject(name);
if(ob){
ob->setTransform(trans);
}
}

//OSG 矩阵 需要进行转换 才能给到FCL使用
//osg 矩阵转fcl矩阵
osg::Vec3 osgTrans = mt.getTrans(); // 获取平移分量
osg::Quat osgQuat = mt.getRotate(); // 获取旋转分量

fcl::Quaternionf rotation(osgQuat.w(), osgQuat.x(), osgQuat.y(), osgQuat.z());
fcl::Vector3f translation(osgTrans.x(), osgTrans.y(), osgTrans.z());
fcl::Transform3f fclTrans=fcl::Transform3f::Identity();
fclTrans.translation() = translation;
fclTrans.linear()=rotation.toRotationMatrix();
FCLManager::getInstance()->updateTrans(this->getName(),fclTrans);

3,碰撞检测

我是检测机器人和其它障碍物的碰撞,这里把机器人关节放到一个集合中,把其它障碍物放到另一个集合中

 
bool FCLManager::detectCollision()
{
fcl::CollisionRequestf request;
fcl::CollisionResultf result;
for(auto &ob1:jointMap){
for(auto &ob2:obstacleMap){
collide(ob1.second, ob2.second, request, result);
if(result.isCollision()){
return true;
}
}
}
return false;
}

4,FCL支持三角面检测。因此我们在FCL中构造对应实体的时候,可以直接用三角面。这样不管OSG中构造的时盒子还是球,还是导入的stl,对应FCL中都是统一用三角面处理。

void FCLManager::addTriMesh(const std::string &name, osg::Node *node)
{
fcl::CollisionObjectf *obj = createNodeCollisionObject(node);
obstacleMap.emplace(name,obj);
}

fcl::CollisionObjectf *FCLManager::createNodeCollisionObject(osg::Node *node)
{
MyComputeTriMeshVisitor visitor;
node->accept( visitor );
osg::Vec3Array* vertices = visitor.getTriMesh();

typedef fcl::BVHModel<fcl::OBBRSSf> Model;
Model* model = new Model();
std::shared_ptr<fcl::CollisionGeometryf> m1_ptr(model);
model->beginModel();
osg::Vec3 p1, p2, p3;
for( size_t i = 0; i + 2 < vertices->size(); i += 3 )
{
p1 = vertices->at( i );
p2 = vertices->at( i + 1 );
p3 = vertices->at( i + 2 );

fcl::Vector3<float> pp1{p1.x(),p1.y(),p1.z()};
fcl::Vector3<float> pp2{p2.x(),p2.y(),p2.z()};
fcl::Vector3<float> pp3{p3.x(),p3.y(),p3.z()};

model->addTriangle(pp1, pp2, pp3);
}
model->endModel();
model->computeLocalAABB();

return new fcl::CollisionObjectf(m1_ptr);
}

5,点云的碰撞。点云的碰撞 使用了一种叫做八叉树的算法。首先将点云转成pcl的点云 格式,然后可以直接构造出fcl实体,这也是选用FCL的原因。

fcl::CollisionObjectf* FCLManager::createPointCloudCollisionObject(const pcl::PointCloud<pcl::PointXYZ>::Ptr pointcloud_ptr, const octomap::point3d &origin_3d)
{
// octomap octree settings
const double resolution = 0.01;
const double prob_hit = 0.9;
const double prob_miss = 0.1;
const double clamping_thres_min = 0.12;
const double clamping_thres_max = 0.98;

std::shared_ptr<octomap::OcTree> octomap_octree = std::make_shared<octomap::OcTree>(resolution);
octomap_octree->setProbHit(prob_hit);
octomap_octree->setProbMiss(prob_miss);
octomap_octree->setClampingThresMin(clamping_thres_min);
octomap_octree->setClampingThresMax(clamping_thres_max);

octomap::KeySet free_cells;
octomap::KeySet occupied_cells;

#if defined(_OPENMP)
#pragma omp parallel
#endif
{
#if defined(_OPENMP)
auto thread_id = omp_get_thread_num();
auto thread_num = omp_get_num_threads();
#else
int thread_id = 0;
int thread_num = 1;
#endif
int start_idx = static_cast<int>(pointcloud_ptr->size() / thread_num) * thread_id;
int end_idx = static_cast<int>(pointcloud_ptr->size() / thread_num) * (thread_id + 1);
if (thread_id == thread_num - 1)
{
end_idx = pointcloud_ptr->size();
}

octomap::KeySet local_free_cells;
octomap::KeySet local_occupied_cells;

for (auto i = start_idx; i < end_idx; i++)
{
octomap::point3d point((*pointcloud_ptr)[i].x, (*pointcloud_ptr)[i].y, (*pointcloud_ptr)[i].z);
octomap::KeyRay key_ray;
if (octomap_octree->computeRayKeys(origin_3d, point, key_ray))
{
local_free_cells.insert(key_ray.begin(), key_ray.end());
}

octomap::OcTreeKey tree_key;
if (octomap_octree->coordToKeyChecked(point, tree_key))
{
local_occupied_cells.insert(tree_key);
}
}

#if defined(_OPENMP)
#pragma omp critical
#endif
{
free_cells.insert(local_free_cells.begin(), local_free_cells.end());
occupied_cells.insert(local_occupied_cells.begin(), local_occupied_cells.end());
}
}

// free cells only if not occupied in this cloud
for (auto it = free_cells.begin(); it != free_cells.end(); ++it)
{
if (occupied_cells.find(*it) == occupied_cells.end())
{
octomap_octree->updateNode(*it, false);
}
}

// occupied cells
for (auto it = occupied_cells.begin(); it != occupied_cells.end(); ++it)
{
octomap_octree->updateNode(*it, true);
}

auto fcl_octree = std::make_shared<fcl::OcTree<float>>(octomap_octree);
std::shared_ptr<fcl::CollisionGeometryf> fcl_geometry = fcl_octree;
return new fcl::CollisionObjectf(fcl_geometry);
}

四,总结

OSG结合FCL实现碰撞的检测,首先将OSG中的Node 转换成FCL中的三角面。然后将点云用上述方式进行转换。最后调用result.isCollision()函数 来判断两个实体是否发生碰撞。

https://github.com/flexible-collision-library/fcl

利用Qt自带的媒体模块播放mp4文件

一. 需求

利用Qt自带的媒体模块,播放mp4等媒体文件。

二. 实现

  • pro文件修改
QT       += core gui multimedia  multimediawidgets
  • ui中放一个QWidget,并提升为QVideoWidget.
  • widget.h 文件。
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

#include <QMediaPlayer>
#include <QVideoWidget>
#include <QMediaPlaylist>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_fileBtn_clicked();

private:
    Ui::Widget *ui;
    QMediaPlayer* mediaPlayer;
    QMediaPlaylist* mediaPlayList;
};
#endif // WIDGET_H
  • widget.cpp 文件
#include "widget.h"
#include "ui_widget.h"

#include <QFileDialog>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    mediaPlayer = new QMediaPlayer(this);
    mediaPlayList = new QMediaPlaylist(mediaPlayer);
    mediaPlayer->setVideoOutput(ui->widget);
}

Widget::~Widget()
{
    delete ui;
}


void Widget::on_fileBtn_clicked()
{
    QString file = QFileDialog::getOpenFileName(this,"open file",".","*.*");
    if(file.isEmpty())
        return;

    mediaPlayList->clear();
    mediaPlayList->addMedia(QUrl::fromLocalFile(file));
    mediaPlayList->setCurrentIndex(0);
    mediaPlayer->setPlaylist(mediaPlayList);
    mediaPlayer->play();
}
  • 涉及到媒体,就涉及到编解码,所以就需要安装编解码器,直接安装这个即可:

https://www.codecguide.com/download_kl.htm

不然会出现如下错误:  DirectShowPlayerService::doRender: Unresolved error code 0x80040266 

三. 效果

利用VTK 包围盒实现简单的碰撞检测

一. 需求

利用VTK自带的包围盒实现简单的碰撞检测。

二. 实现

1, VTK的碰撞检测原理就是 求物体的包围盒,然后检测包围盒与线、面、或者另一个包围盒是否相交。包围盒主要包括球体、轴对齐包围盒(AABB)有向包围盒(OBB)凸包(Convex Hull),参考(VTK笔记——轴对齐包围盒(AABB) – 灰信网(软件开发博客聚合)

2,VTK自带了一个vtkCollisionDetectionFilter过滤器,来计算碰撞检测,此过滤器利用的就是OBB包围盒,使用简单,只需提供两个物体的数据集和矩阵即可。

3,实现的时候 利用一个结构体,来定义角色,矩阵和包围盒。这样便于计算。

struct  SimulateActor
{
    using Ptr =  QSharedPointer<SimulateActor>;
    vtkSmartPointer<vtkActor> Actor;
    vtkSmartPointer<vtkTransform> Trans;
    vtkSmartPointer<vtkPolyData> PolyData;
    vtkSmartPointer<vtkCollisionDetectionFilter> Collide;
};

4,先初始化之后所有的物体。然后利用两个循环来依次判断每两个物体是否发生碰撞。

5,不足

(1),此过滤器只能检测两个物体是否发生碰撞,无奈当前只能通过两个循环来进行遍历。

(2),没有找到如何将多个actor组合成一个,然后进行碰撞检测的方法,如果能组合成一个效率会提高很多。

(3),目前六个部件,与50个障碍物进行碰撞检测。检测一次的时间大约50ms。

VTK 可移动三维坐标轴

一. 需求

利用VTK实现一个可移动,可旋转的三维坐标轴。并通过回调获取其位姿。

二. 效果

三. 步骤

  1. 绘制轴和圈
vtkMovableAxesRepresentation::vtkMovableAxesRepresentation()
{
  // The initial state
  this->InteractionState = vtkMovableAxesRepresentation::Outside;
 
  // Handle size is in pixels for this widget
  this->HandleSize = 5.0;
 
  // Control orientation of normals
  this->InsideOut = 0;
 
  // Set up the initial properties
  this->CreateDefaultProperties();
 
  // Define the point coordinates
  double bounds[6];
  bounds[0] = -0.5;
  bounds[1] = 0.5;
  bounds[2] = -0.5;
  bounds[3] = 0.5;
  bounds[4] = -0.5;
  bounds[5] = 0.5;
  // Points 8-14 are down by PositionHandles();
  this->BoundingBox = vtkBox::New();
  this->PlaceWidget(bounds);
 
  //画 圆
  this->AxisCircel = new vtkActor* [3];
  this->AxisPolygonSource = new vtkRegularPolygonSource* [3];
  this->AxisCircleMapper = new vtkPolyDataMapper*[3];
  for(auto i=0;i<3;i++){
      this->AxisCircel[i]=vtkActor::New();
      this->AxisPolygonSource[i] = vtkRegularPolygonSource::New();
      this->AxisCircleMapper[i] = vtkPolyDataMapper::New();
 
      this->AxisPolygonSource[i]->SetNumberOfSides(100);    //多边形边数
      this->AxisPolygonSource[i]->SetRadius(g_circleRadius);       //半径
      this->AxisPolygonSource[i]->SetCenter(0, 0, 0);      //圆心
 
      this->AxisCircleMapper[i]->SetInputConnection(this->AxisPolygonSource[i]->GetOutputPort());
      this->AxisCircel[i]->SetMapper(this->AxisCircleMapper[i]);
      this->AxisCircel[i]->GetProperty()->SetOpacity(1);    //透明度
      this->AxisCircel[i]->GetProperty()->SetLineWidth(g_circleLineWidth);
      this->AxisCircel[i]->GetProperty()->SetRepresentationToWireframe();//图形不填充,只要边框
      this->AxisCircel[i]->GetProperty()->SetColor(g_normalColor[i][0],g_normalColor[i][1],g_normalColor[i][2]);    //颜色
 
 
      vtkNew<vtkMatrix4x4> initMatrix;
      this->AxisCircel[i]->SetUserMatrix(initMatrix);
  }
  //圆形拾取
  this->CircelPicker = vtkCellPicker::New();
  this->CircelPicker->SetTolerance(0.001);
  for(int i=0;i<3;i++){
      this->CircelPicker->AddPickList(this->AxisCircel[i]);
  }
  this->CircelPicker->PickFromListOn();
  this->CurrentCircel=nullptr;
 
  //轴
  this->HandleAxis = new vtkAssembly* [3];
  for (int i=0; i<3; i++)
  {
      vtkNew<vtkLineSource> lineSource;
      vtkNew<vtkPolyDataMapper> lineMapper;
      vtkNew<vtkActor> lineActor;
 
      lineMapper->SetInputConnection(lineSource->GetOutputPort());
      lineActor->SetMapper(lineMapper);
 
      double point1[3];
      double point2[3];
      if(i==0){//x轴
          point1[0]=-g_axisLength;
          point1[1]=0;
          point1[2]=0;
 
          point2[0]=g_axisLength;
          point2[1]=0;
          point2[2]=0;
      }else if(i==1){//y轴
          point1[0]=0;
          point1[1]=-g_axisLength;
          point1[2]=0;
 
          point2[0]=0;
          point2[1]=g_axisLength;
          point2[2]=0;
      }else if(i==2){//z轴
          point1[0]=0;
          point1[1]=0;
          point1[2]=-g_axisLength;
 
          point2[0]=0;
          point2[1]=0;
          point2[2]=g_axisLength;
      }
      lineSource->SetPoint1(point1);
      lineSource->SetPoint2(point2);
      lineActor->GetProperty()->SetColor(g_normalColor[i][0],g_normalColor[i][1],g_normalColor[i][2]);
      lineActor->GetProperty()->SetLineWidth(g_axisLineWidth);
 
      //箭头
      vtkNew<vtkConeSource> coneSource1;
      vtkNew<vtkPolyDataMapper> coneMapper1;
      vtkNew<vtkActor> coneActor1;
      coneSource1->SetHeight( 0.2 );
      coneSource1->SetRadius( 0.04 );
      coneSource1->SetResolution( 10 );
      coneMapper1->SetInputConnection(coneSource1->GetOutputPort());
      coneActor1->SetMapper(coneMapper1);
 
      coneActor1->GetProperty()->SetColor(g_normalColor[i][0],g_normalColor[i][1],g_normalColor[i][2]);
      coneSource1->SetCenter(point2);
 
      if(i==0){//x轴
 
      }else if(i==1){//y轴
          coneActor1->SetOrigin(point2);
          coneActor1->RotateZ(90);
      }else if(i==2){//z轴
          coneActor1->SetOrigin(point2);
          coneActor1->RotateY(-90);
      }
      //
      vtkNew<vtkConeSource> coneSource2;
      vtkNew<vtkPolyDataMapper> coneMapper2;
      vtkNew<vtkActor> coneActor2;
      coneSource2->SetHeight( 0.2 );
      coneSource2->SetRadius( 0.04 );
      coneSource2->SetResolution( 30 );
      coneMapper2->SetInputConnection(coneSource2->GetOutputPort());
      coneActor2->SetMapper(coneMapper2);
 
      coneActor2->GetProperty()->SetColor(g_normalColor[i][0],g_normalColor[i][1],g_normalColor[i][2]);
      coneSource2->SetCenter(point1);
 
      if(i==0){//x轴
          coneActor2->SetOrigin(point1);
          coneActor2->RotateY(180);
 
          this->AxisPolygonSource[i]->SetNormal(1,0,0);
      }else if(i==1){//y轴
          coneActor2->SetOrigin(point1);
          coneActor2->RotateZ(-90);
 
          this->AxisPolygonSource[i]->SetNormal(0,1,0);
      }else if(i==2){//z轴
          coneActor2->SetOrigin(point1);
          coneActor2->RotateY(90);
 
          this->AxisPolygonSource[i]->SetNormal(0,0,1);
      }
 
      this->HandleAxis[i] = vtkAssembly::New();
      this->HandleAxis[i]->AddPart(lineActor);
      this->HandleAxis[i]->AddPart(coneActor1);
      this->HandleAxis[i]->AddPart(coneActor2);
 
      vtkNew<vtkMatrix4x4> initMatrix;
      this->HandleAxis[i]->SetUserMatrix(initMatrix);
  }
 
  //坐标轴拾取
  this->AxisPicker = vtkCellPicker::New();
  this->AxisPicker->SetTolerance(0.001);
  for (int i=0; i<3; i++)
  {
      this->AxisPicker->AddPickList(this->HandleAxis[i]);
  }
  this->AxisPicker->PickFromListOn();
  //
 
  // Internal data members for performance
  this->Transform = vtkTransform::New();
  this->PlanePoints = vtkPoints::New(VTK_DOUBLE);
  this->PlanePoints->SetNumberOfPoints(6);
  this->PlaneNormals = vtkDoubleArray::New();
  this->PlaneNormals->SetNumberOfComponents(3);
  this->PlaneNormals->SetNumberOfTuples(6);
  this->Matrix = vtkMatrix4x4::New();
 
}

2. 实现轴和圈的互动,以旋转为例。

void vtkMovableAxesRepresentation::Rotate(int X, int Y, double *p1, double *p2, double *vpn, int singleAxis)
{
    for(int i=0;i<3;i++){
        vtkMatrix4x4 *origin_matrixCircle = this->AxisCircel[i]->GetUserMatrix();
        vtkNew<vtkMatrix4x4> result_matrixCircle;
        RotateByMatrix(origin_matrixCircle,p1,p2,singleAxis,result_matrixCircle);
        this->AxisCircel[i]->SetUserMatrix(result_matrixCircle);
 
        vtkMatrix4x4 *origin_matrixAxis = this->HandleAxis[i]->GetUserMatrix();
        vtkNew<vtkMatrix4x4> result_matrixAxis;
        RotateByMatrix(origin_matrixAxis,p1,p2,singleAxis,result_matrixAxis);
        this->HandleAxis[i]->SetUserMatrix(result_matrixAxis);
    }
    this->Modified();
    this->BuildRepresentation();
}
void vtkMovableAxesRepresentation::RotateByMatrix(vtkMatrix4x4 *origin_matrix, double *p1, double *p2,int direction,vtkMatrix4x4* result_matrix)
{
    // 将鼠标位置移动到自身坐标系下,求两次鼠标位置向量在投影平面的夹角
    vtkNew<vtkTransform> trans;
    trans->SetMatrix(origin_matrix);
    double pos_t1[4] { p1[0], p1[1], p1[2], 1 };
    double pos_t2[4] { p2[0], p2[1], p2[2], 1 };
    vtkNew<vtkMatrix4x4> posture_inv;
    vtkMatrix4x4::Invert(origin_matrix, posture_inv);
    auto pos_t = posture_inv->MultiplyDoublePoint(pos_t1);
    double v1[3] = { pos_t[0], pos_t[1], pos_t[2] };
    pos_t = posture_inv->MultiplyDoublePoint(pos_t2);
    double v2[3] = { pos_t[0], pos_t[1], pos_t[2] };
    double normal[3];
 
    if(direction==0){
        normal[0] = 1;
        normal[1] = 0;
        normal[2] = 0;
 
    }else if(direction==1){
        normal[0] = 0;
        normal[1] = 1;
        normal[2] = 0;
    }else if(direction==2){
        normal[0] = 0;
        normal[1] = 0;
        normal[2] = 1;
    }
    double projection1[3], projection2[3];
    GetPlaneProjection(normal, v1, projection1);
    GetPlaneProjection(normal, v2, projection2);
    vtkMath::Normalize(projection1);
    vtkMath::Normalize(projection2);
    double axis[3];
    vtkMath::Cross(projection1, projection2, axis);
    double radians = acos(vtkMath::Dot(projection1, projection2));
    double degrees = vtkMath::DegreesFromRadians(radians);
    trans->RotateWXYZ(degrees, axis);
    result_matrix->DeepCopy(trans->GetMatrix());
}

3,感谢网友的分享。

https://blog.csdn.net/a15005784320/article/details/122096432

利用QImage 显示TIFF格式图片

一,需求

利用Qt 控件 显示 tiff 图片,由于tiff图像深度位96位,3通道,所以无法直接用QImage 显示,QImage 支持24位,因此需要利用Opencv 进行转换。

二,关键点

  • 96位 深度需要利用 IMREAD_UNCHANGED 模式进行加载
Mat image2Draw_mat = imread("depth.tiff",cv::ImreadModes::IMREAD_UNCHANGED);
  • 加载后进行 规一化,然后进行位深 转换,将32f 转成8u
Mat normalize_mat;
normalize(image2Draw_mat, normalize_mat, 0, 500, NORM_MINMAX);
normalize_mat.convertTo(normalize_mat, CV_8U);
  • 因为 opencv 是 bgr格式,所以需要将bgr转成 rgb
cv::cvtColor(normalize_mat, normalize_mat, COLOR_BGR2RGB);
  • 因为原图是3通道,所以qt这边使用 QImage::Format_RGB888
const uchar pSrc = (const uchar)image2Draw_mat->data;
image2Draw_qt = QImage(pSrc, image2Draw_mat->cols,
image2Draw_mat->rows,image2Draw_mat->step,QImage::Format_RGB888);
  • 最后建议使用 QGraphicsView 显示 QImage 这样可以进行缩放,更利用观看。

三,位深度的理解

以3通道图像为例,位深度 显示96位,意思是 一个像素,可以显示的颜色范围为2的96次方种,每个通道32位,也就是4个字节。 而Qt Image 最多支持24位,也就是每个通道位8位 一个字节。因此需要将mat 进行转换(normalize_mat.convertTo(normalize_mat, CV_8U)) 把32转成8。之后就可以利用Qt QImage::Format_RGB888 进行加载了。

集成VTK

一,需求
集成VTK环境到Qt中
二,步骤

  1. 下载vtk源码,并使用cmake 结合vs2019编辑成dll库。
    https://github.com/Kitware/VTK
  2. Qt工程引入,库比较多,偷懒的情况下全部引入。
    pro文件配置如下:
    
    #VTK
    INCLUDEPATH += $PWD/3rd/VTK_8.2/include/vtk-8.2
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkChartsCore-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkCommonColor-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkCommonComputationalGeometry-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkCommonCore-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkCommonDataModel-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkCommonExecutionModel-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkCommonMath-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkCommonMisc-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkCommonSystem-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkCommonTransforms-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkDICOMParser-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkDomainsChemistry-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkDomainsChemistryOpenGL2-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkexpat-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersAMR-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersCore-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersExtraction-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersFlowPaths-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersGeneral-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersGeneric-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersGeometry-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersHybrid-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersHyperTree-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersImaging-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersModeling-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersParallel-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersParallelImaging-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersProgrammable-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersSelection-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersSMP-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersSources-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersStatistics-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersTexture-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkFiltersVerdict-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkfreetype-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkGeovisCore-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkglew-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkGUISupportQt-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkGUISupportQtSQL-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkhdf5-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkhdf5_hl-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkImagingColor-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkImagingCore-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkImagingFourier-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkImagingGeneral-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkImagingHybrid-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkImagingMath-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkImagingMorphological-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkImagingSources-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkImagingStatistics-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkImagingStencil-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkInfovisCore-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkInfovisLayout-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkInteractionImage-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkInteractionStyle-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkInteractionWidgets-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOAMR-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOCore-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOEnSight-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOExodus-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOExport-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOGeometry-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOImage-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOImport-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOInfovis-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOLegacy-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOLSDyna-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOMINC-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOMovie-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIONetCDF-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOParallel-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOParallelXML-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOPLY-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOSQL-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOVideo-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOXML-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkIOXMLParser-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkjpeg-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkjsoncpp-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtklibxml2-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkmetaio-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkNetCDF-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkParallelCore-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkpng-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkproj-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkRenderingAnnotation-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkRenderingContext2D-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkRenderingContextOpenGL2-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkRenderingCore-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkRenderingFreeType-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkRenderingImage-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkRenderingLabel-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkRenderingLOD-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkRenderingOpenGL2-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkRenderingQt-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkRenderingVolume-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkRenderingVolumeOpenGL2-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtksqlite-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtksys-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtktiff-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkverdict-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkViewsContext2D-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkViewsCore-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkViewsInfovis-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkViewsQt-8.2.lib
    LIBS +=$PWD/3rd/VTK_8.2/lib/vtkzlib-8.2.lib

3. UI文件中,创建一个QWidget,并将其提升为QVTKWidget.

4. 我们以显示一个stl文件为例。

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkSmartPointer.h>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;

    vtkSmartPointer<vtkRenderer> Renderer;
    vtkSmartPointer<vtkRenderWindow> RenderWindow;
};
#endif // WIDGET_H

#include <vtkSTLReader.h>
#include <vtkPolyDataMapper.h>
#include <vtkAutoInit.h>

VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);
VTK_MODULE_INIT(vtkRenderingFreeType);


Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);


    RenderWindow=vtkSmartPointer<vtkRenderWindow>::New();
    Renderer = vtkSmartPointer<vtkRenderer>::New();
    Renderer->SetBackground(0.2, 0.2, 0.2);
    RenderWindow->AddRenderer(Renderer);
    ui->widget->SetRenderWindow(RenderWindow);


    std::string file =  "C:\\Users\\keiler\\Desktop\\file\\you-hou-che-men.stl";
    vtkSmartPointer<vtkSTLReader> reader = vtkSmartPointer<vtkSTLReader>::New();
    reader->SetFileName(file.c_str());
    reader->Update();

    vtkSmartPointer<vtkPolyDataMapper> mapper =    vtkSmartPointer<vtkPolyDataMapper>::New();
    mapper->SetInputConnection(reader->GetOutputPort());

    vtkNew<vtkActor> partActor;
    partActor->SetMapper(mapper);
    Renderer->AddActor(partActor);
}

Widget::~Widget()
{
    delete ui;
}

5. 效果

6. 备注

Qt的debug模式需要加载VTK的debug库,同样 release模式需要加载release库。不然会出现这个错误“QWidget: Must construct a QApplication before a QWidget”。

集成OSG

一,新建一个 QOsgWidget 类,继承自osgQOpenGLWidget

#ifndef QOSGWIDGET_H
#define QOSGWIDGET_H

#include <QObject>
#include <osgViewer/Viewer>
#include <osgQOpenGL/osgQOpenGLWidget>
class QOsgWidget: public osgQOpenGLWidget
{
public:
explicit QOsgWidget(QWidget *parent = nullptr);
QSize sizeHint() const;

void InitQOsgWidget();

};

#endif // QOSGWIDGET_H
#include "qosgwidget.h"
#include <QDebug>
QOsgWidget::QOsgWidget(QWidget *parent):osgQOpenGLWidget(parent)
{
//多重采样
QSurfaceFormat surfaceFormat;
surfaceFormat.setSamples(6);
setFormat(surfaceFormat);
}

QSize QOsgWidget::sizeHint() const
{
return QSize(this->width(),this->height());
}

二,UI中创建一个QWidget 并提升为QOsgWidget

三,主窗初始化这个QOsgWidget。 这里以一个 网格 节点为例。

#ifndef WIDGET_H
#define WIDGET_H

#include <osg/Node>
#include <QWidget>
#include "qosgwidget.h"

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
Q_OBJECT

public:
Widget(QWidget *parent = nullptr);
~Widget();

private:
Ui::Widget *ui;

void InitQOsgWidget();

osg::ref_ptr<osg::Group> _Root = nullptr;
osg::ref_ptr<osgViewer::Viewer> _Viewer = nullptr;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"

#include <osgGA/TrackballManipulator>
#include <osgUtil/Optimizer>
#include <osgGA/GUIEventHandler>
#include <osgViewer/ViewerEventHandlers>

Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);

connect(ui->widget, &osgQOpenGLWidget::initialized, this, &Widget::InitQOsgWidget);
}

Widget::~Widget()
{
delete ui;
}

void Widget::InitQOsgWidget()
{
_Root = new osg::Group();
QSize size = ui->widget->size();

osg::ref_ptr<osgGA::TrackballManipulator> trackball = new osgGA::TrackballManipulator;
trackball->setAllowThrow(false);
trackball->setAutoComputeHomePosition(true);
trackball->setThreadSafeRefUnref(true);

osgUtil::Optimizer optimizer;
optimizer.optimize(_Root.get());
_Viewer = ui->widget->getOsgViewer();

_Viewer->addEventHandler(new osgViewer::StatsHandler);
_Viewer->getCamera()->setClearColor(osg::Vec4(0.2, 0.2, 0.2, 1));
_Viewer->getCamera()->setProjectionMatrixAsPerspective(30.0f, static_cast<double>(size.width())/static_cast<double>(size.height()), 1.0f, 10000.0f );
_Viewer->setCameraManipulator(trackball);
_Viewer->setRunMaxFrameRate(60);


osg::ref_ptr<osg::Geode> grid = new osg::Geode;
osg::Geometry* geom = new osg::Geometry;
grid->addChild(geom);

osg::Vec3Array* vertex = new osg::Vec3Array;
geom->setVertexArray(vertex);
//沿xy平面画线,间隔500米,从-10000,画到100000
for (int i = -10; i <= 10; i += 1)
{
vertex->push_back(osg::Vec3(i, -10, 0));
vertex->push_back(osg::Vec3(i, 10, 0));
vertex->push_back(osg::Vec3(-10, i, 0));
vertex->push_back(osg::Vec3(10, i, 0));
}
geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINES, 0, vertex->size()));
osg::Vec4Array* color = new osg::Vec4Array();
color->push_back(osg::Vec4(0.7, 0.7, 0.7, 1.0));
geom->setColorArray(color, osg::Array::BIND_OVERALL);
geom->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
_Root->addChild(grid);

_Viewer->setSceneData(_Root.get());
}

四,效果

利用QGraphicsPixmapItem 渲染视频

一,需求

将相机的视频渲染到QGraphicsPixmapItem 里,这样可以利用QGraphicsView这套系统在上边进行绘图

二,实现

  • 利用单独的一个线程取图,并转换为QImage后,放到队列里。然后激发信号,槽函数里,从队列取图,转成QPixmap后设置到QGraphicsPixmapItem上。
#ifndef MYCAMERA_H
#define MYCAMERA_H

#include <QObject>

#include <QObject>
#include <QImage>
#include <opencv2/opencv.hpp>
#include safequeue.h

class MyCamera : public QObject
{
    Q_OBJECT
public:
    explicit MyCamera(QObject *parent = nullptr);
    virtual ~MyCamera(){}

    QImage getImage();

    virtual void init()=0;
protected:
    //线程安全的队列 存入图片
    SafeQueue<QImage> imageQueue{1};

signals:
    void capture();
};

#endif // MYCAMERA_H
#include mycamera.h

MyCamera::MyCamera(QObject *parent)
    : QObject{parent}
{}

QImage MyCamera::getImage()
{
    return imageQueue.dequeue();
}
#ifndef MIPICAMERA_H
#define MIPICAMERA_H

#include mycamera.h

class MIPICamera : public MyCamera
{
    Q_OBJECT
public:
    explicit MIPICamera(QObject *parent = nullptr);
    ~MIPICamera();

    virtual void init() override;

private:
    //打开相机
    int openDevice();

private:
    std::atomic_bool bExit = true;
};

#endif // MIPICAMERA_H
#include mipicamera.h
#include log.h

MIPICamera::MIPICamera(QObject *parent)
    : MyCamera{parent}
{
    bExit=false;
}

MIPICamera::~MIPICamera()
{
    bExit=true;
}

void MIPICamera::init()
{
    std::thread t([&](){
        openDevice();
    });
    t.detach();
}

int MIPICamera::openDevice()
{
    cv::VideoCapture cap(0);
    if (!cap.isOpened()) {
        LOGE(Camera open failed!);
        return -1;
    }

    LOGI(Camera open!);
    cv::Mat frame;
    // 循环取图
    while (true) {
        cap >> frame;
        if (frame.empty()) {
            continue;
        }

        QImage image;
        if(frame.channels()==1){
            image = QImage((const uchar*)(frame.data),frame.cols,frame.rows,frame.step, QImage::Format_Grayscale8);
        }else if(frame.channels()==3){
            image = QImage((const uchar*)(frame.data),frame.cols,frame.rows,frame.step, QImage::Format_BGR888);
        }

        //qDebug()<<image <<image.width()<< <<frame.cols<< <<frame.channels();
        imageQueue.enqueue(image);
        emit capture();

        if(bExit)
        {
            break;
        }
    }
    cap.release();
    LOGI(Camera close!);
}
  • QGraphicsView初始化时,可以绑定信号和槽,从队列里取出QImage。