目录
安装环境
Deepstream-test4配置文件改写
调用OpenCV截图
存疑
其他收获
本文实现基于test4,基于deepstream-app的更改和代码更新见DeepStream结合OpenCV4实现视频的分析和截图(二)
Ubuntu:18.04.1
DeepStream:SDK 4.0.1
CUDA:10.1
OpenCV:4.1
首先,需要在linux上安装opencv。
sudo apt-get install libopencv-dev
参考:https://www.cnblogs.com/chenzhen0530/p/12109868.html
需要注意的是,要把ffmpeg重新编译安装,否则在make时,会报错,往前翻寻找报错信息,是一个和video相关的报错。
我使用的例子是deepstream-test4。原因是涉及了kafka,相对test5看起来也简单些。在test4上我做了修改。
首先,test4如果在vscode里调试,会遇到写launch.json文件时arg参数的问题。下面是我在vscode github上的提问。
https://github.com/microsoft/vscode/issues/93166#issuecomment-604327721
即deepstream要求--conn-str="add;9092;topic",这个必须一起输入进去,在终端输入是没问题的,但是在vscode里调试,设置launch.json的args时,会省略掉分号后面的所有内容。如果哪位大佬解决了这个问题,可以联系我。
为了解决上面这个问题,我写了个配置文件。顺便也要重新定义一下输入,包括截图时间、人数、kafka连接地址、输入源等。
下面是我改写的配置文件代码。可以作为模板使用。
//**************** my function to parse config file *********//
//my config define
#define CONFIG_GROUP_KAFKA "kafka"
#define CONFIG_GROUP_KAFKA_MSG_BROKER_CONN_STR "conn-str"
#define CONFIG_GROUP_KAFKA_MSG_BROKER_TOPIC "topic"
#define CONFIG_GROUP_ALARM "alarm"
#define CONFIG_GROUP_ALARM_INTERVAL "interval"
#define CONFIG_GROUP_ALARM_NUMBER "number"
GST_DEBUG_CATEGORY (APP_CFG_PARSER_CAT);
#define CHECK_ERROR(error) \
if (error) { \
GST_CAT_ERROR (APP_CFG_PARSER_CAT, "%s", error->message); \
goto done; \
}
typedef struct
{
/* data */
gchar *kafka_conn_str;
gchar *kafka_topic;
}myKafka;
typedef struct
{
guint interval; //config time interval for screenshot
guint number; //number of person. It will cause alarm.
}myalarm;
typedef struct
{
/* data */
myKafka msg_broker; //config kafka address and topic
myalarm alarm; // alarm info.
}myConfig;
static gboolean parse_kafka(myConfig *config, GKeyFile *key_file)
{
gboolean ret = FALSE;
gchar **keys = NULL;
gchar **key = NULL;
GError *error = NULL;
keys = g_key_file_get_keys (key_file, CONFIG_GROUP_KAFKA, NULL, &error);
CHECK_ERROR (error);
for(key=keys;*key;key++)
{
if(!g_strcmp0 (*key, CONFIG_GROUP_KAFKA_MSG_BROKER_CONN_STR)) //address
{
config->msg_broker.kafka_conn_str =
g_key_file_get_string (key_file, CONFIG_GROUP_KAFKA,
CONFIG_GROUP_KAFKA_MSG_BROKER_CONN_STR, &error);
CHECK_ERROR (error);
}
else if (!g_strcmp0 (*key, CONFIG_GROUP_KAFKA_MSG_BROKER_TOPIC)) // topic
{
config->msg_broker.kafka_topic =
g_key_file_get_string (key_file, CONFIG_GROUP_KAFKA,
CONFIG_GROUP_KAFKA_MSG_BROKER_TOPIC, &error);
CHECK_ERROR (error);
}
else {
NVGSTDS_WARN_MSG_V ("Unknown key '%s' for group [%s]", *key,
CONFIG_GROUP_KAFKA);
}
}
ret = TRUE;
done:
if (error) {
g_error_free (error);
}
if (keys) {
g_strfreev (keys);
}
if (!ret) {
NVGSTDS_ERR_MSG_V ("%s failed", __func__);
}
return ret;
}
static gboolean parse_arlam(myConfig *config, GKeyFile *key_file)
{
gboolean ret = FALSE;
gchar **keys = NULL;
gchar **key = NULL;
GError *error = NULL;
config->alarm.interval = 10; // default interval to screenshot
keys = g_key_file_get_keys (key_file, CONFIG_GROUP_ALARM, NULL, &error);
CHECK_ERROR (error);
for(key=keys;*key;key++)
{
if(!g_strcmp0 (*key, CONFIG_GROUP_ALARM_INTERVAL)) //interval
{
config->alarm.interval =
g_key_file_get_integer (key_file, CONFIG_GROUP_ALARM,
CONFIG_GROUP_ALARM_INTERVAL, &error); // it must be integer rather g_key_file_get_string
CHECK_ERROR (error);
}
else if (!g_strcmp0 (*key, CONFIG_GROUP_ALARM_NUMBER)) // number of person
{
config->alarm.number =
g_key_file_get_integer (key_file, CONFIG_GROUP_ALARM,
CONFIG_GROUP_ALARM_NUMBER, &error);
CHECK_ERROR (error);
}
else {
NVGSTDS_WARN_MSG_V ("Unknown key '%s' for group [%s]", *key,
CONFIG_GROUP_ALARM);
}
}
ret = TRUE;
done:
if (error) {
g_error_free (error);
}
if (keys) {
g_strfreev (keys);
}
if (!ret) {
NVGSTDS_ERR_MSG_V ("%s failed", __func__);
}
return ret;
}
gboolean
parse_config_file (myConfig *config, gchar *cfg_file_path)
{
GKeyFile *my_cfg_file = g_key_file_new ();
GError *error = NULL;
gboolean ret = FALSE;
gchar **groups = NULL;
gchar **group;
if (!APP_CFG_PARSER_CAT) {
GST_DEBUG_CATEGORY_INIT (APP_CFG_PARSER_CAT, "CFG_PARSER", 0, NULL);
}
if (!g_key_file_load_from_file (my_cfg_file, cfg_file_path, G_KEY_FILE_NONE,
&error)) {
GST_CAT_ERROR (APP_CFG_PARSER_CAT, "Failed to load uri file: %s",
error->message);
goto done;
}
groups = g_key_file_get_groups (my_cfg_file, NULL);
for (group = groups; *group; group++) {
gboolean parse_err = FALSE;
GST_CAT_DEBUG (APP_CFG_PARSER_CAT, "Parsing group: %s", *group);
// parsing kafka broker
if (!g_strcmp0 (*group, CONFIG_GROUP_KAFKA)) {
parse_err = !parse_kafka (config, my_cfg_file);
}
// parsing alarm
if (!g_strcmp0 (*group, CONFIG_GROUP_ALARM)){
parse_err = !parse_arlam (config, my_cfg_file);
}
if (parse_err) {
GST_CAT_ERROR (APP_CFG_PARSER_CAT, "Failed to parse '%s' group", *group);
goto done;
}
}
ret = TRUE;
done:
if (my_cfg_file) {
g_key_file_free (my_cfg_file);
}
if (groups) {
g_strfreev (groups);
}
if (error) {
g_error_free (error);
}
if (!ret) {
NVGSTDS_ERR_MSG_V ("%s failed", __func__);
}
return ret;
}
//**************************************************************//
终于到主题了。感谢这位博主提供的思路。https://blog.csdn.net/qq_32220889/article/details/102995841
博客中博主说了一句
试了很多方法,最后解决了,基本思想就是:删掉 nvosd,在 nvvidconv 的 src 上或在 sink 的 sink 上通过 gst_pad_add_probe 函数添加 probe,然后再 probe 回调中处理帧。
他踩过的坑我,以及论坛中别人踩过的坑,我很幸运地全踩个遍。最终发现,不用删掉nvosd组件,即原来构建的pipeline无需做修改。添加一个cudaMemcpy。原因很简单,就是直接从NvBufSurface过来的数据是不能直接访问的。
下面这段开始,是我踩坑记录,想了解的可以看一下,想奔结果的,直接跳过!
===========================================我是踩坑分割线========================================
或者NvBufSurfaceMap + NvBufSurfaceSyncForCpu。但是为什么会报"mapping of memory type not supported"呢?意思是说内存映射类型不支持,就去官网找这个函数https://docs.nvidia.com/metropolis/deepstream/4.0/dev-guide/DeepStream_Development_Guide/baggage/group__ee__nvbufsurface.html,加上debug,发现我的类型是NVBUF_MEM_DEFAULT,而官方写的很清楚,我没在jetson上跑,所以类型只能是NVBUF_MEM_CUDA_UNIFIED。如下图
于是我强制转换了类型。原来的错是不报了,但是在NvBufSurfaceSyncForCpu时又报错了,查看文档,还是类型问题。看来耍小聪明是不行滴。从下图中可以看出,无论是NvBufSurfaceSyncForCpu还是NvBufSurfaceSyncForDevice要求的类型都是针对Jetson的。纳尼?那我怎么转也没用。转了也不认!
========================================踩坑爬出来啦==============================================
参考这个论坛官方回复
https://forums.developer.nvidia.com/t/access-frame-pointer-in-deepstream-app/79838/24?u=jiejing_ma
参考完直接上我的代码
int write_frame(GstBuffer *buf)
{
NvDsMetaList * l_frame = NULL;
NvDsMetaList * l_user_meta = NULL;
// Get original raw data
GstMapInfo in_map_info;
char* src_data = NULL;
if (!gst_buffer_map (buf, &in_map_info, GST_MAP_READ)) {
g_print ("Error: Failed to map gst buffer\n");
gst_buffer_unmap (buf, &in_map_info);
return GST_PAD_PROBE_OK;
}
NvBufSurface *surface = (NvBufSurface *)in_map_info.data;
NvDsBatchMeta *batch_meta = gst_buffer_get_nvds_batch_meta (buf);
l_frame = batch_meta->frame_meta_list;
if (l_frame) {
NvDsFrameMeta *frame_meta = (NvDsFrameMeta *) (l_frame->data);
/* Validate user meta */
src_data = (char*) malloc(surface->surfaceList[frame_meta->batch_id].dataSize);
if(src_data == NULL) {
g_print("Error: failed to malloc src_data \n");
}
#ifdef PLATFORM_TEGRA
NvBufSurfaceMap (surface, -1, -1, NVBUF_MAP_READ);
NvBufSurfacePlaneParams *pParams = &surface->surfaceList[frame_meta->batch_id].planeParams;
unsigned int offset = 0;
for(unsigned int num_planes=0; num_planes < pParams->num_planes; num_planes++){
if(num_planes>0)
offset += pParams->height[num_planes-1]*(pParams->bytesPerPix[num_planes-1]*pParams->width[num_planes-1]);
for (unsigned int h = 0; h < pParams->height[num_planes]; h++) {
memcpy((void *)(src_data+offset+h*pParams->bytesPerPix[num_planes]*pParams->width[num_planes]),
(void *)((char *)surface->surfaceList[frame_meta->batch_id].mappedAddr.addr[num_planes]+h*pParams->pitch[num_planes]),
pParams->bytesPerPix[num_planes]*pParams->width[num_planes]
);
}
}
NvBufSurfaceSyncForDevice (surface, -1, -1);
NvBufSurfaceUnMap (surface, -1, -1);
#else
cudaMemcpy((void*)src_data,
(void*)surface->surfaceList[frame_meta->batch_id].dataPtr,
surface->surfaceList[frame_meta->batch_id].dataSize,
cudaMemcpyDeviceToHost);
#endif
gint frame_width = (gint)surface->surfaceList[frame_meta->batch_id].width;
gint frame_height = (gint)surface->surfaceList[frame_meta->batch_id].height;
gint frame_step = surface->surfaceList[frame_meta->batch_id].pitch;
cv::Mat frame = cv::Mat(frame_height, frame_width, CV_8UC4, src_data, frame_step);
// g_print("%d\n",frame.channels());
// g_print("%d\n",frame.rows);
// g_print("%d\n",frame.cols);
cv::Mat out_mat = cv::Mat (cv::Size(frame_width, frame_height), CV_8UC3);
cv::cvtColor(frame, out_mat, CV_RGBA2BGR);
cv::imwrite("test.jpg", out_mat);
if(src_data != NULL) {
free(src_data);
src_data = NULL;
}
}
gst_buffer_unmap (buf, &in_map_info);
}
这只是一个可以完成截图的小demo。后面还要修改。根据Infer出来的数据,取帧做opencv处理。根据哪些数据和信息以及做什么处理这里就不写了。上结果图
surface结构中batchsize有4个,对应surface->surfaceList.mappedAddr.addr有4个地址,但是我并没有设置batchsize。实验过程中,只有第一个地址有数据,并且正常播放的osd视频前三帧为黑色,但是却能截图出正常图片来。不清楚这两者之间是否存在关系。以及如何设置surface的batchsize(比如我要跑多路视频,分别根据infer信息截图)
好吧,其实这是我第一次正式接触Linux、makefile、makelist、gcc/g++和vscode(以前学网络实验时简单用过),边学边用,比熟练的人速度肯定慢了好多,总之过程是很艰辛的。这让我意识到,平时遇到不懂的,一定不能拖,当下立马解决,而且要追根问底,多查多看。还有平时真的要多看编译、链接、装载方面的书籍,不然以后肯定会有很大麻烦。
我就是个小白,哈哈,存疑问题有哪位大佬知道,可以评论区指点迷津哈~还有其他问题的也可以评论区交流~
立个flag,反正也不一定实现。下一篇更新接多路视频opencv的处理~
(问我UE里怎么用opencv插件的,我最近没玩UE,所以一直没更博,emmm先拖一拖吧~)