diff --git a/Makefile b/Makefile index cc922dd1..2afe5a24 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ # Copyright © 2023 Intel Corporation. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -.PHONY: build-dlstreamer build-dlstreamer-realsense build-grpc-python build-grpc-go build-python-apps build-telegraf build-gst-capi +.PHONY: build-dlstreamer build-dlstreamer-realsense build-grpc-python build-grpc-go build-python-apps build-telegraf build-capi_face_detection build-capi_yolov5 .PHONY: run-camera-simulator run-telegraf .PHONY: clean-grpc-go clean-segmentation clean-ovms-server clean-ovms clean-all clean-results clean-telegraf clean-models clean-webcam -.PHONY: clean clean-simulator clean-object-detection clean-classification clean-gst clean-capi_face_detection +.PHONY: clean clean-simulator clean-object-detection clean-classification clean-gst clean-capi_face_detection clean-capi_yolov5 .PHONY: list-profiles .PHONY: unit-test-profile-launcher build-profile-launcher profile-launcher-status clean-profile-launcher webcam-rtsp .PHONY: clean-test @@ -49,7 +49,7 @@ build-ovms-server: HTTPS_PROXY=${HTTPS_PROXY} HTTP_PROXY=${HTTP_PROXY} docker pull openvino/model_server:2023.1-gpu sudo docker build --build-arg HTTPS_PROXY=${HTTPS_PROXY} --build-arg HTTP_PROXY=${HTTP_PROXY} -f configs/opencv-ovms/models/2022/Dockerfile.updateDevice -t update_config:dev configs/opencv-ovms/models/2022/. -clean-profile-launcher: clean-grpc-python clean-grpc-go clean-segmentation clean-object-detection clean-classification clean-gst clean-capi_face_detection clean-test +clean-profile-launcher: clean-grpc-python clean-grpc-go clean-segmentation clean-object-detection clean-classification clean-gst clean-capi_face_detection clean-test clean-capi_yolov5 @echo "containers launched by profile-launcher are cleaned up." @pkill -9 profile-launcher || true @@ -86,6 +86,9 @@ clean-ovms: clean-profile-launcher clean-ovms-server clean-capi_face_detection: ./clean-containers.sh capi_face_detection +clean-capi_yolov5: + ./clean-containers.sh capi_yolov5 + clean-telegraf: ./clean-containers.sh influxdb2 ./clean-containers.sh telegraf @@ -131,8 +134,11 @@ build-grpc-go: build-profile-launcher build-python-apps: build-profile-launcher cd configs/opencv-ovms/demos && make build -build-gst-capi: build-profile-launcher - cd configs/opencv-ovms/gst_capi && $(MAKE) build +build-capi_face_detection: build-profile-launcher + cd configs/opencv-ovms/gst_capi && $(MAKE) build_face_detection + +build-capi_yolov5: build-profile-launcher + cd configs/opencv-ovms/gst_capi && $(MAKE) build_capi_yolov5 clean-docs: rm -rf docs/ diff --git a/configs/opencv-ovms/cmd_client/res/capi_face_detection/configuration.yaml b/configs/opencv-ovms/cmd_client/res/capi_face_detection/configuration.yaml index 7f945ca5..e62a2ce8 100644 --- a/configs/opencv-ovms/cmd_client/res/capi_face_detection/configuration.yaml +++ b/configs/opencv-ovms/cmd_client/res/capi_face_detection/configuration.yaml @@ -11,7 +11,7 @@ OvmsClient: - "$RUN_PATH/configs/opencv-ovms/gst_capi/extensions:/home/intel/gst-ovms/extensions" - "$RUN_PATH/results:/tmp/results" - "$RUN_PATH/configs/opencv-ovms/models/2022/:/home/intel/gst-ovms/models" - PipelineScript: ./run_face_detection.sh + PipelineScript: ./run_gst_capi.sh PipelineInputArgs: "" # space delimited like we run the script in command and take those input arguments EnvironmentVariableFiles: - capi_face_detection.env diff --git a/configs/opencv-ovms/cmd_client/res/capi_yolov5/configuration.yaml b/configs/opencv-ovms/cmd_client/res/capi_yolov5/configuration.yaml new file mode 100644 index 00000000..8a2be79d --- /dev/null +++ b/configs/opencv-ovms/cmd_client/res/capi_yolov5/configuration.yaml @@ -0,0 +1,17 @@ +OvmsSingleContainer: true +OvmsClient: + DockerLauncher: + Script: docker-launcher.sh + DockerImage: openvino/model_server-capi-gst-ovms:latest + ContainerName: capi_yolov5 + Volumes: + - "$cl_cache_dir:/home/intel/gst-ovms/.cl-cache" + - /tmp/.X11-unix:/tmp/.X11-unix + - "$RUN_PATH/sample-media/:/home/intel/gst-ovms/vids" + - "$RUN_PATH/configs/opencv-ovms/gst_capi/extensions:/home/intel/gst-ovms/extensions" + - "$RUN_PATH/results:/tmp/results" + - "$RUN_PATH/configs/opencv-ovms/models/2022/:/models" + PipelineScript: ./run_gst_capi.sh + PipelineInputArgs: "" # space delimited like we run the script in command and take those input arguments + EnvironmentVariableFiles: + - capi_yolov5.env diff --git a/configs/opencv-ovms/envs/capi_yolov5.env b/configs/opencv-ovms/envs/capi_yolov5.env new file mode 100644 index 00000000..d6c98ea2 --- /dev/null +++ b/configs/opencv-ovms/envs/capi_yolov5.env @@ -0,0 +1,11 @@ +RENDER_PORTRAIT_MODE=0 +GST_DEBUG=0 +USE_ONEVPL=1 +PIPELINE_EXEC_PATH=pipelines/capi_yolov5/capi_yolov5 +GST_VAAPI_DRM_DEVICE=/dev/dri/renderD128 +TARGET_GPU_DEVICE=--privileged +LOG_LEVEL=0 +RENDER_MODE=1 +cl_cache_dir=/home/intel/gst-ovms/.cl-cache +WINDOW_WIDTH=1920 +WINDOW_HEIGHT=1080 \ No newline at end of file diff --git a/configs/opencv-ovms/grpc_go/main.go b/configs/opencv-ovms/grpc_go/main.go index 0de61a5f..ad5dbb58 100644 --- a/configs/opencv-ovms/grpc_go/main.go +++ b/configs/opencv-ovms/grpc_go/main.go @@ -69,9 +69,9 @@ func main() { func runModelServer(client *grpc_client.GRPCInferenceServiceClient, webcam *gocv.VideoCapture, img *gocv.Mat, modelname string, modelVersion string, stream *mjpeg.Stream, camWidth float32, camHeight float32) { var aggregateLatencyAfterInfer float64 - var aggregateLatencyAfterFinalProcess float64 var frameNum float64 + initTime := float64(time.Now().UnixMilli()) for webcam.IsOpened() { if ok := webcam.Read(img); !ok { // retry once after 1 millisecond @@ -135,8 +135,7 @@ func runModelServer(client *grpc_client.GRPCInferenceServiceClient, webcam *gocv // Print after processing latency afterFinalProcess := float64(time.Now().UnixMilli()) processTime := afterFinalProcess - start - aggregateLatencyAfterFinalProcess += processTime - avgFps := frameNum / (aggregateLatencyAfterFinalProcess / 1000.0) + avgFps := frameNum / ((afterFinalProcess - initTime) / 1000.0) averageFPSStr := fmt.Sprintf("%v\n", avgFps) fmt.Printf("Processing time: %v ms; fps: %s", processTime, averageFPSStr) diff --git a/configs/opencv-ovms/gst_capi/Dockerfile.ovms-capi-gst b/configs/opencv-ovms/gst_capi/Dockerfile.ovms-capi-gst index 2d4e31d4..51af7573 100644 --- a/configs/opencv-ovms/gst_capi/Dockerfile.ovms-capi-gst +++ b/configs/opencv-ovms/gst_capi/Dockerfile.ovms-capi-gst @@ -77,8 +77,9 @@ RUN DEBIAN_FRONTEND=noninteractive apt install -y jq -y && \ ENV LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/:/usr/local/lib/x86_64-linux-gnu/:/usr/local/lib:/ovms/lib:$LD_LIBRARY_PATH COPY gst_capi/pipelines /home/intel/gst-ovms/pipelines COPY gst_capi/launch-pipeline.sh /home/intel/gst-ovms/launch-pipeline.sh -COPY gst_capi/run_face_detection.sh /home/intel/gst-ovms/run_face_detection.sh -RUN cd /home/intel/gst-ovms/pipelines; chmod +x build-demos.sh; ./build-demos.sh +COPY gst_capi/run_gst_capi.sh /home/intel/gst-ovms/run_gst_capi.sh +ARG PIPELINE_NAME +RUN echo $PIPELINE_NAME; cd /home/intel/gst-ovms/pipelines/$PIPELINE_NAME; make build USER ovms ENV LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu/:/usr/local/lib/x86_64-linux-gnu/:/usr/local/lib:/ovms/lib:$LD_LIBRARY_PATH diff --git a/configs/opencv-ovms/gst_capi/Makefile b/configs/opencv-ovms/gst_capi/Makefile index dd8b83a0..42b8f262 100644 --- a/configs/opencv-ovms/gst_capi/Makefile +++ b/configs/opencv-ovms/gst_capi/Makefile @@ -1,17 +1,29 @@ # Copyright © 2023 Intel Corporation. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -.PHONY: build +.PHONY: build_face_detection build_capi_yolov5 OVMS_CPP_DOCKER_IMAGE ?= openvino/model_server OVMS_CPP_IMAGE_TAG ?= latest -build: +build_face_detection: # Build CAPI docker image docker build $(NO_CACHE_OPTION) -f Dockerfile.ovms-capi-gst ../ \ --build-arg http_proxy=$(HTTP_PROXY) \ --build-arg https_proxy="$(HTTPS_PROXY)" \ --build-arg no_proxy=$(NO_PROXY) \ --build-arg BASE_IMAGE=ubuntu:22.04 \ + --build-arg PIPELINE_NAME=face_detection \ + --progress=plain \ + -t $(OVMS_CPP_DOCKER_IMAGE)-capi-gst-ovms:$(OVMS_CPP_IMAGE_TAG) + +build_capi_yolov5: + # Build CAPI docker image + docker build $(NO_CACHE_OPTION) -f Dockerfile.ovms-capi-gst ../ \ + --build-arg http_proxy=$(HTTP_PROXY) \ + --build-arg https_proxy="$(HTTPS_PROXY)" \ + --build-arg no_proxy=$(NO_PROXY) \ + --build-arg BASE_IMAGE=ubuntu:22.04 \ + --build-arg PIPELINE_NAME=capi_yolov5 \ --progress=plain \ -t $(OVMS_CPP_DOCKER_IMAGE)-capi-gst-ovms:$(OVMS_CPP_IMAGE_TAG) \ No newline at end of file diff --git a/configs/opencv-ovms/gst_capi/launch-pipeline.sh b/configs/opencv-ovms/gst_capi/launch-pipeline.sh index 6529b641..819383d8 100755 --- a/configs/opencv-ovms/gst_capi/launch-pipeline.sh +++ b/configs/opencv-ovms/gst_capi/launch-pipeline.sh @@ -13,9 +13,11 @@ INPUTSRC=$2 USE_ONEVPL=$3 RENDER_MODE=$4 RENDER_PORTRAIT_MODE=$5 +WINDOW_WIDTH=$6 +WINDOW_HEIGHT=$7 # Obtaining codec_type (avc or hevc) is_avc=`gst-discoverer-1.0 "$INPUTSRC" | grep -i h.264 | wc -l` -echo "./$PIPELINE_EXEC_PATH $INPUTSRC $USE_ONEVPL $RENDER_MODE $RENDER_PORTRAIT_MODE $is_avc" -./"$PIPELINE_EXEC_PATH" "$INPUTSRC" "$USE_ONEVPL" "$RENDER_MODE" "$RENDER_PORTRAIT_MODE" "$is_avc" \ No newline at end of file +echo "./$PIPELINE_EXEC_PATH $INPUTSRC $USE_ONEVPL $RENDER_MODE $RENDER_PORTRAIT_MODE $is_avc $WINDOW_WIDTH $WINDOW_HEIGHT" +./"$PIPELINE_EXEC_PATH" "$INPUTSRC" "$USE_ONEVPL" "$RENDER_MODE" "$RENDER_PORTRAIT_MODE" "$is_avc" "$WINDOW_WIDTH" "$WINDOW_HEIGHT" \ No newline at end of file diff --git a/configs/opencv-ovms/gst_capi/pipelines/build-demos.sh b/configs/opencv-ovms/gst_capi/pipelines/build-demos.sh deleted file mode 100755 index 14da8292..00000000 --- a/configs/opencv-ovms/gst_capi/pipelines/build-demos.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2023 Intel Corporation. -# -# SPDX-License-Identifier: Apache-2.0 -# - -for fld in $(find . -maxdepth 1 -type d) -do - if [ $fld != "." ]; then - ( - cd $fld; ls -l; make || true; - ) - fi - -done \ No newline at end of file diff --git a/configs/opencv-ovms/gst_capi/pipelines/capi_yolov5/Makefile b/configs/opencv-ovms/gst_capi/pipelines/capi_yolov5/Makefile new file mode 100644 index 00000000..945e194f --- /dev/null +++ b/configs/opencv-ovms/gst_capi/pipelines/capi_yolov5/Makefile @@ -0,0 +1,23 @@ +# +# Copyright (c) 2023 Intel Corporation +# +# 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. +# + +.PHONY: build + +CV_LIBS = -L/usr/lib/x86_64-linux-gnu/ -L/usr/local/lib/x86_64-linux-gnu/ -L/ovms/lib/ +CV_INCLUDES = -I/usr/lib/include/opencv4 + +build: + g++ main.cpp -I/usr/include/gstreamer-1.0/usr/lib/x86_64-linux-gnu/ -I/usr/local/include/gstreamer-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/gstreamer-1.0/ -I/ovms/include $(CV_INCLUDES) $(CV_LIBS) -L/usr/lib/x86_64-linux-gnu/gstreamer-1.0 -lgstbase-1.0 -lgobject-2.0 -lglib-2.0 -lgstreamer-1.0 -lgstapp-1.0 -lopencv_imgcodecs -lopencv_imgproc -lopencv_videoio -lopencv_core -lopencv_videoio_gstreamer -lovms_shared -lopencv_highgui -lpthread -fPIC --std=c++17 -o capi_yolov5 \ No newline at end of file diff --git a/configs/opencv-ovms/gst_capi/pipelines/capi_yolov5/main.cpp b/configs/opencv-ovms/gst_capi/pipelines/capi_yolov5/main.cpp new file mode 100644 index 00000000..7e7fffb9 --- /dev/null +++ b/configs/opencv-ovms/gst_capi/pipelines/capi_yolov5/main.cpp @@ -0,0 +1,1095 @@ +//***************************************************************************** +// Copyright 2023 Intel Corporation +// +// 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. +//***************************************************************************** +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// Utilized for GStramer hardware accelerated decode and pre-preprocessing +#include +#include +#include + +// Utilized for OpenCV based Rendering only +#include +#include +#include +#include + +// Utilized for infernece output layer post-processing +#include + +#include "ovms.h" // NOLINT + +using namespace std; +using namespace cv; + +std::mutex _mtx; +std::mutex _infMtx; +std::mutex _drawingMtx; +std::condition_variable _cvAllDecodersInitd; +bool _allDecodersInitd = false; + +typedef struct DetectedResult { + int frameId; + float x; + float y; + float width; + float height; + float confidence; + int classId; + std::string classText; +} DetectedResult; + +std::vector _detectedResults; +constexpr size_t DIM_COUNT = 4; +constexpr size_t SHAPE[DIM_COUNT] = {1, 416, 416, 3}; + +// Anchors by region/output layer +const float anchor_width = 1920.0; +const float anchor_height = 1080.0; +const float anchors[] = { + 10.0, + 13.0, + 16.0, + 30.0, + 33.0, + 23.0, + 30.0, + 61.0, + 62.0, + 45.0, + 59.0, + 119.0, + 116.0, + 90.0, + 156.0, + 198.0, + 373.0, + 326.0 +}; + +const std::string labels[80] = { + "person", + "bicycle", + "car", + "motorbike", + "aeroplane", + "bus", + "train", + "truck", + "boat", + "traffic light", + "fire hydrant", + "stop sign", + "parking meter", + "bench", + "bird", + "cat", + "dog", + "horse", + "sheep", + "cow", + "elephant", + "bear", + "zebra", + "giraffe", + "backpack", + "umbrella", + "handbag", + "tie", + "suitcase", + "frisbee", + "skis", + "snowboard", + "sports ball", + "kite", + "baseball bat", + "baseball glove", + "skateboard", + "surfboard", + "tennis racket", + "bottle", + "wine glass", + "cup", + "fork", + "knife", + "spoon", + "bowl", + "banana", + "apple", + "sandwich", + "orange", + "broccoli", + "carrot", + "hot dog", + "pizza", + "donut", + "cake", + "chair", + "sofa", + "pottedplant", + "bed", + "diningtable", + "toilet", + "tvmonitor", + "laptop", + "mouse", + "remote", + "keyboard", + "cell phone", + "microwave", + "oven", + "toaster", + "sink", + "refrigerator", + "book", + "clock", + "vase", + "scissors", + "teddy bear", + "hair drier", + "toothbrush" +}; + +class MediaPipelineServiceInterface { +public: + enum VIDEO_TYPE { + H265, + H264 + }; + + virtual ~MediaPipelineServiceInterface() {} + virtual const std::string getVideoDecodedPreProcessedPipeline(std::string mediaLocation, VIDEO_TYPE videoType, int video_width, int video_height, bool use_onevpl) = 0; + virtual const std::string getBroadcastPipeline() = 0; + virtual const std::string getRecordingPipeline() = 0; + + const std::string updateVideoDecodedPreProcessedPipeline(int video_width, int video_height, bool use_onevpl) + { + return getVideoDecodedPreProcessedPipeline(m_mediaLocation, m_videoType, video_width, video_height, use_onevpl); + } + +protected: + std::string m_mediaLocation; + VIDEO_TYPE m_videoType; + int m_videoWidth; + int m_videoHeight; +}; + +OVMS_Server* _srv; +OVMS_ServerSettings* _serverSettings = 0; +OVMS_ModelsSettings* _modelsSettings = 0; +int _server_grpc_port; +int _server_http_port; + +std::string _videoStreamPipeline; +MediaPipelineServiceInterface::VIDEO_TYPE _videoType = MediaPipelineServiceInterface::VIDEO_TYPE::H264; +int _detectorModel = 0; +bool _render = 0; +bool _use_onevpl = 0; +bool _renderPortrait = 0; +cv::Mat _presentationImg; +int _video_input_width = 0; // Get from media _img +int _video_input_height = 0; // Get from media _img +std::vector _vidcaps; +int _window_width = 1920; // default value +int _window_height = 1080; // default value + +class GStreamerMediaPipelineService : public MediaPipelineServiceInterface { +public: + const std::string getVideoDecodedPreProcessedPipeline(std::string mediaLocation, VIDEO_TYPE videoType, int video_width, int video_height, bool use_onevpl) { + m_mediaLocation = mediaLocation; + m_videoType = videoType; + m_videoWidth = video_width; + m_videoHeight = video_height; + + if (mediaLocation.find("rtsp") != std::string::npos ) { + // video/x-raw(memory:VASurface),format=NV12 + switch (videoType) + { + case H264: + if (use_onevpl) + return "rtspsrc location=" + mediaLocation + " ! rtph264depay ! h264parse ! " + + "msdkh264dec ! msdkvpp scaling-mode=lowpower ! " + + "video/x-raw, width=" + std::to_string(video_width) + + ", height=" + std::to_string(video_height) + " ! videoconvert ! video/x-raw,format=BGR ! queue ! appsink drop=1 sync=0"; + else + return "rtspsrc location=" + mediaLocation + " ! rtph264depay ! vaapidecodebin ! video/x-raw(memory:VASurface),format=NV12 ! vaapipostproc" + + " width=" + std::to_string(video_width) + + " height=" + std::to_string(video_height) + + " scale-method=fast ! videoconvert ! video/x-raw,format=BGR ! queue ! appsink drop=1 sync=0"; + case H265: + if (use_onevpl) + return "rtspsrc location=" + mediaLocation + " ! rtph265depay ! h265parse ! " + + "msdkh265dec ! " + + "msdkvpp scaling-mode=lowpower ! " + + "video/x-raw, width=" + std::to_string(video_width) + + ", height=" + std::to_string(video_height) + " ! videoconvert ! video/x-raw,format=BGR ! queue ! appsink drop=1 sync=0"; + else + return "rtspsrc location=" + mediaLocation + " ! rtph265depay ! vaapidecodebin ! vaapipostproc" + + " width=" + std::to_string(video_width) + + " height=" + std::to_string(video_height) + + " scale-method=fast ! videoconvert ! video/x-raw,format=BGR ! appsink sync=0 drop=1"; + default: + std::cout << "Video type not supported!" << std::endl; + return ""; + } + } + else if (mediaLocation.find(".mp4") != std::string::npos ) { + switch (videoType) + { + case H264: + if (use_onevpl) + return "filesrc location=" + mediaLocation + " ! qtdemux ! h264parse ! " + + "msdkh264dec ! msdkvpp scaling-mode=lowpower ! " + + "video/x-raw, width=" + std::to_string(video_width) + ", height=" + std::to_string(video_height) + + " ! videoconvert ! video/x-raw,format=BGR ! queue ! appsink drop=1 sync=0"; + else + return "filesrc location=" + mediaLocation + " ! qtdemux ! h264parse ! vaapidecodebin ! vaapipostproc" + + " width=" + std::to_string(video_width) + + " height=" + std::to_string(video_height) + + " scale-method=fast ! videoconvert ! video/x-raw,format=BGR ! appsink drop=1"; + case H265: + if (use_onevpl) + return "filesrc location=" + mediaLocation + " ! qtdemux ! h265parse ! " + + "msdkh265dec ! msdkvpp scaling-mode=lowpower ! " + + " video/x-raw, width=" + std::to_string(video_width) + ", height=" + std::to_string(video_height) + + " ! videoconvert ! video/x-raw,format=BGR ! queue ! appsink drop=1 sync=0"; + else + return "filesrc location=" + mediaLocation + " ! qtdemux ! h265parse ! vaapidecodebin ! vaapipostproc" + + " width=" + std::to_string(video_width) + + " height=" + std::to_string(video_height) + + " scale-method=fast ! videoconvert ! video/x-raw,format=BGR ! appsink drop=1"; + default: + std::cout << "Video type not supported!" << std::endl; + return ""; + } + } + else { + std::cout << "Unknown media source specified " << mediaLocation << " !!" << std::endl; + return ""; + } + } + + const std::string getBroadcastPipeline() { + return "videotestsrc ! videoconvert,format=BGR ! video/x-raw ! appsink drop=1"; + } + + const std::string getRecordingPipeline() { + return "videotestsrc ! videoconvert,format=BGR ! video/x-raw ! appsink drop=1"; + } +protected: + +}; + +class ObjectDetectionInterface { +public: + const static size_t MODEL_DIM_COUNT = 4; + int64_t model_input_shape[MODEL_DIM_COUNT] = { 0 }; + + virtual ~ObjectDetectionInterface() {} + virtual const char* getModelName() = 0; + virtual const uint64_t getModelVersion() = 0; + virtual const char* getModelInputName() = 0; + virtual const size_t getModelDimCount() = 0; + virtual const std::vector getModelInputShape() = 0; + virtual const std::string getClassLabelText(int classIndex) = 0; + + virtual void postprocess(const int64_t* output_shape, const void* voutputData, const size_t *input_shape, const size_t bytesize, const uint32_t dimCount, std::vector &detectedResults) = 0; + virtual void displayGUIInferenceResults(cv::Mat analytics_frame, std::vector &results, int latency, int througput) = 0; + static inline float sigmoid(float x) { + return 1.f / (1.f + std::exp(-x)); + } + + static inline float linear(float x) { + return x; + } + + // ObjectDetectionInterface() {} + + double intersectionOverUnion(const DetectedResult& o1, const DetectedResult& o2) { + double overlappingWidth = std::fmin(o1.x + o1.width, o2.x + o2.width) - std::fmax(o1.x, o2.x); + double overlappingHeight = std::fmin(o1.y + o1.height, o2.y + o2.height) - std::fmax(o1.y, o2.y); + double intersectionArea = (overlappingWidth < 0 || overlappingHeight < 0) ? 0 : overlappingHeight * overlappingWidth; + double unionArea = o1.width * o1.height + o2.width * o2.height - intersectionArea; + return intersectionArea / unionArea; + } + + int calculateEntryIndex(int totalCells, int lcoords, size_t lclasses, int location, int entry) { + int n = location / totalCells; + int loc = location % totalCells; + return (n * (lcoords + lclasses) + entry) * totalCells + loc; + } + + void postprocess(std::vector &detectedResults, std::vector &outDetectedResults) + { + if (useAdvancedPostprocessing) { + // Advanced postprocessing + // Checking IOU threshold conformance + // For every i-th object we're finding all objects it intersects with, and comparing confidence + // If i-th object has greater confidence than all others, we include it into result + for (const auto& obj1 : detectedResults) { + bool isGoodResult = true; + for (const auto& obj2 : detectedResults) { + if (obj1.classId == obj2.classId && obj1.confidence < obj2.confidence && + intersectionOverUnion(obj1, obj2) >= boxiou_threshold) { // if obj1 is the same as obj2, condition + // expression will evaluate to false anyway + isGoodResult = false; + break; + } + } + if (isGoodResult) { + outDetectedResults.push_back(obj1); + } + } + } else { + // Classic postprocessing + std::sort(detectedResults.begin(), detectedResults.end(), [](const DetectedResult& x, const DetectedResult& y) { + return x.confidence > y.confidence; + }); + for (size_t i = 0; i < detectedResults.size(); ++i) { + if (detectedResults[i].confidence == 0) + continue; + for (size_t j = i + 1; j < detectedResults.size(); ++j) + if (intersectionOverUnion(detectedResults[i], detectedResults[j]) >= boxiou_threshold) + detectedResults[j].confidence = 0; + outDetectedResults.push_back(detectedResults[i]); + } //end for + } // end if + } // end postprocess filter + + +protected: + float confidence_threshold = .5; + float boxiou_threshold = .2; + float iou_threshold = 0.2; + int classes = 80; + bool useAdvancedPostprocessing = true; + +}; + +class Yolov5 : public ObjectDetectionInterface { +public: + + Yolov5() { + confidence_threshold = .7; + classes = 80; + std::vector vmodel_input_shape = getModelInputShape(); + std::copy(vmodel_input_shape.begin(), vmodel_input_shape.end(), model_input_shape); + } + + const char* getModelName() { + return MODEL_NAME; + } + + const uint64_t getModelVersion() { + return MODEL_VERSION; + } + + const char* getModelInputName() { + return INPUT_NAME; + } + + const size_t getModelDimCount() { + return MODEL_DIM_COUNT; + } + + const std::vector getModelInputShape() { + std::vector shape{1,416,416,3}; + return shape; + } + + const std::string getClassLabelText(int classIndex) { + if (classIndex > 80) + return ""; + + return labels[classIndex].c_str(); + } + + void postprocess(const int64_t* output_shape, const void* voutputData, const size_t *input_shape, const size_t bytesize, const uint32_t dimCount, std::vector &detectedResults) + { + if (!voutputData || !output_shape) { + // nothing to do + return; + } + + const int regionCoordsCount = dimCount; + const int sideH = output_shape[2]; // NCHW + const int sideW = output_shape[3]; // NCHW + const int regionNum = 3; + const int scaleH = input_shape[1]; // NHWC + const int scaleW = input_shape[2]; // NHWC + + auto entriesNum = sideW * sideH; + const float* outData = reinterpret_cast(voutputData); + int original_im_w = _window_width; //TODO + int original_im_h = _window_height; + + auto postprocessRawData = sigmoid; //sigmoid or linear + + for (int i = 0; i < entriesNum; ++i) { + int row = i / sideW; + int col = i % sideW; + + for (int n = 0; n < regionNum; ++n) { + + int obj_index = calculateEntryIndex(entriesNum, regionCoordsCount, classes + 1 /* + confidence byte */, n * entriesNum + i,regionCoordsCount); + int box_index = calculateEntryIndex(entriesNum, regionCoordsCount, classes + 1, n * entriesNum + i, 0); + float outdata = outData[obj_index]; + float scale = postprocessRawData(outData[obj_index]); + + if (scale >= confidence_threshold) { + float x, y; + x = static_cast((col + postprocessRawData(outData[box_index + 0 * entriesNum])) / sideW * original_im_w); + y = static_cast((row + postprocessRawData(outData[box_index + 1 * entriesNum])) / sideH * original_im_h); + float height = static_cast(std::exp(outData[box_index + 3 * entriesNum]) * anchors[2 * n + 1] * original_im_h / scaleH ); + float width = static_cast(std::exp(outData[box_index + 2 * entriesNum]) * anchors[2 * n] * original_im_w / scaleW ); + + DetectedResult obj; + obj.x = std::clamp(x - width / 2, 0.f, static_cast(original_im_w)); + obj.y = std::clamp(y - height / 2, 0.f, static_cast(original_im_h)); + obj.width = std::clamp(width, 0.f, static_cast(original_im_w - obj.x)); + obj.height = std::clamp(height, 0.f, static_cast(original_im_h - obj.y)); + + for (int j = 0; j < classes; ++j) { + int class_index = calculateEntryIndex(entriesNum, regionCoordsCount, classes + 1, n * entriesNum + i, regionCoordsCount + 1 + j); + float prob = scale * postprocessRawData(outData[class_index]); + + if (prob >= confidence_threshold) { + obj.confidence = prob; + obj.classId = j; + obj.classText = getClassLabelText(j); + detectedResults.push_back(obj); + } + } + } // end else + } // end for + } // end for + } + // End of Yolov5 Post-Processing + + void displayGUIInferenceResults(cv::Mat analytics_frame, std::vector &results, int latency, int througput) + { + auto ttid = std::this_thread::get_id(); + std::stringstream ss; + ss << ttid; + std::string tid = ss.str(); + + std::vector inputShape; + inputShape = getModelInputShape(); + + float scaler_w = (float)_window_width / (float)inputShape[1]; + float scaler_h = (float)_window_height / (float)inputShape[2]; + + scaler_w *= (anchor_width / _window_width); + scaler_h *= (anchor_height / _window_height); + + for (auto & obj : results) { + const float x0 = obj.x; + const float y0 = obj.y; + const float x1 = obj.x + obj.width; + const float y1 = obj.y + obj.height; + const float xc = (x1-x0)/2.0f + x0; + const float yc = (y1-y0)/2.0f + y0; + + const float scaled_x0 = std::clamp(xc-obj.width*scaler_w / 2.0f, 0.f, static_cast(_window_width)); + const float scaled_y0 = std::clamp(yc-obj.height*scaler_h / 2.0f, 0.f, static_cast(_window_height)); + + const float scaled_x1 = std::clamp(xc+obj.width*scaler_w / 2.0f, 0.f, static_cast(_window_width)); + const float scaled_y1 = std::clamp(yc+obj.height*scaler_h / 2.0f, 0.f, static_cast(_window_height)); + + cv::rectangle( analytics_frame, + cv::Point( (int)(scaled_x0),(int)(scaled_y0) ), + cv::Point( (int)(scaled_x1), (int)(scaled_y1)), + cv::Scalar(255, 0, 0), + 2, cv::LINE_8 ); + } // end for + + cv::Mat presenter; + + { + std::lock_guard lock(_drawingMtx); + cv::imshow("OpenVINO Results " + tid, analytics_frame); + cv::waitKey(1); + } + } + + + private: + const char* MODEL_NAME = "yolov5s"; + const uint64_t MODEL_VERSION = 0; + const char* INPUT_NAME = "images"; + }; + +GStreamerMediaPipelineService* _mediaService = NULL; +std::string _user_request; + +namespace { +volatile sig_atomic_t shutdown_request = 0; +} + +bool stringIsInteger(std::string strInput) { + std::string::const_iterator it = strInput.begin(); + while (it != strInput.end() && std::isdigit(*it)) ++it; + return !strInput.empty() && it == strInput.end(); +} + +bool setActiveModel(int detectionType, ObjectDetectionInterface** objDet) +{ + if (objDet == NULL) + return false; + *objDet = new Yolov5(); + return true; +} + +static void onInterrupt(int status) { + shutdown_request = 1; +} + +static void onTerminate(int status) { + shutdown_request = 1; +} + +static void onIllegal(int status) { + shutdown_request = 2; +} + +static void installSignalHandlers() { + static struct sigaction sigIntHandler; + sigIntHandler.sa_handler = onInterrupt; + sigemptyset(&sigIntHandler.sa_mask); + sigIntHandler.sa_flags = 0; + sigaction(SIGINT, &sigIntHandler, NULL); + + static struct sigaction sigTermHandler; + sigTermHandler.sa_handler = onTerminate; + sigemptyset(&sigTermHandler.sa_mask); + sigTermHandler.sa_flags = 0; + sigaction(SIGTERM, &sigTermHandler, NULL); + + static struct sigaction sigIllHandler; + sigIllHandler.sa_handler = onIllegal; + sigemptyset(&sigIllHandler.sa_mask); + sigIllHandler.sa_flags = 0; + sigaction(SIGILL, &sigIllHandler, NULL); +} + +void printInferenceResults(std::vector &results) +{ + for (auto & obj : results) { + std::cout << "Rect: [ " << obj.x << " , " << obj.y << " " << obj.width << ", " << obj.height << "] Class: " << obj.classText << "(" << obj.classId << ") Conf: " << obj.confidence << std::endl; + } +} + +// This function is responsible for generating a GST pipeline that +// decodes and resizes the video stream based on the desired window size or +// the largest analytics frame size needed if running headless +std::string getVideoPipelineText(std::string mediaPath, ObjectDetectionInterface* objDet, ObjectDetectionInterface* textDet) +{ + std::vector modelFrameShape = objDet->getModelInputShape(); + if (textDet) { + modelFrameShape = textDet->getModelInputShape(); + } + + int frame_width = _window_width; + int frame_height = _window_height; + + return _mediaService->getVideoDecodedPreProcessedPipeline( + mediaPath, + _videoType, + frame_width, + frame_height, + _use_onevpl); +} + +bool createModelServer() +{ + if (_srv == NULL) + return false; + + OVMS_Status* res = OVMS_ServerStartFromConfigurationFile(_srv, _serverSettings, _modelsSettings); + + if (res) { + uint32_t code = 0; + const char* details = nullptr; + + OVMS_StatusCode(res, &code); + OVMS_StatusDetails(res, &details); + std::cerr << "ERROR: during start: code:" << code << "; details:" << details + << "; grpc_port: " << _server_grpc_port + << "; http_port: " << _server_http_port + << ";" << std::endl; + + OVMS_StatusDelete(res); + + if (_srv) + OVMS_ServerDelete(_srv); + + if (_modelsSettings) + OVMS_ModelsSettingsDelete(_modelsSettings); + + if (_serverSettings) + OVMS_ServerSettingsDelete(_serverSettings); + + return false; + } + + return true; +} + +bool loadGStreamer(GstElement** pipeline, GstElement** appsink, std::string mediaPath, ObjectDetectionInterface* _objDet) +{ + static int threadCnt = 0; + + std::cout << "loadGStreamer" << std::endl; + std::string videoPipelineText = getVideoPipelineText(mediaPath, _objDet, NULL); + std::cout << "--------------------------------------------------------------" << std::endl; + std::cout << "Opening Media Pipeline: " << videoPipelineText << std::endl; + std::cout << "--------------------------------------------------------------" << std::endl; + + *pipeline = gst_parse_launch (videoPipelineText.c_str(), NULL); + if (*pipeline == NULL) { + std::cout << "ERROR: Failed to parse GST pipeline. Quitting." << std::endl; + return false; + } + + std::string appsinkName = "appsink" + std::to_string(threadCnt++); + + *appsink = gst_bin_get_by_name (GST_BIN (*pipeline), appsinkName.c_str()); + + // Check if all elements were created + if (!(*appsink)) + { + printf("ERROR: Failed to initialize GST pipeline (missing %s) Quitting.\n", appsinkName.c_str()); + return false; + } + + GstStateChangeReturn gst_res; + + // Start pipeline so it could process incoming data + gst_res = gst_element_set_state(*pipeline, GST_STATE_PLAYING); + + if (gst_res != GST_STATE_CHANGE_SUCCESS && gst_res != GST_STATE_CHANGE_ASYNC ) { + printf("ERROR: StateChange not successful. Error Code: %d\n", gst_res); + return false; + } + + return true; +} + +// OVMS C-API is a global process (singleton design) wide server so can't create many of them +bool loadOVMS() +{ + OVMS_Status* res = NULL; + + OVMS_ServerSettingsNew(&_serverSettings); + OVMS_ModelsSettingsNew(&_modelsSettings); + OVMS_ServerNew(&_srv); + OVMS_ServerSettingsSetGrpcPort(_serverSettings, _server_grpc_port); + OVMS_ServerSettingsSetRestPort(_serverSettings, _server_http_port); + OVMS_ServerSettingsSetLogLevel(_serverSettings, OVMS_LOG_ERROR); + + OVMS_ModelsSettingsSetConfigPath(_modelsSettings, "/models/config.json"); + + if (!createModelServer()) { + std::cout << "Failed to create model server\n" << std::endl; + return false; + } + else { + std::cout << "--------------------------------------------------------------" << std::endl; + std::cout << "Server ready for inference C-API ports " << _server_grpc_port << " " << _server_http_port << std::endl; + std::cout << "--------------------------------------------------------------" << std::endl; + _server_http_port+=1; + _server_grpc_port+=1; + } + return true; +} + +bool getMAPipeline(std::string mediaPath, GstElement** pipeline, GstElement** appsink, ObjectDetectionInterface** _objDet) +{ + if (!setActiveModel(_detectorModel, _objDet)) { + std::cout << "Unable to set active detection model" << std::endl; + return false; + } + + return loadGStreamer(pipeline, appsink, mediaPath, *_objDet); +} + +void run_stream(std::string mediaPath, GstElement* pipeline, GstElement* appsink, ObjectDetectionInterface* objDet) +{ + auto ttid = std::this_thread::get_id(); + std::stringstream ss; + ss << ttid; + std::string tid = ss.str(); + + // Wait for all decoder streams to init...otherwise causes a segfault when OVMS loads + // https://stackoverflow.com/questions/48271230/using-condition-variablenotify-all-to-notify-multiple-threads + std::unique_lock lk(_mtx); + _cvAllDecodersInitd.wait(lk, [] { return _allDecodersInitd;} ); + lk.unlock(); + + printf("Starting thread: %s\n", tid.c_str()) ; + + auto initTime = std::chrono::high_resolution_clock::now(); + unsigned long numberOfFrames = 0; + long long numberOfSkipFrames = 0; + OVMS_Status* res = NULL; + + while (!shutdown_request) { + auto startTime = std::chrono::high_resolution_clock::now(); + + const void* voutputData1; + size_t bytesize1 = 0; + uint32_t outputCount = 0; + uint32_t outputId; + OVMS_DataType datatype1 = (OVMS_DataType)42; + const int64_t* shape1{nullptr}; + size_t dimCount1 = 0; + OVMS_BufferType bufferType1 = (OVMS_BufferType)42; + uint32_t deviceId1 = 42; + const char* outputName1{nullptr}; + + GstSample *sample; + GstStructure *s; + GstBuffer *buffer; + GstMapInfo m; + + std::vector detectedResults; + std::vector detectedResultsFiltered; + + if (gst_app_sink_is_eos(GST_APP_SINK(appsink))) { + std::cout << "INFO: EOS " << std::endl; + return; + } + + sample = gst_app_sink_try_pull_sample (GST_APP_SINK(appsink), 50 * GST_SECOND); + + if (sample == nullptr) { + std::cout << "ERROR: No sample found" << std::endl; + return; + } + + GstCaps *caps; + caps = gst_sample_get_caps(sample); + + if (caps == nullptr) { + std::cout << "ERROR: No caps found for sample" << std::endl; + return; + } + + s = gst_caps_get_structure(caps, 0); + gst_structure_get_int(s, "width", &_video_input_width); + gst_structure_get_int(s, "height", &_video_input_height); + + buffer = gst_sample_get_buffer(sample); + gst_buffer_map(buffer, &m, GST_MAP_READ); + + if (m.size <= 0) { + std::cout << "ERROR: Invalid buffer size" << std::endl; + return; + } + + cv::Mat analytics_frame; + cv::Mat floatImage; + std::vector inputShape; + inputShape = objDet->getModelInputShape(); + + cv::Mat img(_video_input_height, _video_input_width, CV_8UC3, (void *) m.data); + + if (dynamic_cast(objDet) != nullptr) + { + resize(img, analytics_frame, cv::Size(inputShape[1], inputShape[2]), 0, 0, cv::INTER_LINEAR); + } + else + { + printf("ERROR: Unknown model type\n"); + return; + } + analytics_frame.convertTo(floatImage, CV_32F); + + const int DATA_SIZE = floatImage.step[0] * floatImage.rows; + + OVMS_InferenceResponse* response = nullptr; + OVMS_InferenceRequest* request{nullptr}; + + // OD Inference + { + std::lock_guard lock(_infMtx); + + OVMS_InferenceRequestNew(&request, _srv, objDet->getModelName(), objDet->getModelVersion()); + + OVMS_InferenceRequestAddInput( + request, + objDet->getModelInputName(), + OVMS_DATATYPE_FP32, + objDet->model_input_shape, + objDet->getModelDimCount() + ); + + // run sync request + OVMS_InferenceRequestInputSetData( + request, + objDet->getModelInputName(), + reinterpret_cast(floatImage.data), + DATA_SIZE , + OVMS_BUFFERTYPE_CPU, + 0 + ); + + res = OVMS_Inference(_srv, request, &response); + + if (res != nullptr) { + std::cout << "OVMS_Inference failed " << std::endl; + uint32_t code = 0; + const char* details = 0; + OVMS_StatusCode(res, &code); + OVMS_StatusDetails(res, &details); + std::cout << "Error occured during inference. Code:" << code + << ", details:" << details << std::endl; + + OVMS_StatusDelete(res); + if (request) + OVMS_InferenceRequestDelete(request); + break; + } + } // end lock on inference request to server + + OVMS_InferenceResponseOutputCount(response, &outputCount); + outputId = outputCount - 1; + OVMS_InferenceResponseOutput(response, outputId, &outputName1, &datatype1, &shape1, &dimCount1, &voutputData1, &bytesize1, &bufferType1, &deviceId1); + + objDet->postprocess(shape1, voutputData1, SHAPE, bytesize1, dimCount1, detectedResults); + objDet->postprocess(detectedResults, detectedResultsFiltered); + // printInferenceResults(detectedResultsFiltered); + + numberOfSkipFrames++; + float fps = 0; + if (numberOfSkipFrames <= 120) // allow warm up for latency/fps measurements + { + initTime = std::chrono::high_resolution_clock::now(); + numberOfFrames = 0; + + //printf("Too early...Skipping frames..\n"); + } + else + { + numberOfFrames++; + + auto endTime = std::chrono::high_resolution_clock::now(); + auto latencyTime = ((std::chrono::duration_cast(endTime-startTime)).count()); + auto runningLatencyTime = ((std::chrono::duration_cast(endTime-initTime)).count()); + if (runningLatencyTime > 0) { // skip a few to account for init + fps = (float)numberOfFrames/(float)(runningLatencyTime/1000); // convert to seconds + } + + if (_render) + objDet->displayGUIInferenceResults(img, detectedResultsFiltered, latencyTime, fps); + + static int highest_latency_frame = 0; + static int lowest_latency_frame = 9999; + static int avg_latency_frame = 0; + static int total_latency_frames = 0; + + int frame_latency = chrono::duration_cast(endTime - startTime).count(); + + if (frame_latency > highest_latency_frame) + highest_latency_frame = frame_latency; + if (frame_latency < lowest_latency_frame) + lowest_latency_frame = frame_latency; + + total_latency_frames += frame_latency; + if (numberOfFrames % 30 == 0) { + avg_latency_frame = total_latency_frames / 30; + + time_t currTime = time(0); + struct tm tstruct; + char bCurrTime[80]; + tstruct = *localtime(&currTime); + // http://en.cppreference.com/w/cpp/chrono/c/strftime + strftime(bCurrTime, sizeof(bCurrTime), "%Y-%m-%d.%X", &tstruct); + + cout << detectedResultsFiltered.size() << " object(s) detected at " << bCurrTime << endl; + cout << "Avg. Pipeline Throughput FPS: " << ((isinf(fps)) ? "..." : std::to_string(fps)) << endl; + cout << "Avg. Pipeline Latency (ms): " << avg_latency_frame << endl; + cout << "Max. Pipeline Latency (ms): " << highest_latency_frame << endl; + cout << "Min. Pipeline Latency (ms): " << lowest_latency_frame << endl; + highest_latency_frame = 0; + lowest_latency_frame = 9999; + total_latency_frames = 0; + } + + } + + if (request) { + OVMS_InferenceRequestInputRemoveData(request, objDet->getModelInputName()); // doesn't help + OVMS_InferenceRequestRemoveInput(request, objDet->getModelInputName()); + OVMS_InferenceRequestDelete(request); + } + + if (response) { + OVMS_InferenceResponseDelete(response); + } + + gst_buffer_unmap(buffer, &m); + gst_sample_unref(sample); + + if (shutdown_request > 0) + break; + } // end while get frames + + std::cout << "Goodbye..." << std::endl; + + if (res != NULL) { + OVMS_StatusDelete(res); + res = NULL; + } + + if (objDet) { + delete objDet; + objDet = NULL; + } + + gst_element_set_state (pipeline, GST_STATE_NULL); + if (pipeline) + gst_object_unref(pipeline); + + if (appsink) + gst_object_unref(appsink); +} + +void print_usage(const char* programName) { + std::cout << "Usage: ./" << programName << " \n\n" + << "mediaLocation is an rtsp://127.0.0.1:8554/camera_0 url or a path to an *.mp4 file\n" + << "use_onevpl is 0 (libva - default) or 1 for onevpl\n" + << "render is 1 to launch render window or 0 (default) for headless\n" + << "video_type is 0 for AVC or 1 for HEVC\n" + << "window_width is display window width\n" + << "window_height is display window height\n"; +} + +int get_running_servers() { + char buffer[128]; + string cmd = "echo $cid_count"; + std::string result = ""; + FILE* pipe = popen(cmd.c_str(), "r"); + + if (!pipe) + throw std::runtime_error("popen() failed!"); + + try + { + while (fgets(buffer, sizeof buffer, pipe) != NULL) + { + result += buffer; + } + } + catch (...) + { + pclose(pipe); + throw; + } + pclose(pipe); + return std::stoi(result.c_str()); +} + +int main(int argc, char** argv) { + std::cout << std::setprecision(2) << std::fixed; + std::cout << argv[0]<< argv[1]<< argv[2]<< argv[3]<< argv[4]<< argv[5]<< " \n\n"; + // Use GST pipelines for media HWA decode and pre-procesing + _mediaService = new GStreamerMediaPipelineService(); + + // get valid server port numbers + int running_servers = get_running_servers(); + _server_grpc_port = 9178 + running_servers; + _server_http_port = 11338 + running_servers; + + _videoStreamPipeline = "people-detection.mp4"; + + if (argc < 3) { + print_usage(argv[0]); + return 1; + } + + if (!stringIsInteger(argv[2]) || !stringIsInteger(argv[3]) || !stringIsInteger(argv[4]) + || !stringIsInteger(argv[5]) || !stringIsInteger(argv[6]) || !stringIsInteger(argv[7])) { + print_usage(argv[0]); + return 1; + } else { + _videoStreamPipeline = argv[1]; + _use_onevpl = std::stoi(argv[2]); + _render = std::stoi(argv[3]); + _renderPortrait = std::stoi(argv[4]); + _videoType = (MediaPipelineServiceInterface::VIDEO_TYPE) std::stoi(argv[5]); + _window_width = std::stoi(argv[6]); + _window_height = std::stoi(argv[7]); + std::cout << "_window_width: " << _window_width << std::endl; + std::cout << "_window_height: " << _window_height << std::endl; + + if (_renderPortrait) { + int tmp = _window_width; + _window_width = _window_height; + _window_height = tmp; + } + } + + gst_init(NULL, NULL); + + std::vector running_streams; + _allDecodersInitd = false; + + GstElement *pipeline; + GstElement *appsink; + ObjectDetectionInterface* objDet; + getMAPipeline(_videoStreamPipeline, &pipeline, &appsink, &objDet); + running_streams.emplace_back(run_stream, _videoStreamPipeline, pipeline, appsink, objDet); + + if (!loadOVMS()) + return -1; + + _allDecodersInitd = true; + _cvAllDecodersInitd.notify_all();; + + + for(auto& running_stream : running_streams) + running_stream.join(); + + if (_mediaService != NULL) { + delete _mediaService; + _mediaService = NULL; + } + + if (_srv) + OVMS_ServerDelete(_srv); + if (_modelsSettings) + OVMS_ModelsSettingsDelete(_modelsSettings); + if (_serverSettings) + OVMS_ServerSettingsDelete(_serverSettings); + + return 0; +} diff --git a/configs/opencv-ovms/gst_capi/pipelines/face_detection/Makefile b/configs/opencv-ovms/gst_capi/pipelines/face_detection/Makefile index b5f03fbd..bf7c8408 100644 --- a/configs/opencv-ovms/gst_capi/pipelines/face_detection/Makefile +++ b/configs/opencv-ovms/gst_capi/pipelines/face_detection/Makefile @@ -14,6 +14,8 @@ # limitations under the License. # +.PHONY: build + CV_LIBS = -L/usr/lib/x86_64-linux-gnu/ -L/usr/local/lib/x86_64-linux-gnu/ -L/ovms/lib/ CV_INCLUDES = -I/usr/lib/include/opencv4 diff --git a/configs/opencv-ovms/gst_capi/run_face_detection.sh b/configs/opencv-ovms/gst_capi/run_gst_capi.sh similarity index 91% rename from configs/opencv-ovms/gst_capi/run_face_detection.sh rename to configs/opencv-ovms/gst_capi/run_gst_capi.sh index b848ec8c..d25da503 100755 --- a/configs/opencv-ovms/gst_capi/run_face_detection.sh +++ b/configs/opencv-ovms/gst_capi/run_gst_capi.sh @@ -13,6 +13,8 @@ GST_VAAPI_DRM_DEVICE="${GST_VAAPI_DRM_DEVICE:=/dev/dri/renderD128}" USE_ONEVPL="${USE_ONEVPL:=0}" RENDER_MODE="${RENDER_MODE:=0}" TARGET_GPU_DEVICE="${TARGET_GPU_DEVICE:=--privileged}" +WINDOW_WIDTH="${WINDOW_WIDTH:=1920}" +WINDOW_HEIGHT="${WINDOW_HEIGHT:=1080}" update_media_device_engine() { # Use discrete GPU if it exists, otherwise use iGPU or CPU @@ -29,7 +31,7 @@ update_media_device_engine() { # The default state of all libva (*NIX) media decode/encode/etc is GPU.0 instance update_media_device_engine -bash_cmd="./launch-pipeline.sh $PIPELINE_EXEC_PATH $INPUTSRC $USE_ONEVPL $RENDER_MODE $RENDER_PORTRAIT_MODE" +bash_cmd="./launch-pipeline.sh $PIPELINE_EXEC_PATH $INPUTSRC $USE_ONEVPL $RENDER_MODE $RENDER_PORTRAIT_MODE $WINDOW_WIDTH $WINDOW_HEIGHT" echo "BashCmd: $bash_cmd with media on $GST_VAAPI_DRM_DEVICE with USE_ONEVPL=$USE_ONEVPL" diff --git a/docs_src/OVMS/capiPipelineRun.md b/docs_src/OVMS/capiPipelineRun.md index c3ba1cf2..f8044ae5 100644 --- a/docs_src/OVMS/capiPipelineRun.md +++ b/docs_src/OVMS/capiPipelineRun.md @@ -68,7 +68,7 @@ OvmsClient: - "$RUN_PATH/configs/opencv-ovms/gst_capi/extensions:/home/intel/gst-ovms/extensions" - "$RUN_PATH/results:/tmp/results" - "$RUN_PATH/configs/opencv-ovms/models/2022/:/home/intel/gst-ovms/models" - PipelineScript: ./run_face_detection.sh + PipelineScript: ./run_gst_capi.sh PipelineInputArgs: "" # space delimited like we run the script in command and take those input arguments EnvironmentVariableFiles: - capi_face_detection.env diff --git a/docs_src/OVMS/capiYolov5PipelineRun.md b/docs_src/OVMS/capiYolov5PipelineRun.md new file mode 100644 index 00000000..5152a3f0 --- /dev/null +++ b/docs_src/OVMS/capiYolov5PipelineRun.md @@ -0,0 +1,80 @@ +# OpenVINO OVMS C-API Yolov5 Pipeline Run + +OpenVINO Model Server has [many ways to run inferencing pipeline](https://docs.openvino.ai/2023.1/ovms_docs_server_api.html): +TensorFlow Serving gRPC API, KServe gRPC API, TensorFlow Serving REST API, KServe REST API and OVMS C API through OpenVINO model server (OVMS). Here we are demonstrating for using OVMS C API method to run inferencing pipeline yolov5s model in following steps: + +1. Add new section to model configuration file for model server +2. Add pipeline specific files +3. Add environment variable file dependency +4. Add a profile launcher pipeline configuration file +5. Build and run + + +## Add New Section To Model Config File for Model Server + +Here is the template config file location: [`configs/opencv-ovms/models/2022/config_template.json`](https://github.com/intel-retail/automated-self-checkout/blob/main/configs/opencv-ovms/models/2022/config_template.json), edit the file and append the new model's configuration into the template, such as yolov5 model as shown below: +```json + { + "config": { + "name": "yolov5s", + "base_path": "/models/yolov5s/FP16-INT8", + "layout": "NHWC:NCHW", + "shape": "(1,416,416,3)", + "nireq": 1, + "batch_size": "1", + "plugin_config": { + "PERFORMANCE_HINT": "LATENCY" + }, + "target_device": "{target_device}" + } + } +``` +!!! Note + `shape` is optional and takes precedence over batch_size, please remove this attribute if you don't know the value for the model. + +!!! Note + Please leave `target_device` value as it is, as the value `{target_device}` will be recognized and replaced by script run. + +You can find the parameter description in the [ovms docs](https://docs.openvino.ai/2023.1/ovms_docs_parameters.html). + +## Add pipeline specific files + +Here is the list of files we added in directory of `configs/opencv-ovms/gst_capi/pipelines/capi_yolov5/`: + +1. `main.cpp` - this is all the work about pre-processing before sending to OVMS for inferencing and post-processing for displaying. +2. `Makefile` - to help building the pre-processing and post-processing binary. + +## Add Environment Variable File + +You can add multiple environment variable files to `configs/opencv-ovms/envs/` directory for your pipeline, we've added `capi_yolov5.env` for yolov5 pipeline run. Below is a list of explanation for all environment variables and current default values we set, this list can be extended for any future modification. + +| EV Name |Default Value | Description | +| --------------------------|-----------------------------------------|-------------------------------------------------------| +| RENDER_PORTRAIT_MODE | 1 | rendering in portrait mode, value: 0 or 1 | +| GST_DEBUG | 1 | running GStreamer in debug mode, value: 0 or 1 | +| USE_ONEVPL | 1 | using OneVPL CPU & GPU Support, value: 0 or 1 | +| PIPELINE_EXEC_PATH | pipelines/capi_yolov5/capi_yolov5 | pipeline execution path inside container | +| GST_VAAPI_DRM_DEVICE | /dev/dri/renderD128 | GStreamer VAAPI DRM device input | +| TARGET_GPU_DEVICE | --privileged | allow using GPU devices if any | +| LOG_LEVEL | 0 | [GST_DEBUG log level](https://gstreamer.freedesktop.org/documentation/tutorials/basic/debugging-tools.html?gi-language=c#the-debug-log) to be set when running gst pipeline | +| RENDER_MODE | 1 | option to display the input source video stream with the inferencing results, value: 0 or 1 | +| cl_cache_dir | /home/intel/gst-ovms/.cl-cache | cache directory in container | +| WINDOW_WIDTH | 1920 | display window width | +| WINDOW_HEIGHT | 1080 | display window height | + +details of yolov5s pipeline environment variable file can be viewed in [`configs/opencv-ovms/envs/capi_yolov5.env`](https://github.com/intel-retail/automated-self-checkout/blob/main/configs/opencv-ovms/envs/capi_yolov5.env). + +## Add A Profile Launcher Configuration File + +The details about Profile Launcher configuration can be found [here](./profileLauncherConfigs.md), details for yolov5 pipeline profile launcher configuration can be viewed in [`configs/opencv-ovms/cmd_client/res/capi_yolov5/configuration.yaml`](https://github.com/intel-retail/automated-self-checkout/tree/main/configs/opencv-ovms/cmd_client/res/capi_yolov5/configuration.yaml) + +## Build and Run + +Here are the quick start steps to build and run capi yolov5 pipeline profile : + +1. Build docker image with profile-launcher: `make build-capi-yolov5` +2. Download sample video files: `cd benchmark-scripts/ && ./download_sample_videos.sh && cd ..` +3. Start simulator camera: `make run-camera-simulator` +4. To start the pipeline run: `PIPELINE_PROFILE="capi_yolov5" RENDER_MODE=1 sudo -E ./run.sh --platform core --inputsrc rtsp://127.0.0.1:8554/camera_1 --workload ovms` +!!! Note + The pipeline will automatically download the OpenVINO model files listed in [`configs/opencv-ovms/models/2022/config_template.json`](https://github.com/intel-retail/automated-self-checkout/blob/main/configs/opencv-ovms/models/2022/config_template.json) \ No newline at end of file diff --git a/run_smoke_test.sh b/run_smoke_test.sh index 6620ec6b..24a84e44 100755 --- a/run_smoke_test.sh +++ b/run_smoke_test.sh @@ -201,7 +201,7 @@ verifyNonEmptyPipelineLog object_detection $od_input_src teardown #8. gst capi capi_face_detection profile: -make build-gst-capi +make build-capi_face_detection echo "Running capi_face_detection profile..." input_src="rtsp://127.0.0.1:8554/camera_1" PIPELINE_PROFILE="capi_face_detection" RENDER_MODE=0 sudo -E ./run.sh --workload ovms --platform core --inputsrc "$input_src" @@ -211,3 +211,15 @@ verifyStatusCode capi_face_detection $status_code $input_src waitForLogFile verifyNonEmptyPipelineLog capi_face_detection $input_src teardown + +#9. gst capi capi_yolov5 profile: +make build-capi_yolov5 +echo "Running capi_yolov5 profile..." +input_src="rtsp://127.0.0.1:8554/camera_1" +PIPELINE_PROFILE="capi_yolov5" RENDER_MODE=0 sudo -E ./run.sh --workload ovms --platform core --inputsrc "$input_src" +status_code=$? +verifyStatusCode capi_yolov5 $status_code $input_src +# allowing some time to process +waitForLogFile +verifyNonEmptyPipelineLog capi_yolov5 $input_src +teardown \ No newline at end of file