40.[ ALL_Python_APP]How to save a frame or object in Python
1.Add Python binding for nvds_obj_enc_process
diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt
index 7b01fb2..6df8952 100644
--- a/bindings/CMakeLists.txt
+++ b/bindings/CMakeLists.txt
@@ -98,7 +98,7 @@ function(add_ds_lib libname)
target_link_libraries(pyds ${libname})
endfunction()
-foreach(nvds_lib nvds_osd nvds_meta nvds_infer nvdsgst_meta nvbufsurface nvbufsurftransform nvdsgst_helper)
+foreach(nvds_lib nvds_osd nvds_meta nvds_infer nvdsgst_meta nvbufsurface nvbufsurftransform nvdsgst_helper nvds_batch_jpegenc)
add_ds_lib(${nvds_lib})
endforeach()
diff --git a/bindings/docstrings/functionsdoc.h b/bindings/docstrings/functionsdoc.h
index 2796605..371d77b 100644
--- a/bindings/docstrings/functionsdoc.h
+++ b/bindings/docstrings/functionsdoc.h
@@ -627,5 +627,35 @@ namespace pydsdoc
#add this code in plugin probe function.
num_sources_in_batch = pyds.nvds_measure_buffer_latency(hash(gst_buffer));)pyds";
- }
+
+ constexpr const char* nvds_obj_enc_create_context=R"pyds(
+ Create context and return a handle to NvObjEncCtx.
+
+ :arg gpu_id: gpu id.
+
+ :returns: handle to NvObjEncCtx.)pyds";
+
+ constexpr const char* nvds_obj_enc_process=R"pyds(
+ Enqueue an object crop for JPEG encode.
+ This is a non-blocking call and user should call `nvds_obj_enc_finish()`
+ to make sure all enqueued object crops have been processed.
+
+ :arg context: handle to NvObjEncCtx.
+ :arg args: An object of type :class:`NvDsObjEncUsrArgs`.
+ :arg buffer: GstBuffer from which to retrieve the :class:`NvBufSurface`
+ :arg obj_meta: An object of type :class:`NvDsOjbectMeta`.
+ :arg frame_meta: An object of type :class:`NvDsFrameMeta`.
+
+ :returns: 0 for success, -1 for failure.)pyds";
+
+ constexpr const char* nvds_obj_enc_finish=R"pyds(
+ Wait for all enqueued crops to be encoded.
+
+ :arg context: handle to NvObjEncCtx.)pyds";
+
+ constexpr const char* nvds_obj_enc_destroy_context=R"pyds(
+ Destroy NvObjEncCtx context.
+
+ :arg context: handle to NvObjEncCtx.)pyds";
+ }
}
\ No newline at end of file
diff --git a/bindings/docstrings/utilsdoc.h b/bindings/docstrings/utilsdoc.h
new file mode 100644
index 0000000..866c21f
--- /dev/null
+++ b/bindings/docstrings/utilsdoc.h
@@ -0,0 +1,44 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2021-2023 NVIDIA CORPORATION &
+ * AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace pydsdoc {
+namespace utilsdoc {
+namespace NvDsObjEncUsrArgsDoc {
+constexpr const char *descr = R"pyds(
+ user parameters for a nvds_obj_enc_process call
+
+ :ivar numDims: *int*, Number of dimesions of the layer.
+ :ivar numElements: *int*, Number of elements in the layer including all dimensions.
+ :ivar d: *np.array of int*, Size of the layer in each dimension.)pyds";
+
+constexpr const char *cast =
+ R"pyds(cast given object/data to :class:`NvDsObjEncUsrArgs`, call pyds.NvDsObjEncUsrArgs.cast(data))pyds";
+} // namespace NvDsObjEncUsrArgsDoc
+
+namespace NvDsObjEncOutParamsDoc {
+constexpr const char *descr = R"pyds(
+ output parameters of NVDS_CROP_IMAGE_META
+
+ :ivar outBuffer: *int*, The output buffer JPEG Encoded Object.)pyds";
+
+constexpr const char *cast =
+ R"pyds(cast given object/data to :class:`NvDsObjEncOutParams`, call pyds.NvDsObjEncOutParams.cast(data))pyds";
+} // namespace NvDsObjEncOutParamsDoc
+} // namespace utilsdoc
+} // namespace pydsdoc
\ No newline at end of file
diff --git a/bindings/include/utils.hpp b/bindings/include/utils.hpp
index f713b9b..223be0a 100644
--- a/bindings/include/utils.hpp
+++ b/bindings/include/utils.hpp
@@ -43,6 +43,9 @@ namespace pydeepstream {
pybind11::arg operator ""_a(const char *str, size_t len);
}
+namespace pydeepstream {
+ void bindutils(py::module &m);
+}
namespace pydeepstream::utils {
diff --git a/bindings/src/bindfunctions.cpp b/bindings/src/bindfunctions.cpp
index cc2c9cc..88e7163 100644
--- a/bindings/src/bindfunctions.cpp
+++ b/bindings/src/bindfunctions.cpp
@@ -17,6 +17,7 @@
#include "bind_string_property_definitions.h"
#include "bindfunctions.hpp"
+#include "nvds_obj_encode.h"
namespace py = pybind11;
using namespace std;
@@ -839,5 +840,49 @@ namespace pydeepstream {
},
"gst_buffer"_a, py::return_value_policy::reference,
pydsdoc::methodsDoc::nvds_measure_buffer_latency);
+
+ m.def("nvds_obj_enc_create_context",
+ [](int gpu_id) -> size_t {
+ auto handle = nvds_obj_enc_create_context(gpu_id);
+ std::cout << "nvds_obj_enc_create_context: " << handle << std::endl;
+ return reinterpret_cast<size_t>(handle);
+ }, py::return_value_policy::reference,
+ pydsdoc::methodsDoc::nvds_obj_enc_create_context);
+
+ m.def("nvds_obj_enc_process",
+ [](size_t ctx, NvDsObjEncUsrArgs *args,
+ size_t gst_buffer, NvDsObjectMeta *obj_meta, NvDsFrameMeta *frame_meta) -> bool {
+ auto *buffer = reinterpret_cast<GstBuffer *>(gst_buffer);
+ auto *handle = reinterpret_cast<NvDsObjEncCtxHandle>(ctx);
+ if (buffer == nullptr || handle == nullptr) {
+ std::cout << "buffer: " << buffer << " handle: " << handle << std::endl;
+ return false;
+ }
+
+ GstMapInfo inmap;
+ gst_buffer_map(buffer, &inmap, GST_MAP_READ);
+ auto *inputnvsurface = reinterpret_cast<NvBufSurface *>(inmap.data);
+ gst_buffer_unmap(buffer, &inmap);
+
+ return nvds_obj_enc_process(handle, args, inputnvsurface, obj_meta, frame_meta);
+ }, "ctx"_a, "args"_a, "gst_buffer"_a, "obj_meta"_a, "frame_meta"_a,
+ py::return_value_policy::reference,
+ pydsdoc::methodsDoc::nvds_obj_enc_process);
+
+ m.def("nvds_obj_enc_finish",
+ [](size_t ctx) {
+ auto *handle = reinterpret_cast<NvDsObjEncCtxHandle>(ctx);
+ if (handle != nullptr) {
+ nvds_obj_enc_finish(handle);
+ }
+ }, "ctx"_a, pydsdoc::methodsDoc::nvds_obj_enc_finish);
+
+ m.def("nvds_obj_enc_destroy_context",
+ [](size_t ctx) {
+ auto *handle = reinterpret_cast<NvDsObjEncCtxHandle>(ctx);
+ if (handle != nullptr) {
+ nvds_obj_enc_destroy_context(handle);
+ }
+ }, "ctx"_a, pydsdoc::methodsDoc::nvds_obj_enc_destroy_context);
}
}
diff --git a/bindings/src/pyds.cpp b/bindings/src/pyds.cpp
index 30d02ff..d1ac84e 100644
--- a/bindings/src/pyds.cpp
+++ b/bindings/src/pyds.cpp
@@ -63,6 +63,7 @@ namespace pydeepstream {
bindnvbufsurface(m);
bindnvdsinfer(m);
bindopticalflowmeta(m);
+ bindutils(m);
bindcustom(m);
} // end PYBIND11_MODULE(pyds, m)
diff --git a/bindings/src/utils.cpp b/bindings/src/utils.cpp
index 356ff8a..9bafe24 100644
--- a/bindings/src/utils.cpp
+++ b/bindings/src/utils.cpp
@@ -24,8 +24,13 @@
#include <functional>
#include <iostream>
#include "gstnvdsmeta.h"
+#include "pyds.hpp"
#include "nvdsmeta_schema.h"
#include "utils.hpp"
+#include "nvds_obj_encode.h"
+#include "bind_string_property_definitions.h"
+#include "../../docstrings/utilsdoc.h"
+
/**
* Specifies the type of function to copy meta data.
* It is passed the pointer to meta data and user specific data.
@@ -44,12 +49,53 @@ using COPYFUNC_SIG = gpointer(gpointer, gpointer);
*/
using RELEASEFUNC_SIG = void(gpointer, gpointer);
+namespace py = pybind11;
+
namespace pydeepstream {
pybind11::arg operator ""_a(const char *str, size_t len) {
return pybind11::arg(str);
}
}
+namespace pydeepstream {
+ void bindutils(py::module &m) {
+ py::class_<NvDsObjEncOutParams>(m, "NvDsObjEncOutParams",
+ pydsdoc::utilsdoc::NvDsObjEncOutParamsDoc::descr)
+ .def(py::init<>())
+ .def("outBuffer", [](const NvDsObjEncOutParams &self) {
+ // Use this if the C++ buffer should NOT be deallocated
+ // once Python no longer has a reference to it
+ py::capsule buffer_handle([](){});
+ return py::array(self.outLen, self.outBuffer, buffer_handle);
+ })
+ .def("cast", [](void *data) {
+ return (NvDsObjEncOutParams *) data;
+ },
+ py::return_value_policy::reference,
+ pydsdoc::utilsdoc::NvDsObjEncOutParamsDoc::cast);
+
+ py::class_<NvDsObjEncUsrArgs>(m, "NvDsObjEncUsrArgs",
+ pydsdoc::utilsdoc::NvDsObjEncUsrArgsDoc::descr)
+ .def(py::init<>())
+ .def_readwrite("saveImg", &NvDsObjEncUsrArgs::saveImg)
+ .def_readwrite("attachUsrMeta", &NvDsObjEncUsrArgs::attachUsrMeta)
+ .def_readwrite("scaleImg", &NvDsObjEncUsrArgs::scaleImg)
+ .def_readwrite("scaledWidth", &NvDsObjEncUsrArgs::scaledWidth)
+ .def_readwrite("scaledHeight", &NvDsObjEncUsrArgs::scaledHeight)
+ .def_property("fileNameImg", STRING_CHAR_ARRAY(NvDsObjEncUsrArgs, fileNameImg))
+ .def_readwrite("objNum", &NvDsObjEncUsrArgs::objNum)
+ .def_readwrite("quality", &NvDsObjEncUsrArgs::quality)
+ .def_readwrite("isFrame", &NvDsObjEncUsrArgs::isFrame)
+ .def_readwrite("calcEncodeTime", &NvDsObjEncUsrArgs::calcEncodeTime)
+ .def("cast",
+ [](void *data) {
+ return (NvDsObjEncUsrArgs *) data;
+ },
+ py::return_value_policy::reference,
+ pydsdoc::utilsdoc::NvDsObjEncUsrArgsDoc::cast);
+ }
+}
+
namespace pydeepstream::utils {
std::unordered_map<std::string, std::shared_ptr<char>> font_name_memory;
2.recompile and reinstall pyds
cd /deepstream_python_apps/bindings/build
make -j
# If you use jetson, please replace it with the correct `whl` name
pip3 install --force-reinstall ./pyds-1.1.11-py3-none-linux_x86_64.whl
3.How to use the bindings? Here we use deepstream-test1.py as an example.
You can also compare the native sample code, the code is located at
/opt/nvidia/deepstream/deepstream/sources/apps/sample_apps/deepstream-image-meta-test
diff --git a/apps/deepstream-test1/deepstream_test_1.py b/apps/deepstream-test1/deepstream_test_1.py
index 1367fb4..1d92204 100755
--- a/apps/deepstream-test1/deepstream_test_1.py
+++ b/apps/deepstream-test1/deepstream_test_1.py
@@ -34,9 +34,75 @@ PGIE_CLASS_ID_PERSON = 2
PGIE_CLASS_ID_ROADSIGN = 3
MUXER_BATCH_TIMEOUT_USEC = 33000
+def pgie_src_pad_buffer_probe(pad, info, u_data):
+ gst_buffer = info.get_buffer()
+ if not gst_buffer:
+ print("Unable to get GstBuffer ")
+ return
+
+ obj_ctx_handle = u_data
+ # Retrieve batch metadata from the gst_buffer
+ # Note that pyds.gst_buffer_get_nvds_batch_meta() expects the
+ # C address of gst_buffer as input, which is obtained with hash(gst_buffer)
+ batch_meta = pyds.gst_buffer_get_nvds_batch_meta(hash(gst_buffer))
+ l_frame = batch_meta.frame_meta_list
+ while l_frame is not None:
+ try:
+ # Note that l_frame.data needs a cast to pyds.NvDsFrameMeta
+ # The casting is done by pyds.NvDsFrameMeta.cast()
+ # The casting also keeps ownership of the underlying memory
+ # in the C code, so the Python garbage collector will leave
+ # it alone.
+ frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data)
+ frame_number=frame_meta.frame_num
+ if frame_number < 3:
+ frameData = pyds.NvDsObjEncUsrArgs()
+ frameData.isFrame = 1
+ frameData.saveImg = False
+ frameData.attachUsrMeta = True
+ frameData.scaleImg = False
+ frameData.scaledWidth = 0
+ frameData.scaledHeight = 0
+ frameData.quality = 80
+ frameData.calcEncodeTime = 1
+ ret = pyds.nvds_obj_enc_process (obj_ctx_handle, frameData, hash(gst_buffer), None, frame_meta)
+ except StopIteration:
+ break
+ l_obj=frame_meta.obj_meta_list
+ num_rects = 0
+ while l_obj is not None:
+ try:
+ # Casting l_obj.data to pyds.NvDsObjectMeta
+ # For demonstration, we will encode the first object in the frame
+ obj_meta=pyds.NvDsObjectMeta.cast(l_obj.data)
+ if frame_number < 3 and obj_meta.class_id == PGIE_CLASS_ID_VEHICLE and num_rects == 0:
+ num_rects += 1
+ objData = pyds.NvDsObjEncUsrArgs()
+ objData.saveImg = False
+ objData.attachUsrMeta = True
+ objData.scaleImg = False
+ objData.scaledWidth = 0
+ objData.scaledHeight = 0
+ objData.objNum = num_rects
+ objData.quality = 80
+ objData.calcEncodeTime = 1
+ ret = pyds.nvds_obj_enc_process (obj_ctx_handle, objData, hash(gst_buffer), obj_meta, frame_meta)
+ except StopIteration:
+ break
+ try:
+ l_obj=l_obj.next
+ except StopIteration:
+ break
+ try:
+ l_frame=l_frame.next
+ except StopIteration:
+ break
+ pyds.nvds_obj_enc_finish(obj_ctx_handle)
+ return Gst.PadProbeReturn.OK
+
def osd_sink_pad_buffer_probe(pad,info,u_data):
- frame_number=0
num_rects=0
+ frame_number=0
gst_buffer = info.get_buffer()
if not gst_buffer:
@@ -56,6 +122,21 @@ def osd_sink_pad_buffer_probe(pad,info,u_data):
# in the C code, so the Python garbage collector will leave
# it alone.
frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data)
+ frame_number=frame_meta.frame_num
+ fusrMetaList = frame_meta.frame_user_meta_list
+ while fusrMetaList is not None:
+ try:
+ fuser_meta = pyds.NvDsUserMeta.cast(fusrMetaList.data)
+ if fuser_meta.base_meta.meta_type == pyds.NvDsMetaType.NVDS_CROP_IMAGE_META:
+ fenc_output = pyds.NvDsObjEncOutParams.cast(fuser_meta.user_meta_data)
+ foutput = fenc_output.outBuffer()
+ foutput.tofile(f"{frame_number}-out.jpg")
+ except StopIteration:
+ break
+ try:
+ fusrMetaList = fusrMetaList.next
+ except StopIteration:
+ break
except StopIteration:
break
@@ -66,17 +147,31 @@ def osd_sink_pad_buffer_probe(pad,info,u_data):
PGIE_CLASS_ID_BICYCLE:0,
PGIE_CLASS_ID_ROADSIGN:0
}
- frame_number=frame_meta.frame_num
num_rects = frame_meta.num_obj_meta
l_obj=frame_meta.obj_meta_list
while l_obj is not None:
try:
# Casting l_obj.data to pyds.NvDsObjectMeta
obj_meta=pyds.NvDsObjectMeta.cast(l_obj.data)
+ obj_counter[obj_meta.class_id] += 1
+ obj_meta.rect_params.border_color.set(0.0, 0.0, 1.0, 0.8) #0.8 is alpha (opacity)
+
+ usrMetaList = obj_meta.obj_user_meta_list
+ while usrMetaList is not None:
+ try:
+ user_meta = pyds.NvDsUserMeta.cast(usrMetaList.data)
+ if user_meta.base_meta.meta_type == pyds.NvDsMetaType.NVDS_CROP_IMAGE_META:
+ enc_output = pyds.NvDsObjEncOutParams.cast(user_meta.user_meta_data)
+ output = enc_output.outBuffer()
+ output.tofile(f"{frame_number}-{num_rects}-out.jpg")
+ except StopIteration:
+ break
+ try:
+ usrMetaList = usrMetaList.next
+ except StopIteration:
+ break
except StopIteration:
break
- obj_counter[obj_meta.class_id] += 1
- obj_meta.rect_params.border_color.set(0.0, 0.0, 1.0, 0.8) #0.8 is alpha (opacity)
try:
l_obj=l_obj.next
except StopIteration:
@@ -191,7 +286,8 @@ def main(args):
sink = Gst.ElementFactory.make("nv3dsink", "nv3d-sink")
else:
print("Creating EGLSink \n")
- sink = Gst.ElementFactory.make("nveglglessink", "nvvideo-renderer")
+ sink = Gst.ElementFactory.make("fakesink", "nv3d-sink")
+ #sink = Gst.ElementFactory.make("nveglglessink", "nvvideo-renderer")
if not sink:
sys.stderr.write(" Unable to create egl sink \n")
@@ -222,6 +318,9 @@ def main(args):
source.link(h264parser)
h264parser.link(decoder)
+ # Create Context for Object Encoding.
+ # Takes GPU ID as a parameter.
+ obj_ctx_handle = pyds.nvds_obj_enc_create_context (0)
sinkpad = streammux.request_pad_simple("sink_0")
if not sinkpad:
sys.stderr.write(" Unable to get the sink pad of streammux \n")
@@ -240,6 +339,11 @@ def main(args):
bus.add_signal_watch()
bus.connect ("message", bus_call, loop)
+ pgiesrcpad = pgie.get_static_pad("src")
+ if not pgiesrcpad:
+ sys.stderr.write(" Unable to get src pad of pgie \n")
+ pgiesrcpad.add_probe(Gst.PadProbeType.BUFFER, pgie_src_pad_buffer_probe, obj_ctx_handle)
+
# Lets add probe to get informed of the meta data generated, we add probe to
# the sink pad of the osd element, since by that time, the buffer would have
# had got all the metadata.
@@ -258,6 +362,8 @@ def main(args):
pass
# cleanup
pipeline.set_state(Gst.State.NULL)
+ # Destroy context for Object Encoding
+ pyds.nvds_obj_enc_destroy_context (obj_ctx_handle)
if __name__ == '__main__':