一、开发原因
项目需要调用高德红外相机拍图,需要有一个采图软件,为了方便操作并且有可视化见面,我选择了基于**“Qt + Opencv + 高德SDK”的组合,使用“VS + QT”** 实现相机的采图。
原始的想法:
- 点击按钮/按键拍图功能
- 按F键或者点击“拍摄",存图
- 模仿高德的例程写一个就行
二、Qt学习笔记
2.1 信号与槽
信号是动作开关,槽是执行函数;信号和槽函数之间用 connect 函数连接。个人理解应该是类似于中断机制,当触发中断后,进入中断函数执行相关操作。
添加槽函数的方式:
方法一
使用VS插件中的Qt Designer,转到槽函数;步骤如下:
- 在VS中需要 “VS解决方案中选中方案,右键->Qt->Refresh intelliSense进行刷新”;
- 然后在对应的.h和.cpp文件中分别添加 信号类函数声明 和 槽函数具体功能;
- 该方法在会自动在 ui_xxxxx.h 文件中定义connect函数
方法二
在构造函数里写connect函数,(纯手动添加connect函数)
connect函数两种写法:
// 连接信号与槽 谁发出信号 发出什么信号 谁处理信号 怎么处理信号
// 方法一:用宏定义
connect(ui.cmd_Edit, SIGNAL(returnPressed()), this, SLOT(commit_BtnClicked()));
// 方法二:取地址
connect(ui.exit_Btn, &QPushButton::clicked, this, &QCamera::on_exit_BtnClicked);
然后在对应的.h和.cpp文件中分别添加 信号类函数声明 和 槽函数具体功能(一般先声明好槽函数);
当槽函数的功能比较少时,可以用QMessageBox的方法
connect(ui.browse_Btn, &QPushButton::clicked, [this]()
{
QMessageBox::information(this, "信息", "点击浏览");
});
2.2 定时器
两种方式开启定时器
方式一:使用QObject类
// 相关函数
// startTimer 开启
// timerEvent 事件
// killTimer 关闭
// 槽函数里打开计时器
myTimerId = this->startTimer(TIMEOUT); //TIMEOUT
// 然后重写虚函数(类继承)
virtual void timerEvent(QTimerEvent *event);
timerEvent(QTimerEvent *event){
if(event->timerId() != myTimerId)
return;
// 实现定时功能
}
// 槽函数关闭定时器
this->killTimer(myTimerId);
方式二:使用QTimer类
// 头文件定义
QTimer *timer;
// 构造函数中
timer = new QTimer;
// 定时器槽函数
connect(timer,&QTimer::timerout,this,&Widget::timeoutSlot)
// 开启定时器
timer->start(TIMEOUT);
void Widget::timeoutSlot(){
//定时器操作
}
// 结束定时器
timer->stop();
// 只执行单次定时
QTimer::singleShot(1000, this, SLOT(timerout));
三、高德红外SDK梳理
这里可以跳过,我自己梳理手册记录的。
数据类型说明:
enum guide_usb_code_e
typedef enum
{
ERROR_NO = 1,
//1:无错误
ERROR_DEVICE_NOT_FOUND = -1,
//-1:找不到设备
ERROR_POINT_NULL = -2,
//-2:数据指针为空
ERROR_POINTS_TOO_LARGE = -3,
//-3:数据指针过大
ERROR_POINTS_TOO_SMALL = -4,
//-4:数据指针过小
ERROR_MALLOC_FAILED = -5,
//-5:申请缓存失败
ERROR_RESOLUTION = -6,
//-6:分辨率设置错误
ERROR_UNKNOW = -999
//-999:未知错误
} guide_usb_code_e;
struct guide_usb_device_info_t 高德设备信息,打开设备时需要的信息
typedef struct
{
int width; //图像宽度
int height; //图像高度
guide_usb_video_mode_e video_mode; //视频模式
} guide_usb_device_info_t;
enum guide_usb_video_mode_e 根据机芯配置好的视频模式,传入对应的类型
typedef enum
{
X16 = 0, //X16
X16_PARAM = 1, //X16+参数行
Y16 = 2, //Y16
Y16_PARAM = 3, //Y16+参数行
YUV = 4, //YUV
YUV_PARAM = 5, //YUV+参数行
Y16_YUV = 6, //Y16+YUV
Y16_PARAM_YUV = 7 //Y16+参数行+YUV
} guide_usb_video_mode_e;
struct guide_usb_frame_data_t 图像数据X16/Y16 模式从 frame_src_data 传出数据 YUV 模式从 frame_yuv_data 传出数据
typedef struct
{
int frame_width; //图像宽度
int frame_height; //图像高度
unsigned char *frame_rgb_data; //rgb 数据
int frame_rgb_data_length; //rgb 数据长度
short *frame_src_data; //原始数据,X16/Y16
int frame_src_data_length; //原始数据长度
short *frame_yuv_data; //yuv 数据
int frame_yuv_data_length; //yuv 数据长度
short *paraLine; //参数行
int paraLine_length; //参数行长度
} guide_usb_frame_data_t;
OnFrameDataReceivedCB 这段代码定义了一个函数指针类型 OnFrameDataReceivedCB,该函数指向一个没有返回值的函数,该函数接受一个 guide_usb_frame_data_t 类型的常量参数 frame_data。
typedef void(__stdcall *OnFrameDataReceivedCB)(const guide_usb_frame_data_t frame_data);
enum guide_usb_device_status_e 机芯连接状态
typedef enum
{
DEVICE_CONNECT_OK = 1,
//连接正常
DEVICE_DISCONNECT_OK = -1,
//断开连接
} guide_usb_device_status_e;
OnDeviceConnectStatusCB 连接状态回调方法,自动读取相机连接状态
typedef void(__stdcall *OnDeviceConnectStatusCB)(const guide_usb_device_status_e device_status);
struct device_info
typedef struct
{
int devID; //设备 ID
char devName[128]; //设备名称
} device_info;
struct device_info_list
typedef struct
{
int devCount; //设备数量
device_info devs[32]; //设备信息
} device_info_list;
四、问题、小技巧
问题(数据类型需要对应,指针也是一样)
报错:在Qt的类中申明了OnFrameDataReceivedCB pFrameProc;
并且在主函数中将pFrameProc = FrameData_callback;
不能将"void (CameraControl:😗)(quide usb frame data tframe data)“类型的值分配到"OnFrameDataReceivedCB” 类型的实体。
解决方法
问题出在函数指针类型的匹配上。
OnFrameDataReceivedCB 定义的函数指针类型不兼容 void (CameraControl::)(guide_usb_frame_data_t) 这种成员函数指针类型。
原因是 CameraControl:: 部分表示成员函数指针,而OnFrameDataReceivedCB 是一个全局函数指针类型。
想要使用成员函数指针,需要修改 OnFrameDataReceivedCB 的定义来匹配成员函数指针的类型。
可以将 OnFrameDataReceivedCB 定义为一个成员函数指针类型,例如:
typedef void (CameraControl::OnFrameDataReceivedCB)(guide_usb_frame_data_t frame_data);
这样,OnFrameDataReceivedCB 就可以匹配 void (CameraControl::)(guide_usb_frame_data_t) 这种成员函数指针类型了。
代码规范:
命名:驼峰命名
类名,结构体名,函数名:大驼峰 class Student
类对象,变量: 小驼峰 int countNum
{ 前面加空格
; , 后面加空格
Qt出现中文乱码
在头文件中添加 #pragma execution_character_set(“UTF-8”)
关于Qt图像处理
问题一:
QImage((const unsigned char*)(myImg.data), myImg.cols, myImg.rows, QImage::Format_RGB888).copy()
QImage((const unsigned char*)(myImg.data), myImg.cols, myImg.rows, QImage::Format_RGB888)
为什么用后者会有程序卡退和报错:(Qt5Gui.dll)处(位于 xxx.exe 中)引发的异常: 0xC0000005: 读取位置 XXXXXXXX 时发生访问冲突。这里copy()作用是啥?
解释:
QImage((const unsigned char*)(myImg.data), myImg.cols, myImg.rows, QImage::Format_RGB888).copy():
这行代码创建了一个 QImage 对象,并使用给定的参数构造了这个对象。然后调用了 copy() 方法,该方法返回了一个 QImage 对象的深拷贝,也就是说,它会复制原始图像的像素数据,而不仅仅是共享相同的数据。因此,这个操作会在内存中复制一份图像数据。
QImage((const unsigned char*)(myImg.data), myImg.cols, myImg.rows, QImage::Format_RGB888);
这行代码创建了一个 QImage 对象,但没有调用 copy() 方法。这意味着它只是使用了给定的参数直接构造了一个 QImage 对象,但没有额外的深拷贝操作。
因此,它与原始图像共享相同的像素数据,而不是创建了一个完全独立的副本。所以,主要区别在于第一行代码通过 copy() 方法创建了一个图像数据的深拷贝,而第二行代码则没有进行额外的拷贝操作,只是直接使用了原始图像数据。当你使用 QImage 构造函数直接传递 myImg.data 时,并且没有使用 copy() 方法,QImage 对象会尝试直接访问 myImg.data 指向的内存。如果 myImg 对象在 QImage 对象使用之后被释放或者销毁,那么 QImage 对象就会尝试访问无效的内存地址,从而导致程序崩溃或异常。
这种报错通常是由于内存访问错误引起的。在这种情况下,可能是因为 myImg 对象的生命周期问题导致了内存访问错误。
解决这个问题的一种方法是确保在 QImage 对象的生命周期内保持 myImg 对象有效(保证图像处理完之前不会无效)。
另一种方法是使用 copy() 方法创建 QImage 对象的深拷贝,以避免与原始图像数据的共享,从而减少潜在的生命周期问题(先缓存下来)。