海康威视摄像机SDK的使用
写在前面
文章未经允许,不可转载
海康威视提供的SDK,支持C++、Java、C#,但是就是不支持Python,调用SDK并不是件很愉快的事情。毕竟和图像相关的程序,Python编写起来更加方便。如果不特定选择海康威视这个品牌, 大华摄像机 是支持Python,而且还支持go,第一次看到的时候,我还是吃了一惊,心想厉害呀!
本文章将会有如下内容:
+ 解码yuv
+ 编写类调用海康威视相机
+ void *
+ redis
+ 多线程
+ 制作Python的海康威视库(不是重点)
首发于个人博客
opencv解码
摄像机是图像采集的设备,采集得到是YUV格式的图像数据,如果要对图像进行处理,第一步就是转换为rgb的图像。 方法嘛,网上很多文章都已经写了,有自己编程,也有用opencv直接转换的,我个人推荐使用opencv库进行转换。
位于回调函数DecCBFun中,代码片段如下:
// 解码部分不控制速度,只要从回调函数中返回,解码器就会解码下一部分数据
// 解码回调后,需要及时返回;如果等到上层所有操作都结束再返回,可能会影响到播放库主线程执行
long lFrameType = pFrameInfo->nType;
if (lFrameType == T_YV12)
// 原始yuv图像
cv::Mat YUVImg(pFrameInfo->nHeight + pFrameInfo->nHeight / 2, pFrameInfo->nWidth, CV_8UC1, pBuf);
cv::Mat BGRImg;
cv::cvtColor(YUVImg, BGRImg, cv::COLOR_YUV2BGR_YV12); // 随着图片像素增加,cup占用率也增加
多个摄像机调用,编写类
对于帮助文档中的例子,或者网上绝大多数文章,都仅仅只是一个摄像头的调用,其实在工作中不只是一个摄像头的调用。 如果要调用多个摄像头,很自然想到建立一个 HKCamera类,每一个实例对应一个摄像机。 如果要实现一个HKCamera类,遇到最大的问题回调函数没有this指针,指定定义为静态函数。
如果是一个摄像机,可以直接定义一个静态指针,可以完成任务。
class HKCamera{
private:
static HKCamera* pThis // 回调函数是静态函数,可以访问静态成员
public:
HKCamera()
pThis = this;
A * A::pThis=NULL
但是是多个摄像机,就有点不方便了,记住一点,
友好的回调函数一定会设计一个类似
void* duser
的指针
,这个保留指针的存在,就可以把this传进去,就可以大功告成了。
帮助文档的错误
海康威视的文档让我试错了很久,还把64位的SDK换成了32位的SDK,下面讲讲其中的过程。 整体思路确定下来,然后看海康威视的文档,发现文档中并没有提供
void* duser
的指针, 但是有
long nUser
。 如果把this转换为long类型,然后再转换回来,那么也可以达到目标,但是long类型是4个字节,用64位的SDK,this指针占用8个字节,无论如何也不能轻易转换。
所以就有和官方人员的邮件交流,我提的问题大致如下:
获取实时解码流,根据帮助文档的例子,在类中定义了静态函数,,
static void CALLBACK g_RealDataCallBack_V30(LONG lRealHandle, DWORD dwDataType, BYTE* pBuffer, DWORD dwBufSize, void* dwUser);
static void CALLBACK DecCBFun(long nPort, char* pBuf, long nSize, FRAME_INFO* pFrameInfo, long nUser, long nReserved2);
首先把this指针传入g_RealDataCallBack_V30,因为g_RealDataCallBack_V30中有void * dwUser参数
lRealPlayHandle = NET_DVR_RealPlay_V40(this->lUserID, &struPlayInfo, g_RealDataCallBack_V30, this)
在g_RealDataCallBack_V30函数体内调用PlayM4_SetDecCallBackMend,问题就出在了这里
if (!PlayM4_SetDecCallBackMend(pHk->lPort, DecCBFun, nUser))
BOOL PlayM4_SetDecCallBackMend( long nPort, DecCBFun fDecCBFun, long nUser);
其中回调函数的定义:
typedef void(CALLBACK *DecCBFun)(
long nPort,
char *pBuf,
long nSize,
FRAME_INFO *pFrameInfo,
long nUser,
long nReserved2
以上的函数并没有定义传入 void * 参数,而是用 long nUser替代,我无法把g_RealDataCallBack_V30中获得的this指针传入PlayM4_SetDecCallBackMend中
尝试如下:
将指针类型用reinterpret_cast为long类型,然后传入
在windows 64位系统下,long只占4个字节,而指针占有8个字节,那个转换类型会发生很大问题
也就是无法通过reinterpret_cast把8直接指针类型转换为4字节long类型。请问有什么办法解决这个问题码?当初为什么要不long nUser定义为 void *nUser呢?
和官方交流了很多个来回,然后弄清楚了问题
不要使用官方文档中的plaympeg4.h
头文件,而是换成
PlayM4.h
, 果不其然,这个头文件相应引入的函数中存在了熟悉的
void* nUser
的指针
评论区里说还是没有 void * nUser,请再仔细看看,应该是有的
PLAYM4_API BOOL __stdcall PlayM4_SetDecCallBackMend(LONG nPort,void (CALLBACK* DecCBFun)(long nPort,char * pBuf,long nSize,FRAME_INFO * pFrameInfo, void* nUser,void* nReserved2), void* nUser);
建议所有的大公司一定要重视帮助文档的正确性,保证同步测试。
指针随想
看到海康威视这么大的公司,都会犯这个低级错误,一直在文档中用 plaympeg4.h ,可能原因是工业界为了保证稳定性,很多时候还是用32位进行编译,而这个问题呢,就没有被发觉。
redis
解决多个摄像机的问题,还有一个简单的方法,就是用刚才说的定义一个静态指针的方法,而在回调函数中把解码得到的数据,存入到redis中,将整体编译成一个可执行文件。 然后开启多个进程运行这个程序就好了,类似这样的代码
std::vector<uchar> img_encode;
cv::imencode(".jpg", BGRImg, img_encode, img_quality_params); // 以特定格式编码,存入vector
std::string img_str(img_encode.begin(), img_encode.end()); // bytes数据组成string
// 将二进制数据存入redis中
redisReply* reply;
reply = (redisReply*)redisCommand(pHk->redisCon, "RPUSH %s %b", pHk->redisDetectKey.c_str(), img_str.c_str(), (size_t)img_str.length());
freeReplyObject(reply);
// 保证redis中列表数据不会超过2个,不用写成事务形式。线程安全由redis保证
reply = (redisReply*)redisCommand(pHk->redisCon, "LTRIM %s -2 -1", pHk->redisDetectKey.c_str());
freeReplyObject(reply);
多线程
在回调函数中,尽可能不要停留,所以用多线程中锁,尽快返回YUV数据
// 解码部分不控制速度,只要从回调函数中返回,解码器就会解码下一部分数据
// 解码回调后,需要及时返回;如果等到上层所有操作都结束再返回,可能会影响到播放库主线程执行
long lFrameType = pFrameInfo->nType;
if (lFrameType == T_YV12)
// 原始yuv图像
cv::Mat YUVImg(pFrameInfo->nHeight + pFrameInfo->nHeight / 2, pFrameInfo->nWidth, CV_8UC1, pBuf);