由于工作的需要,又完成了一款相机的SDK的调用。通过这一款相机的调用,还是学习到了很多调用一款相机的SDK在细节上的一些东西。本篇文章基本只会讲关于调用过程中的一些重难点。其他,通过SDK的Demo就基本能够完成。但整体的流程,我还是会按照我标题写的那样逐步写下来的。希望能帮你少走一些弯路。
我的工作环境是:Win10+Qt5.15+MSVC VS2019
。
大华的SDK使用的是:Ver2.3.1。基本上大华的SDK就都在这里面了。注意,我使用的这款SDK是没有C++的接口的,只有C#和C。并且还有一个很多的缺陷,这个版本没有一个可以获取所有参数的统一的接口。据说Vver2.2.5这款是有C++的接口的。我也没详细去看,仅供参考,有需要还是要自己下载下来,看一下对应版本的SDK的区别。再决定开发的版本。
一开始,我们要先获取一下该相机的基本信息,这基本是所有操作SDK最开始的操作:
我选取核心的这个函数展示一下:
bool MDeviceDahuaG3UCInfo::SearchDeviceInfos(QStringList &lInfo)
{
int ret = IMV_OK;
#ifdef WIN_DAHUA_G3U
IMV_DeviceInfo* pDevInfo = NULL;
// 发现设备
// discover camera
IMV_DeviceList deviceInfoList;
ret = IMV_EnumDevices(&deviceInfoList, interfaceTypeAll);
if (IMV_OK != ret)
{
qDebug()<<"Enum Device fail"<<ret;
return false;
}
if (deviceInfoList.nDevNum < 1)
{
qDebug()<<"--> no camera";
return false;
}
for (uint32_t i = 0; i < deviceInfoList.nDevNum; i++)
{
pDevInfo = &deviceInfoList.pDevInfo[i];
QString ModelName = pDevInfo->modelName;
QString CameraVendor = pDevInfo->vendorName;
QString DeviceVersion = pDevInfo->deviceVersion;
QString manufacetureInfo = pDevInfo->manufactureInfo;
QString DeviceSN = pDevInfo->serialNumber;
QString DeviceKey = pDevInfo->cameraKey;
QString DeviceUserID = pDevInfo->cameraName;
bool bUsed = isDeviceOpen(DeviceKey);
QString devInfo;
devInfo += QString("%1:%2;").arg(XML_TLAYER_TYPE) //传输协议
.arg(XML_TLAYER_GEV);
devInfo += QString("%1:%2;").arg(XML_DRIVER_TYPE) //驱动类型
.arg(m_cCameraType);
devInfo += QString("%1:%2;").arg(XML_DEVICE_MODEL_NAME)//设备型号
.arg(ModelName);
devInfo += QString("%1:%2;").arg(XML_DEVICE_USER_DEFINE)//自定义名称
.arg(DeviceUserID);
devInfo += QString("%1:%2;").arg(XML_DEVICE_ID) //序列号
.arg(DeviceSN);
devInfo += QString("%1:%2;").arg(XML_DEVICE_ID) //序列号
.arg(DeviceSN);
devInfo += QString("%1:%2;").arg(XML_DEVICE_VENDOR) //生产厂家
.arg(CameraVendor);
devInfo += QString("%1:%2;").arg(XML_DEVICE_VERSION) //设备版本
.arg(DeviceVersion);
devInfo += QString("%1:%2;").arg(XML_DEVICE_STATUS) //设备状态
.arg(bUsed);
qDebug()<<"=>GetDeviceDaHuaG3U:"<<devInfo;
lInfo << devInfo;
ret = true;
}
#endif
return ret;
}
里面大部分内容应该都是很简单的。里面有一个isDeviceOpen
这个函数可以暂时先不用管它,等后面,我会重点讲一下这部分的内容。
这里基本上就是发现设备,调用第几个设备的接口。从而获取对应设备的信息。
主动抓图的话,也是比较简单地,核心就是弄个线程不断的调用下面这个函数就可以了。你们可以做个参考,如果语言是Qt的。关于这部分还是建议去看大华SDK本身的demo.qt的写的也不错。
qint32 MDeviceDahuaG3UC::CaptureImageEx(QImage *img, MFrameInfo *info)
{
qint32 ret = RETURN_FAIL;
try {
if(m_bLoaded&&m_cTriggerMode=="Off")
{
IMV_Frame frame;
unsigned char* pRGBbuffer = NULL;
ret = IMV_GetFrame(m_devHandle, &frame, 1000);
if (IMV_OK != ret)
{
return ret;
}
m_iHeight = (qint32)frame.frameInfo.height;
m_iWidth = (qint32)frame.frameInfo.width;
QImage::Format format = QImage::Format_Indexed8;
if(m_cPixelFormat.compare("Mono8")!=0)
format = QImage::Format_RGB888;
if((img->width() !=m_iWidth) ||
(img->height() !=m_iHeight) ||
(img->format() !=format) )
{
qDebug()<<"--> DahuaG3V:CaptureImageEx reset image memory! ";
*img = QImage(m_iWidth,m_iHeight,format);
if(format == QImage::Format_Indexed8)
img->setColorTable(m_vColorTabel);
}
if(m_cPixelFormat.compare("Mono8")!=0)//注意点1
{
int nRgbBufferSize = 0;
nRgbBufferSize = frame.frameInfo.width *frame.frameInfo.height*3;
pRGBbuffer = (unsigned char*)malloc(nRgbBufferSize);
if (pRGBbuffer == NULL)
{
// 释放内存
// release memory
free(frame.pData);
printf("RGBbuffer malloc failed.\n");
}
IMV_PixelConvertParam stPixelConvertParam;
stPixelConvertParam.nWidth = frame.frameInfo.width;
stPixelConvertParam.nHeight = frame.frameInfo.height;
stPixelConvertParam.ePixelFormat = frame.frameInfo.pixelFormat;
stPixelConvertParam.pSrcData = frame.pData;
stPixelConvertParam.nSrcDataLen = frame.frameInfo.size;
stPixelConvertParam.nPaddingX = frame.frameInfo.paddingX;
stPixelConvertParam.nPaddingY = frame.frameInfo.paddingY;
stPixelConvertParam.eBayerDemosaic = demosaicNearestNeighbor;
stPixelConvertParam.eDstPixelFormat = gvspPixelRGB8;
stPixelConvertParam.pDstBuf = pRGBbuffer;
stPixelConvertParam.nDstBufSize = nRgbBufferSize;
int ret = IMV_PixelConvert(m_devHandle, &stPixelConvertParam);
if (IMV_OK != ret)
{
// 释放内存
// release memory
qDebug()<<"image convert to RGB failed! ErrorCode"<<ret;
if(pRGBbuffer&&frame.pData)//注意点2
{
free(frame.pData);
free(pRGBbuffer);
}
return false;
}
}
void *pbit = nullptr;
int size =0;
if(m_cPixelFormat.compare("Mono8")!=0)
{
pbit = pRGBbuffer;
size = qMin((int)frame.frameInfo.size*3,img->byteCount());//注意点3
}
else
{
pbit = frame.pData;
size = qMin((int)frame.frameInfo.size,img->byteCount());//这个操作是为了防止复制操作会导致数据溢出
}
if(size)
memcpy(img->bits(),pbit,size);
info->nWidth = (qint32)frame.frameInfo.width;
info->nHeight = (qint32)frame.frameInfo.height;
info->nFramerLen = (qint32)frame.frameInfo.size;
quint64 nformat = (qint32)frame.frameInfo.pixelFormat;
//info->cFormat = nformat;
info->cFormat = "RGB888";
info->fFrameRate = m_fFrameRate;
if(m_cPixelFormat.compare("Mono8")!=0&&pRGBbuffer!=nullptr)//注意点4
{
free(pRGBbuffer);
}
ret = IMV_ReleaseFrame(m_devHandle, &frame);//注意点5
if (IMV_OK != ret)
{
printf("Release frame failed! ErrorCode[%d]\n", ret);
}
ret = RETURN_OK;
}
} catch (...) {
qDebug()<<"MDeviceDahuaG3UC::CaptureImageEx has some nullPtr";
return false;
}
return ret;
}
有几个需要注意的点,我这里提一下,对应的注意点在程序中都已经标出,可以对应着过去那边看一看:
注意点1:首先,我在这个程序里面,不管当前相机采集出的格式是什么(除了Mon8),我都会调用它的转换函数,转换成RGB888.这也是其demo提供出的方法。
注意点2:这里最好做一个判断,再去释放,万一分配不成功,你还强行去释放,程序容易崩溃。
注意点3:拷贝数据的时候,注意,这里要拷贝3倍的mono8格式数据的大小。
注意点4:转换完后,要及时把申请的内存给释放了。不然,你程序很快就会变缓慢,然后卡顿崩溃。因为内存都会分配完了。
注意点5:注意,结束的时候,要调用IMV_ReleaseFrame的这个帧。
关于这部分,还是建议去看其SDK的demo,因为我这边会写的比较割裂,也就是步骤都是比较分开的,不太适宜观看,但为了文章结构的完整性,还是贴上来,进行讲解。
1、首先,要先注册一下回调函数,这是触发前的准备工作:
code
qint32 MDeviceDahuaG3UC::GrabThreadStart()
{
qint32 ret = RETURN_FAIL;
if(m_bLoaded)
{
try {
IMV_ClearFrameBuffer(m_devHandle);//注意点1
ret = IMV_AttachGrabbing(m_devHandle, (IMV_FrameCallBack)onGetFrame, this);//注意点2
if (IMV_OK != ret)
{
printf("Attach grabbing failed! ErrorCode[%d]\n", ret);
}
else
{
ret = RETURN_OK;
}
}
catch (...) {
qDebug()<<"MDeviceDahuaG3UC::GrabThreadStart has nullptr";
}
}
return ret;
}
注意点:
ret = IMV_AttachGrabbing(m_devHandle, (IMV_FrameCallBack)onGetFrame, this);//注意点2
2、这个函数就是回调函数,首先,这个函数要是静态函数,将,传入的对象进行强制类型转换。
code
static void onGetFrame(IMV_Frame* pFrame, const void* pUser)
{
const MDeviceDahuaG3UC* pDev=static_cast<const MDeviceDahuaG3UC*>(pUser);
if(pDev)
{
pDev->triggerEvent(pFrame);
}
}
3、 接下来是调用的传帧的函数
code
void MDeviceDahuaG3UC::triggerEvent(IMV_Frame* pFrame)const
{
#ifdef WIN_DAHUA_G3U
int ret = IMV_OK;
if(m_bStopWork)
{
qDebug()<<"triggerEvent m_bStopWork is true";
return;
}
if(m_fGrabCallbackEx)
{
MFrameInfo info;
unsigned char* pRGBbuffer = NULL;
if(m_cPixelFormat.compare("Mono8")!=0)//注意点1
{
int nRgbBufferSize = 0;
nRgbBufferSize = pFrame->frameInfo.width *pFrame->frameInfo.height * 3;//注意点2
pRGBbuffer = (unsigned char*)malloc(nRgbBufferSize);
if (pRGBbuffer == NULL)
{
// 释放内存
// release memory
free(pFrame->pData);
printf("RGBbuffer malloc failed.\n");
}
IMV_PixelConvertParam stPixelConvertParam;
stPixelConvertParam.nWidth = pFrame->frameInfo.width;
stPixelConvertParam.nHeight = pFrame->frameInfo.height;
stPixelConvertParam.ePixelFormat = pFrame->frameInfo.pixelFormat;
stPixelConvertParam.pSrcData = pFrame->pData;
stPixelConvertParam.nSrcDataLen = pFrame->frameInfo.size;
stPixelConvertParam.nPaddingX = pFrame->frameInfo.paddingX;
stPixelConvertParam.nPaddingY = pFrame->frameInfo.paddingY;
stPixelConvertParam.eBayerDemosaic = demosaicNearestNeighbor;
stPixelConvertParam.eDstPixelFormat = gvspPixelRGB8;
stPixelConvertParam.pDstBuf = pRGBbuffer;
stPixelConvertParam.nDstBufSize = nRgbBufferSize;
ret = IMV_PixelConvert(m_devHandle, &stPixelConvertParam);
if (IMV_OK != ret)
{
// 释放内存
// release memory
qDebug()<<"image convert to RGB failed! ErrorCode\n";
free(pFrame->pData);
free(pRGBbuffer);
return ;
}
}
uchar *pbit = nullptr;
if(m_cPixelFormat.compare("Mono8")!=0)
{
pbit = pRGBbuffer;
info.nFramerLen = pFrame->frameInfo.size*3;//注意点3
info.cFormat = "RGB888";
}
else
{
pbit = pFrame->pData;
info.nFramerLen = pFrame->frameInfo.size;
info.cFormat = "Mono8";
}
info.nWidth = pFrame->frameInfo.width;
info.nHeight = pFrame->frameInfo.height;
m_fGrabCallbackEx(m_pCallUser,pbit,&info);
if(m_cPixelFormat.compare("Mono8")!=0&&pRGBbuffer!=NULL)//注意点4
{
free(pRGBbuffer);
}
ret = IMV_ReleaseFrame(m_devHandle, pFrame);//注意点5
if (IMV_OK != ret)
{
qDebug()<<"Release frame failed!";
}
}
#endif
}
注意点
注意点1:注意,非Mono8格式的帧要进行格式转换。
注意点2:开辟的内存空间,要是宽高的三倍。
注意点3:往上传递的帧信息的大小也要是宽高的3倍。
注意点4:如果是非Mono8的格式的要对申请的缓存进行释放
注意点5:要释放帧。
关于这个SDK的缺点就在于这了,没有一个接口可以获取所有的参数名称,让我能够进行统一获取,然后,批量化显示,所以,我这里只能使用xml文件进行固定的一些参数进行显示。
我这里就直接举例几个获取与设置参数的函数。
曝光的获取:
double MDeviceDahuaG3UC::GetExposureTime()
{
double ExposureTime = 0;
int ret = IMV_OK;
#ifdef WIN_DAHUA_G3U
if(m_devHandle)
{
ret = IMV_GetDoubleFeatureValue(m_devHandle,"ExposureTime",&ExposureTime);
if (IMV_OK != ret)
{
qDebug()<<"--> GetExposureTime fail"<<ExposureTime;
}
else
{
m_fExposureTime = ExposureTime/1000;
}
}
#endif
return m_fExposureTime;
}
曝光的设置
void MDeviceDahuaG3UC::SetExposureTime(double time)
{
int ret = IMV_OK;
#ifdef WIN_DAHUA_G3U
if(m_devHandle)
{
ret = IMV_SetDoubleFeatureValue(m_devHandle,"ExposureTime",time*1000);
if (IMV_OK != ret)
{
qDebug()<<"--> SetExposureTime fail"<<time;
}
else
{
m_fExposureTime = time;
}
}
#endif
}