service: add Air Gesture service API support 84/316684/4
authorInki Dae <inki.dae@samsung.com>
Tue, 23 Jul 2024 05:25:00 +0000 (14:25 +0900)
committerInki Dae <inki.dae@samsung.com>
Tue, 24 Dec 2024 06:43:22 +0000 (15:43 +0900)
Add Air Gesture service API support to control mouse input
with gaze and hand gesture.

The task pipeline of this WIP is as follows,
face detection --- bridge --- face landmark --- bridge --- gaze tracking ---- endpoint
                                                                          |
hand detection ------------------------------------------------------------

In default, TinyTrackerS model is used for gaze estimation.
This pipeline behavior is as follows(pipeline is changed in runtime),
1. Perform face landmark and gaze tracking only if gaze estimation is
   gotten correctly after face detection.
2. Change gaze position with the position from hand detection if hand is
   detected.

Change-Id: Ib328e25d90a228eb59f642badf5a6ff871949348
Signed-off-by: Inki Dae <inki.dae@samsung.com>
26 files changed:
capi/singleo_native_capi.h
common/include/SingleoCommonTypes.h
inference/backends/mediavision/CMakeLists.txt
inference/backends/mediavision/include/MvGazeTracking.h
inference/backends/mediavision/include/MvHandDetection.h
inference/backends/mediavision/include/MvHandLandmark.h
inference/backends/mediavision/src/MvGazeTracking.cpp
packaging/singleo.spec
services/CMakeLists.txt
services/air_gesture/CMakeLists.txt [new file with mode: 0644]
services/air_gesture/include/AirGesture.h [new file with mode: 0644]
services/air_gesture/include/DataTypes.h [new file with mode: 0644]
services/air_gesture/include/TinyTrackerS.h [new file with mode: 0644]
services/air_gesture/src/AirGesture.cpp [new file with mode: 0644]
services/air_gesture/src/TinyTrackerS.cpp [new file with mode: 0644]
services/auto_zoom/include/AutoZoom.h
services/common/include/Context.h
services/common/include/IDecoder.h [new file with mode: 0644]
services/common/include/IService.h
services/common/include/ServiceDataType.h
services/common/src/ImagePreprocessor.cpp
services/focus_finder/include/FocusFinder.h
services/singleo_native_capi.cpp
test/services/CMakeLists.txt
test/services/test_airgesture_on_screen.cpp [new file with mode: 0644]
visualizer/include/singleo_util_render_2d.h

index d185f9ca252a0d02fa1166c84d80382db9a5ecad..3465823bf997a511abc7e6f30ca030d90d2580b5 100644 (file)
@@ -203,6 +203,28 @@ int singleo_service_get_result_cnt(singleo_service_h handle, unsigned int *cnt);
  */
 int singleo_service_get_result_int(singleo_service_h handle, unsigned int idx, const char *key, unsigned int *value);
 
+/**
+ * @internal
+ * @brief Gets float type of result corresponding to a given index value.
+ *
+ * @since_tizen 9.0
+ *
+ * @param[in] handle   The handle to the service.
+ * @param[in] idx      Index value to results.
+ * @param[in] key      Key string to a certain member of the result.
+ * @param[out] value   Pointer to the float variable to be stored.
+ *
+ * @return 0 on success, otherwise a negative error value
+ * @retval #SINGLEO_SERVICE_ERROR_NONE Successful
+ * @retval #SINGLEO_SERVICE_ERROR_ERROR_INVALID_PARAMETER Invalid parameter
+ * @retval #SINGLEO_SERVICE_ERROR_ERROR_INVALID_OPERATION Invalid operation
+ *
+ * @pre Create a source handle by calling singleo_service_create()
+ * @pre Perform a requested service by calling singleo_service_perform()
+ * @pre Get a number of results by calling singleo_service_get_result_cnt()
+ */
+int singleo_service_get_result_float(singleo_service_h handle, unsigned int idx, const char *key, float *value);
+
 /**
  * @internal
  * @brief Gets string type of result corresponding to a given index value.
index 5fbe139e9939a4f95526b4a9fa9c301ee8745f89..fa165d59f92283d5650c0058ed214735344d83a5 100644 (file)
@@ -18,6 +18,7 @@
 #define __SINGLEO_COMMON_TYPES_H__
 
 #include <memory>
+#include <cmath>
 #include <cstring>
 #include <vector>
 #include "SingleoLog.h"
@@ -47,6 +48,18 @@ struct Point {
        }
 };
 
+struct FPoint {
+       float x {};
+       float y {};
+
+       bool operator==(const FPoint &other) const
+       {
+               const float EPSILON = 1e-5f;
+
+               return (fabs(x - other.x) < EPSILON  && fabs(y - other.y) < EPSILON);
+       }
+};
+
 enum class DataType { NONE, FILE, IMAGE, RAW };
 
 enum class ImagePixelFormat { NONE, YUV420, RGB888 };
@@ -148,7 +161,17 @@ struct RawDataType : public BaseDataType {
        }
 };
 
-enum class ResultType { NONE, OBJECT_DETECTION, FACE_DETECTION, FACE_LANDMARK, IMAGE_CLASSIFICATION, FACE_RECOGNITION, GAZE_ESTIMATION };
+enum class ResultType {
+       NONE,
+       OBJECT_DETECTION,
+       FACE_DETECTION,
+       FACE_LANDMARK,
+       HAND_DETECTION,
+       HAND_LANDMARK,
+       IMAGE_CLASSIFICATION,
+       FACE_RECOGNITION,
+       GAZE_TRACKING
+};
 
 struct BaseResultType {
        ResultType _type { ResultType::NONE };
@@ -195,6 +218,29 @@ struct FldResultType : public BaseResultType {
        }
 };
 
+struct HdResultType : public BaseResultType {
+       HdResultType() : BaseResultType(ResultType::HAND_DETECTION)
+       {}
+       std::vector<Rect> _rects;
+
+       std::shared_ptr<BaseResultType> clone() override
+       {
+               return std::make_shared<HdResultType>(*this);
+       }
+};
+
+struct HldResultType : public BaseResultType {
+       HldResultType() : BaseResultType(ResultType::HAND_LANDMARK)
+       {}
+       std::vector<Point> _points;
+
+       std::shared_ptr<BaseResultType> clone() override
+       {
+               return std::make_shared<HldResultType>(*this);
+       }
+};
+
+
 struct IcResultType : public BaseResultType {
        IcResultType() : BaseResultType(ResultType::IMAGE_CLASSIFICATION)
        {}
@@ -219,12 +265,12 @@ struct FrResultType : public BaseResultType {
 };
 
 struct GazeResultType : public BaseResultType {
-       GazeResultType() : BaseResultType(ResultType::GAZE_ESTIMATION)
+       GazeResultType() : BaseResultType(ResultType::GAZE_TRACKING)
        {}
-
+       std::vector<Point> _points; // 0: center, 1: gazed
+       std::vector<FPoint> _fpoints;
        double _pitch {};
        double _yaw {};
-       std::vector<Point> _points; // 0: center, 1: gazed
 
        std::shared_ptr<BaseResultType> clone() override
        {
@@ -232,8 +278,6 @@ struct GazeResultType : public BaseResultType {
        }
 };
 
-enum class ServiceType { NONE, AUTO_ZOOM, FOCUS_FINDER };
-
 enum class InputFeedType { NONE, CAMERA, SCREEN_CAPTURE };
 
 enum class CameraBackendType { NONE, OPENCV, CAMERA_API, VISION_SOURCE };
index 2103a7c7100db3ce2e5686aac5ca7388e8e2008f..c4b00bcf4638ffa28f380c58533ab08cbdc3d3b6 100644 (file)
@@ -7,6 +7,7 @@ SET(INFERENCE_MEDIAVISION_BACKEND_DIRECTORY ${INFERENCE_DIRECTORY}/backends/medi
 
 SET(SINGLEO_SERVICE_SOURCE_FILES
     ${SINGLEO_SERVICE_SOURCE_FILES}
+    ${INFERENCE_MEDIAVISION_BACKEND_DIRECTORY}/src/MvGazeTracking.cpp
     ${INFERENCE_MEDIAVISION_BACKEND_DIRECTORY}/src/MvHandLandmark.cpp
     ${INFERENCE_MEDIAVISION_BACKEND_DIRECTORY}/src/MvHandDetection.cpp
     ${INFERENCE_MEDIAVISION_BACKEND_DIRECTORY}/src/MvFaceDetection.cpp
index 5891db5647b00e1d08f63efa457d7cd98dce4b55..f9dc4c253fcc8de1157030b0d702ab56ac92bcff 100644 (file)
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef __MV_GAZE_ESTIMATION_H__
-#define __MV_GAZE_ESTIMATION_H__
+#ifndef __MV_GAZE_TRACKING_H__
+#define __MV_GAZE_TRACKING_H__
 
 #include "IInferenceTaskInterface.h"
 #include "mv_gaze_tracking.h"
@@ -32,7 +32,7 @@ class MvGazeTracking : public IInferenceTaskInterface
 {
 private:
        mv_gaze_tracking_h _handle {};
-       GazeResultType _output_data;
+       GazeResultType _output_data {};
 
 public:
        MvGazeTracking();
index bb7010477660e2645f29b775179e1bc162293ce1..f50f612ea04d41c6e5864d4de8dfc6e194bab37e 100644 (file)
@@ -32,7 +32,7 @@ class MvHandDetection : public IInferenceTaskInterface
 {
 private:
        mv_hand_detection_h _handle {};
-       FdResultType _output_data {};
+       HdResultType _output_data {};
 
 public:
        MvHandDetection();
index 901d1c8ddf5ea6633e55a8ab4705dcca9412f9d6..160e472b002f04630cc15b207babd0a2833a97a0 100644 (file)
@@ -32,7 +32,7 @@ class MvHandLandmark : public IInferenceTaskInterface
 {
 private:
        mv_hand_landmark_h _handle {};
-       FldResultType _output_data;
+       HldResultType _output_data;
 
 public:
        MvHandLandmark();
index 62325d3ab6498a6ba056b5777dcf0e884b709ddb..ca5ef8a279a57ce1e3dd513ff30eca5f55c34186 100644 (file)
@@ -46,12 +46,7 @@ MvGazeTracking::~MvGazeTracking()
 
 void MvGazeTracking::configure()
 {
-       int ret = mv_gaze_tracking_set_model(_handle, "gzt_l2cs_mobilenetv2_224x224.tflite", "gzt_l2cs_mobilenetv2_224x224.json",
-                                                                                  "", "GZE_L2CS_NET_MOBILENETV2");
-       if (ret != MEDIA_VISION_ERROR_NONE)
-               throw runtime_error("Fail to set gaze tracking model.");
-
-       ret = mv_gaze_tracking_configure(_handle);
+       int ret = mv_gaze_tracking_configure(_handle);
        if (ret != MEDIA_VISION_ERROR_NONE)
                throw runtime_error("Fail to configure gaze tracking.");
 }
@@ -86,7 +81,7 @@ void MvGazeTracking::invoke(BaseDataType &input, bool async)
 
                ret = mv_gaze_tracking_inference(_handle, mv_src);
                if (ret != MEDIA_VISION_ERROR_NONE)
-                       throw runtime_error("Fail to invoke gaze estimation.");
+                       throw runtime_error("Fail to invoke gaze tracking.");
        } catch (std::runtime_error &e) {
                SINGLEO_LOGE("%s", e.what());
        }
@@ -103,8 +98,9 @@ BaseResultType &MvGazeTracking::result()
 
        int ret = mv_gaze_tracking_get_result_count(_handle, &frame_number, &result_cnt);
        if (ret != MEDIA_VISION_ERROR_NONE)
-               throw runtime_error("Fail to get gaze estimation result count.");
+               throw runtime_error("Fail to get gaze tracking result count.");
 
+       _output_data = GazeResultType();
        _output_data._frame_number = frame_number;
        _output_data._is_empty = result_cnt == 0;
 
@@ -117,6 +113,14 @@ BaseResultType &MvGazeTracking::result()
 
                _output_data._yaw = yaw;
                _output_data._pitch = pitch;
+
+               FPoint point {};
+
+               ret = mv_gaze_tracking_get_pos(_handle, 0, &point.x, &point.y);
+               if (ret != MEDIA_VISION_ERROR_NONE)
+                       throw runtime_error("Fail to get gaze tracking result.");
+
+               _output_data._fpoints.push_back(point);
        }
 
        return _output_data;
index 78054194c0430edf9fd075723ec1c0f277595119..5fa7bf8ee1a2464e51997674f888af9fdbfac447 100644 (file)
@@ -20,7 +20,7 @@ BuildRequires: pkgconfig(grpc++)
 BuildRequires: pkgconfig(re2)
 BuildRequires: pkgconfig(libtzplatform-config)
 
-%define enable_visualizer 0
+%define enable_visualizer 1
 %define enable_focusfinder 1
 %define use_3rdparty_facedetection 0
 
@@ -71,6 +71,7 @@ MAJORVER=`echo %{version} | awk 'BEGIN {FS="."}{print $1}'`
          -DUSE_INPUT_OPENCV_BACKEND=ON \
          -DUSE_AUTOZOOM_API=ON \
          -DUSE_FOCUSFINDER_API=%{enable_focusfinder} \
+         -DUSE_AIRGESTURE_API=ON \
          -DUSE_INPUT_CAMERA_API_BACKEND=ON \
          -DUSE_VISUALIZER=%{enable_visualizer} \
          -DUSE_3RDPARTY_FACEDETECTION=%{use_3rdparty_facedetection}
@@ -128,5 +129,6 @@ rm -rf %{buildroot}
 %{_bindir}/test_singleo
 %if "%{enable_visualizer}" == "1"
 %{_bindir}/test_singleo_on_screen
+%{_bindir}/test_singleo_airgesture
 %endif
 %{_bindir}/test_singleo_task_manger
index d9a165b09b485a92f1b2a418ce31423e60d9ad32..ef6c4427eaffb0be303141471d61118088aa49ff 100644 (file)
@@ -20,6 +20,10 @@ IF (${USE_FOCUSFINDER_API})
     INCLUDE(focus_finder/CMakeLists.txt)
 ENDIF()
 
+IF (${USE_AIRGESTURE_API})
+    INCLUDE(air_gesture/CMakeLists.txt)
+ENDIF()
+
 ADD_LIBRARY(${PROJECT_NAME} SHARED ${SINGLEO_SERVICE_SOURCE_FILES})
 
 TARGET_INCLUDE_DIRECTORIES(${PROJECT_NAME} PRIVATE include common/include ${ROOT_DIRECTORY}/capi/ ${COMMON_HEADER_LIST} ${INPUT_HEADER_LIST} ${INFERENCE_HEADER_LIST} ${SERVICE_HEADER_LIST} ${LOG_HEADER_LIST})
diff --git a/services/air_gesture/CMakeLists.txt b/services/air_gesture/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c5f1a62
--- /dev/null
@@ -0,0 +1,8 @@
+SET(SINGLEO_SERVICE_SOURCE_FILES
+    ${SINGLEO_SERVICE_SOURCE_FILES}
+    air_gesture/src/TinyTrackerS.cpp
+    air_gesture/src/AirGesture.cpp
+)
+
+LIST(APPEND SERVICE_LIBRARY_LIST ${SERVICE_LIBRARY_LIST})
+LIST(APPEND SERVICE_HEADER_LIST ${SERVICE_HEADER_LIST} ${CMAKE_CURRENT_SOURCE_DIR}/air_gesture/include)
\ No newline at end of file
diff --git a/services/air_gesture/include/AirGesture.h b/services/air_gesture/include/AirGesture.h
new file mode 100644 (file)
index 0000000..d3004ee
--- /dev/null
@@ -0,0 +1,99 @@
+/**
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * 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.
+ */
+
+#ifndef __AIR_GESTURE_H__
+#define __AIR_GESTURE_H__
+
+#include <opencv2/opencv.hpp>
+#include <mutex>
+
+#include "IService.h"
+#include "SingleoException.h"
+#include "SingleoCommonTypes.h"
+#include "IInputService.h"
+#include "SingleoInferenceTypes.h"
+#include "InputTypes.h"
+#include "IInputObserver.h"
+#include "ServiceDataType.h"
+#include "DataTypes.h"
+#include "AsyncManager.h"
+#include "IPreprocessor.h"
+#include "TaskManager.h"
+#include "IDecoder.h"
+
+namespace singleo
+{
+namespace services
+{
+namespace airgesture
+{
+class AirGesture : public IService, public input::IInputObserver
+{
+private:
+       static bool _registered;
+       std::unique_ptr<TaskManager> _taskManager;
+       std::unique_ptr<singleo::input::IInputService> _input_service;
+       std::queue<std::shared_ptr<BaseDataType> > _inputQ;
+       std::mutex _inputMutex;
+       AirGestureResult _result;
+       std::mutex _result_lock;
+       std::map<std::string, AirGestureResultType> _result_keys = { { "X", AirGestureResultType::X },
+                                                                                                                                { "Y", AirGestureResultType::Y } };
+       bool _async_mode { false };
+
+       bool isKeyValid(std::string key);
+       void updateResult(BaseDataType &input_data);
+
+       std::unique_ptr<AsyncManager<ImageDataType, AirGestureResult> > _async_manager;
+       singleo_user_cb_t _user_cb {};
+       void *_user_data {};
+       std::unique_ptr<IPreprocessor> _preprocessor;
+       std::unique_ptr<IDecoder> _decoder;
+       AirGestureMode _mode { AirGestureMode::FACE_TRACKING };
+
+public:
+       AirGesture();
+       virtual ~AirGesture();
+
+       static IService *create()
+       {
+               return new AirGesture();
+       }
+
+       // This function will be called by specific input service internally.
+       // Ps. caller has to provide captured data with concrete class object as data parameter.
+       void inputFeedCb(BaseDataType &data) override;
+       void configure(input::InputConfigBase &config) override;
+       void add_input(BaseDataType &input_data) override;
+       void perform() override;
+       void performAsync() override;
+       void getResultCnt(unsigned int *cnt) override;
+       void getResultInt(unsigned int idx, std::string key, unsigned int *value) override
+       {
+               throw exception::InvalidOperation("Not supported yet.");
+       }
+       void getResultFloat(unsigned int idx, std::string key, float *value) override;
+       void registerUserCallback(singleo_user_cb_t user_cb, void *user_data) override;
+       void unregisterUserCallback() override;
+
+       void runTaskManager(BaseDataType &input_data);
+};
+
+} // airgesture
+} // service
+} // singleo
+
+#endif
\ No newline at end of file
diff --git a/services/air_gesture/include/DataTypes.h b/services/air_gesture/include/DataTypes.h
new file mode 100644 (file)
index 0000000..683ff40
--- /dev/null
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * 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.
+ */
+
+#ifndef __DATA_TYPES_H__
+#define __DATA_TYPES_H__
+
+#include <vector>
+#include "SingleoCommonTypes.h"
+#include "ServiceDataType.h"
+
+namespace singleo
+{
+namespace services
+{
+namespace airgesture
+{
+enum class AirGestureResultType { X, Y };
+
+enum class AirGestureMode { NONE, FACE_TRACKING, GAZE_TRACKING, HAND_TRACKING };
+
+struct AirGestureResult : public ServiceBaseResultType {
+       AirGestureResult() : ServiceBaseResultType(ServiceResultType::AIR_GESTURE)
+       {}
+       bool is_valid { false };
+       unsigned int frame_number {};
+       unsigned int num_of_objects {};
+       float point_x {};
+       float point_y {};
+       std::vector<Rect> rects;
+       std::vector<FPoint> gaze_points;
+       std::vector<Point> landmark_points;
+       std::vector<Rect> handRects;
+       std::vector<Rect> preHandRects;
+       std::vector<Point> hand_landmark_points;
+};
+
+}
+}
+}
+
+#endif
\ No newline at end of file
diff --git a/services/air_gesture/include/TinyTrackerS.h b/services/air_gesture/include/TinyTrackerS.h
new file mode 100644 (file)
index 0000000..652b952
--- /dev/null
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * 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.
+ */
+
+#ifndef __TINY_TRACKER_S_H__
+#define __TINY_TRACKER_S_H__
+
+#include "IDecoder.h"
+#include "SingleoCommonTypes.h"
+#include "ServiceDataType.h"
+#include "DataTypes.h"
+
+namespace singleo
+{
+namespace services
+{
+namespace airgesture
+{
+class TinyTrackerS : public IDecoder
+{
+private:
+       float _screenWidth { 1600.0f };
+       float _screenHeight { 900.0f };
+       float _x_weight {};
+       float _y_weight {};
+       float _x_intercept {};
+       float _y_intercept {};
+
+       void getCurrentPosition(Rect previous, Rect current, int *x, int *y);
+
+public:
+       TinyTrackerS();
+       virtual ~TinyTrackerS() = default;
+
+       void decode(ServiceBaseResultType &baseResult) override;
+};
+
+}
+}
+}
+
+#endif
\ No newline at end of file
diff --git a/services/air_gesture/src/AirGesture.cpp b/services/air_gesture/src/AirGesture.cpp
new file mode 100644 (file)
index 0000000..9754f7f
--- /dev/null
@@ -0,0 +1,463 @@
+/**
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * 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 <algorithm>
+#include <map>
+#include "SingleoException.h"
+#include "AirGesture.h"
+#include "InferenceTaskFactory.h"
+#include "SingleoLog.h"
+#include "ImagePreprocessor.h"
+#include "ServiceFactory.h"
+#include "InputCamera.h"
+#include "InputTypes.h"
+#include "InferenceNode.h"
+#include "BridgeNode.h"
+#include "EndpointNode.h"
+#include "ClaheFilter.h"
+#include "TinyTrackerS.h"
+
+using namespace std;
+using namespace singleo::inference;
+using namespace singleo::input;
+using namespace singleo::exception;
+
+namespace singleo
+{
+namespace services
+{
+namespace airgesture
+{
+bool AirGesture::_registered = registerService<AirGesture>("AirGesture");
+
+void BridgeNodeCallback(INode *node)
+{
+       auto callbackNode = dynamic_cast<CallbackNode *>(node);
+       auto &inputBuffer = callbackNode->getInputBuffer();
+       auto imageData = dynamic_pointer_cast<ImageDataType>(inputBuffer->getInputs()[0]);
+       cv::Mat cv_image(cv::Size(imageData->width, imageData->height), CV_MAKETYPE(CV_8U, 3), imageData->ptr);
+       auto outputBuffer = make_shared<SharedBuffer>();
+
+       auto &results = callbackNode->results();
+       for (auto r : results) {
+               if (r->_type != ResultType::FACE_DETECTION)
+                       continue;
+
+               auto f_r = dynamic_pointer_cast<FdResultType>(r);
+
+               for (auto rect : f_r->_rects) {
+                       if (rect.left < 0)
+                               rect.left = 0;
+                       if (rect.top < 0)
+                               rect.top = 0;
+                       if (rect.right >= static_cast<int>(imageData->width))
+                               rect.right = imageData->width - 1;
+                       if (rect.bottom >= static_cast<int>(imageData->height))
+                               rect.bottom = imageData->height - 1;
+
+                       cv::Rect roi(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
+                       cv::Mat roi_img = cv_image(roi).clone();
+                       ImageDataType faceImage;
+
+                       faceImage.width = roi_img.cols;
+                       faceImage.height = roi_img.rows;
+                       faceImage.byte_per_pixel = roi_img.channels();
+                       faceImage.pixel_format = ImagePixelFormat::RGB888;
+                       faceImage.is_owned = true;
+
+                       size_t faceSize = roi_img.cols * roi_img.rows * roi_img.channels();
+
+                       faceImage.ptr = new unsigned char[faceSize];
+                       memcpy(faceImage.ptr, roi_img.data, faceSize);
+
+                       outputBuffer->addInput(make_shared<ImageDataType>(faceImage));
+                       break;
+               }
+       }
+
+       // Face detection failed so do not go forward.
+       if (outputBuffer->getInputs().size() == 0)
+               return;
+
+       callbackNode->setOutputBuffer(outputBuffer);
+}
+
+AirGesture::AirGesture()
+{
+       // In default, we will use Inference service factory for Mediavision to use Mediavision framework
+       // for inference service. TODO. introduce meta config file approach later.
+       auto factory = InferenceTaskFactory::instance().create("MvInferenceTaskFactory");
+
+       _taskManager = make_unique<TaskManager>();
+
+       _taskManager->requestNewNode(NodeType::STARTPOINT, "startpoint");
+
+       auto hand_detection_node = _taskManager->requestNewNode(NodeType::INFERENCE, "hand_detection");
+       dynamic_cast<InferenceNode *>(hand_detection_node)->setInferenceTask(factory->createHandDetection());
+
+       auto face_detection_node = _taskManager->requestNewNode(NodeType::INFERENCE, "face_detection");
+       dynamic_cast<InferenceNode *>(face_detection_node)->setInferenceTask(factory->createFaceDetection());
+
+       auto bridge_node_a = _taskManager->requestNewNode(NodeType::BRIDGE, "bridge_node_a");
+       dynamic_cast<BridgeNode *>(bridge_node_a)->setCb(BridgeNodeCallback);
+
+       auto gaze_tracking_node = _taskManager->requestNewNode(NodeType::INFERENCE, "gaze_tracking");
+       dynamic_cast<InferenceNode *>(gaze_tracking_node)->setInferenceTask(factory->createGazeTracking());
+
+       _taskManager->requestNewNode(NodeType::ENDPOINT, "endpoint");
+
+       _preprocessor = make_unique<ImagePreprocessor>();
+       _decoder = make_unique<TinyTrackerS>();
+
+       _taskManager->resetPipeline();
+
+       _taskManager->addEdge("startpoint", "hand_detection");
+       _taskManager->addEdge("startpoint", "face_detection");
+       _taskManager->addEdge("hand_detection", "endpoint");
+       _taskManager->addEdge("face_detection", "bridge_node_a");
+       _taskManager->addEdge("bridge_node_a", "gaze_tracking");
+       _taskManager->addEdge("face_detection", "endpoint");
+       _taskManager->addEdge("gaze_tracking", "endpoint");
+
+       _taskManager->setBypass("hand_detection");
+
+       _taskManager->verify();
+}
+
+void AirGesture::configure(InputConfigBase &config)
+{
+       // Create InputCamera service if input service type is CAMERA.
+       if (config._input_feed_type == InputFeedType::CAMERA) {
+               CameraConfig cameraConfig;
+
+               try {
+                       cameraConfig = dynamic_cast<CameraConfig &>(config);
+               } catch (bad_cast &e) {
+                       SINGLEO_LOGE("Camera input service requires CameraConfig.");
+                       throw InvalidOperation("Camera input service requires CameraConfig.");
+               }
+
+               _input_service = make_unique<InputCamera>(cameraConfig.backend_type);
+               _input_service->registerObserver(this);
+               _input_service->configure(cameraConfig);
+
+               SINGLEO_LOGD("Camera input service has been initialized.");
+       }
+}
+
+AirGesture::~AirGesture()
+{
+       if (_async_mode) {
+               _input_service->streamOff();
+               _async_manager->destroy();
+       }
+
+       _taskManager->clear();
+}
+
+void AirGesture::inputFeedCb(BaseDataType &data)
+{
+       _preprocessor->addInput(data);
+
+       auto originalImg = dynamic_pointer_cast<ImageDataType>(_preprocessor->getOutput().clone());
+       auto preprocessedImg = dynamic_cast<ImageDataType &>(_preprocessor->getOutput());
+
+       if (_user_cb) {
+               cv::Mat imgCv(cv::Size(preprocessedImg.width, preprocessedImg.height), CV_MAKETYPE(CV_8U, 3),
+                                         preprocessedImg.ptr);
+
+               _result_lock.lock();
+
+               if (_result.is_valid) {
+                       auto rects = _result.rects;
+                       auto landmarks = _result.landmark_points;
+                       auto handRects = _result.handRects;
+                       auto handLandmarks = _result.hand_landmark_points;
+
+                       _result_lock.unlock();
+
+                       int delta_x {}, delta_y {};
+
+                       for (unsigned int idx = 0; idx < rects.size(); ++idx) {
+                               cv::rectangle(imgCv, cv::Point(rects[idx].left, rects[idx].top),
+                                                         cv::Point(rects[idx].right, rects[idx].bottom), cv::Scalar(255, 0, 0), 3);
+
+                               delta_x = rects[idx].left;
+                               delta_y = rects[idx].top;
+                       }
+
+                       for (auto &point : landmarks) {
+                               // delta_x and delta_y are a base position where circle will be displayed.
+                               // point.x and point.y starts at 0 so move the base position on screen with delta_x and delta_y.
+                               int x = point.x + delta_x;
+                               int y = point.y + delta_y;
+                               cv::circle(imgCv, cv::Point(x, y), 2, cv::Scalar(255, 0, 0), 3);
+                       }
+
+                       for (unsigned int idx = 0; idx < handRects.size(); ++idx) {
+                               cv::rectangle(imgCv, cv::Point(handRects[idx].left, handRects[idx].top),
+                                                         cv::Point(handRects[idx].right, handRects[idx].bottom), cv::Scalar(255, 0, 0), 3);
+
+                               delta_x = handRects[idx].left;
+                               delta_y = handRects[idx].top;
+                       }
+
+                       for (auto &point : handLandmarks) {
+                               // delta_x and delta_y are a base position where circle will be displayed.
+                               // point.x and point.y starts at 0 so move the base position on screen with delta_x and delta_y.
+                               int x = point.x + delta_x;
+                               int y = point.y + delta_y;
+
+                               cv::circle(imgCv, cv::Point(x, y), 2, cv::Scalar(255, 0, 0), 3);
+                       }
+
+                       _user_cb(preprocessedImg.ptr, preprocessedImg.width, preprocessedImg.height, preprocessedImg.byte_per_pixel,
+                                        _user_data);
+
+                       _result_lock.lock();
+                       _result.is_valid = false;
+                       _result_lock.unlock();
+               }
+
+               _result_lock.unlock();
+       }
+
+       _preprocessor->invoke();
+
+       // Set is_owned to false to prevent from releasing copied image data
+       // because the owner of copied is AirGesture so the copied will be released
+       // in performAsync function.
+       // Ps. if is_owned is true, copied image data can be released while task manager is being performed.
+       originalImg->is_owned = false;
+
+       // Make sure to release copied buffer if incoming queue isn't empty so skipped pushing the buffer.
+       // If empty then push the buffer to the incoming queue. This buffer will be released at the end of runTaskManager function.
+       if (_async_manager->pushInput(*originalImg) != SINGLEO_ERROR_NONE)
+               delete originalImg->ptr;
+}
+
+bool AirGesture::isKeyValid(std::string key)
+{
+       auto it = _result_keys.find(key);
+       if (it == _result_keys.end())
+               return false;
+
+       return true;
+}
+
+void AirGesture::add_input(BaseDataType &input_data)
+{
+       if (_inputQ.size() > 0) {
+               SINGLEO_LOGE("Only one input data is allowed.");
+               throw InvalidOperation("Only one input data is allowed.");
+       }
+
+       if (input_data._data_type != DataType::IMAGE && input_data._data_type != DataType::FILE) {
+               SINGLEO_LOGE("Only IMAGE and FILE types are allowed.");
+               throw InvalidOperation("Only IMAGE and FILE types are allowed.");
+       }
+
+       std::lock_guard<std::mutex> lock(_inputMutex);
+
+       _inputQ.push(input_data.clone());
+}
+
+void AirGesture::runTaskManager(BaseDataType &input_data)
+{
+       _taskManager->addInput(input_data);
+       _taskManager->run();
+
+       updateResult(input_data);
+}
+
+void AirGesture::perform()
+{
+       ImagePreprocessor preprocessor;
+
+       // If input service is not set, input data comes from user.
+       // In this case, get input data from _inputs.
+       if (!_input_service) {
+               std::lock_guard<std::mutex> lock(_inputMutex);
+
+               _preprocessor->addInput(*_inputQ.front());
+               _inputQ.pop();
+       } else {
+               ImageDataType input_data;
+
+               _input_service->capture(input_data);
+               _preprocessor->addInput(input_data);
+       }
+
+       runTaskManager(_preprocessor->getOutput());
+}
+
+void AirGesture::performAsync()
+{
+       if (!_input_service) {
+               SINGLEO_LOGE("This API is valid only the case that input feed service is used.");
+               throw InvalidOperation("Invalid API request.");
+       }
+
+       _input_service->streamOn();
+
+       _async_mode = true;
+       _async_manager = make_unique<AsyncManager<ImageDataType, AirGestureResult> >();
+       _async_manager->registerInvokeCb(this, [this](IService *service, BaseDataType &data) {
+               auto air_gesture = static_cast<AirGesture *>(service);
+
+               air_gesture->runTaskManager(data);
+
+               auto imageData = dynamic_cast<ImageDataType &>(data);
+
+               // A given imageData - allocated by inputFeedCb function - has been used so release it here.
+               if (!imageData.is_owned)
+                       delete imageData.ptr;
+       });
+}
+
+void AirGesture::updateResult(BaseDataType &in_data)
+{
+       AirGestureResult airgesture_result;
+
+       for (auto &output : _taskManager->output()) {
+               if (output->_is_empty)
+                       continue;
+
+               if (output->_type == ResultType::FACE_DETECTION) {
+                       auto &rects = dynamic_cast<FdResultType &>(*output)._rects;
+
+                       airgesture_result.rects = rects;
+               } else if (output->_type == ResultType::FACE_LANDMARK) {
+                       auto &points = dynamic_cast<FldResultType &>(*output)._points;
+
+                       airgesture_result.landmark_points = points;
+               } else if (output->_type == ResultType::GAZE_TRACKING) {
+                       auto &points = dynamic_cast<GazeResultType &>(*output)._fpoints;
+
+                       airgesture_result.gaze_points = points;
+               } else if (output->_type == ResultType::HAND_DETECTION) {
+                       auto &rects = dynamic_cast<HdResultType &>(*output)._rects;
+
+                       if (_result.preHandRects.empty())
+                               airgesture_result.preHandRects = rects;
+                       else
+                               airgesture_result.preHandRects = _result.preHandRects;
+
+                       airgesture_result.handRects = rects;
+               } else if (output->_type == ResultType::HAND_LANDMARK) {
+                       auto &hand_landmark_points = dynamic_cast<HldResultType &>(*output)._points;
+
+                       airgesture_result.hand_landmark_points = hand_landmark_points;
+               }
+       }
+
+       if (_mode == AirGestureMode::FACE_TRACKING) {
+               airgesture_result.is_valid = true;
+               airgesture_result.num_of_objects = 1;
+               SINGLEO_LOGD("### FACE TRACKING MODE.");
+
+               if (!airgesture_result.rects.empty()) {
+                       _mode = AirGestureMode::GAZE_TRACKING;
+                       _taskManager->setBypass("face_detection", true, false);
+                       _taskManager->setBypass("hand_detection", false, false);
+               }
+       } else if (_mode == AirGestureMode::GAZE_TRACKING) {
+               airgesture_result.is_valid = true;
+               airgesture_result.num_of_objects = 1;
+               SINGLEO_LOGD("### GAZE TRACKING MODE.");
+
+               if (!airgesture_result.handRects.empty()) {
+                       _mode = AirGestureMode::HAND_TRACKING;
+                       _taskManager->setBypass("face_detection", true, false);
+                       _taskManager->setBypass("gaze_tracking", true, false);
+               }
+       } else if (_mode == AirGestureMode::HAND_TRACKING) {
+               airgesture_result.is_valid = true;
+               airgesture_result.num_of_objects = 1;
+               SINGLEO_LOGD("### HAND_TRACKING MODE.");
+
+               if (airgesture_result.handRects.empty()) {
+                       _mode = AirGestureMode::FACE_TRACKING;
+                       _taskManager->setBypass("hand_detection", true, false);
+                       _taskManager->setBypass("face_detection", false, false);
+                       _taskManager->setBypass("gaze_tracking", false, false);
+               }
+       }
+
+       if (_decoder)
+               _decoder->decode(airgesture_result);
+
+       if (_async_mode) {
+               _async_manager->pushOutput(airgesture_result);
+       } else {
+               _result_lock.lock();
+               _result = airgesture_result;
+               _result_lock.unlock();
+       }
+}
+
+void AirGesture::getResultCnt(unsigned int *cnt)
+{
+       if (_async_mode) {
+               auto result = _async_manager->popOutput();
+               std::unique_lock<std::mutex> lock(_result_lock);
+
+               _result = result;
+               *cnt = static_cast<unsigned int>(_result.num_of_objects);
+               return;
+       }
+
+       std::unique_lock<std::mutex> lock(_result_lock);
+
+       *cnt = static_cast<unsigned int>(_result.num_of_objects);
+}
+
+void AirGesture::getResultFloat(unsigned int idx, std::string key, float *value)
+{
+       transform(key.begin(), key.end(), key.begin(), ::toupper);
+
+       SINGLEO_LOGD("given key = %s", key.c_str());
+
+       if (!isKeyValid(key))
+               throw InvalidParameter("A given result key is inavlid.");
+
+       std::unique_lock<std::mutex> lock(_result_lock);
+
+       switch (_result_keys[key]) {
+       case AirGestureResultType::X:
+               *value = _result.point_x;
+               break;
+       case AirGestureResultType::Y:
+               *value = _result.point_y;
+               break;
+       }
+}
+
+void AirGesture::registerUserCallback(singleo_user_cb_t user_cb, void *user_data)
+{
+       _user_cb = user_cb;
+       _user_data = user_data;
+}
+
+void AirGesture::unregisterUserCallback()
+{
+       _user_cb = nullptr;
+       _user_data = nullptr;
+}
+
+}
+}
+}
diff --git a/services/air_gesture/src/TinyTrackerS.cpp b/services/air_gesture/src/TinyTrackerS.cpp
new file mode 100644 (file)
index 0000000..3c5858c
--- /dev/null
@@ -0,0 +1,87 @@
+/**
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * 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 "SingleoException.h"
+#include "SingleoLog.h"
+#include "TinyTrackerS.h"
+
+using namespace std;
+using namespace singleo::exception;
+
+namespace singleo
+{
+namespace services
+{
+namespace airgesture
+{
+TinyTrackerS::TinyTrackerS()
+{
+       // The output result of TinyTracker model corresponds to -10cm to 10cm,
+       // so it is necessary to convert the gaze position to the coordinates on the screen.
+       // For this purpose, we can obtain a linear equation that passes through two points (-10, 0)
+       // and (10, screen width) for the horizontal resolution of the screen and (-10, 0)
+       // and (10, screen height) for the vertical resolution.
+       // This allows us to map the output results onto the screen's coordinate system.
+       // Let's find the slope a(weight) and intercept b(intercept)
+       // a = (y2 - y1) / (x2 - x1)
+       _x_weight = _screenWidth / 20.0f;
+       _y_weight = _screenHeight / 20.0f;
+
+       // 0 = a * -10 + b ==> b = a * 10
+       _x_intercept = _x_weight * 10.0f;
+       _y_intercept = _y_weight * 10.0f;
+}
+
+void TinyTrackerS::getCurrentPosition(Rect previous, Rect current, int *x, int *y)
+{
+       // Get center position.
+       int center_x = (previous.left + previous.right) / 2;
+       int center_y = (previous.top + previous.bottom) / 2;
+       int center_x_new = (current.left + current.right) / 2;
+       int center_y_new = (current.top + current.bottom) / 2;
+
+       *x = center_x_new - center_x;
+       *y = center_y_new - center_y;
+}
+
+void TinyTrackerS::decode(ServiceBaseResultType &baseResult)
+{
+       if (baseResult._result_type != ServiceResultType::AIR_GESTURE) {
+               SINGLEO_LOGE("invalid result data type.");
+               throw InvalidParameter("invalid result data type.");
+       }
+
+       auto &result = dynamic_cast<AirGestureResult &>(baseResult);
+       if (!result.is_valid)
+               return;
+
+       if (result.gaze_points.empty())
+               return;
+
+       int delta_x = 0, delta_y = 0;
+
+       if (!result.preHandRects.empty() && !result.handRects.empty())
+               getCurrentPosition(result.preHandRects[0], result.handRects[0], &delta_x, &delta_y);
+
+       result.point_x = _x_weight * result.gaze_points[0].x + _x_intercept;
+       result.point_y = _screenHeight - (_y_weight * result.gaze_points[0].y + _y_intercept);
+       result.point_x -= delta_x;
+       result.point_y += delta_y;
+}
+
+}
+}
+}
\ No newline at end of file
index 1cdea4227bc6f236ac36ca38b2cbbd66a2b94989..9cc75268887902ec38712418fd2e4cd8e31f96f8 100644 (file)
@@ -18,6 +18,7 @@
 #define __AUTO_ZOOM_H__
 
 #include "IService.h"
+#include "SingleoException.h"
 #include "SingleoCommonTypes.h"
 #include "IInputService.h"
 #include "SingleoInferenceTypes.h"
@@ -78,6 +79,10 @@ public:
        void performAsync() override;
        void getResultCnt(unsigned int *cnt) override;
        void getResultInt(unsigned int idx, std::string key, unsigned int *value) override;
+       void getResultFloat(unsigned int idx, std::string key, float *value) override
+       {
+               throw exception::InvalidOperation("Not support yet.");
+       }
        void registerUserCallback(singleo_user_cb_t user_cb, void *user_data) override;
        void unregisterUserCallback() override;
 
index 946d0f467283175d9ae6ba4d130c0c31c8bbd039..89b1b16ece46dffba50b1861b9087ae0aff430b2 100644 (file)
@@ -32,7 +32,6 @@ public:
        ~Context()
        {}
 
-       ServiceType _service_type {};
        bool _async_mode { false };
        IService *_service_handle {};
 };
diff --git a/services/common/include/IDecoder.h b/services/common/include/IDecoder.h
new file mode 100644 (file)
index 0000000..7ab6900
--- /dev/null
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * 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.
+ */
+
+#ifndef __IDECODER_H__
+#define __IDECODER_H__
+
+#include "ServiceDataType.h"
+
+namespace singleo
+{
+namespace services
+{
+class IDecoder
+{
+public:
+       virtual void decode(ServiceBaseResultType &baseResult) = 0;
+};
+
+}
+}
+
+#endif
\ No newline at end of file
index 78c3510d3ec7b8b6db5b85621e608743e185f1b9..a21314adbc4d1a8221e1851b9feca9a6fb687361 100644 (file)
@@ -36,6 +36,7 @@ public:
        virtual void performAsync() = 0;
        virtual void getResultCnt(unsigned int *cnt) = 0;
        virtual void getResultInt(unsigned int idx, std::string key, unsigned int *value) = 0;
+       virtual void getResultFloat(unsigned int idx, std::string key, float *value) = 0;
        virtual void registerUserCallback(singleo_user_cb_t user_cb, void *user_data) = 0;
        virtual void unregisterUserCallback() = 0;
 };
index 73cffd5a4642e293f0ce9e6a92a673f42eacb48f..3a28fc1e84b932105583e1f84eb78e2e1b6315ee 100644 (file)
@@ -26,7 +26,7 @@ namespace services
 {
 // TODO
 
-enum class ServiceResultType { NONE, AUTO_ZOOM, FOCUS_FINDER };
+enum class ServiceResultType { NONE, AUTO_ZOOM, FOCUS_FINDER, AIR_GESTURE };
 
 struct ServiceBaseResultType {
        ServiceResultType _result_type { ServiceResultType::NONE };
index 783d0dc3e457777a49d6ff289e3924aa310b23de..8161f270a0098788b40188b310eb2682cb767e56 100644 (file)
@@ -37,6 +37,7 @@ void ImagePreprocessor::convertToCv(string file_name)
 void ImagePreprocessor::convertToCv(ImageDataType &image_data)
 {
        cv::Mat cv_image(cv::Size(image_data.width, image_data.height), CV_MAKETYPE(CV_8U, 3), image_data.ptr);
+       cv::cvtColor(cv_image, cv_image, cv::COLOR_BGR2RGB);
 
        _cv_image = cv_image;
 }
index c6644e77c5af15f0341cb26a75c52cdd81516729..3a7a35705f681c2de023937d28ee39af2f4d2b30 100644 (file)
@@ -78,6 +78,10 @@ public:
        void performAsync() override;
        void getResultCnt(unsigned int *cnt) override;
        void getResultInt(unsigned int idx, std::string key, unsigned int *value) override;
+       void getResultFloat(unsigned int idx, std::string key, float *value) override
+       {
+               throw exception::InvalidOperation("Not supported yet.");
+       }
        void registerUserCallback(singleo_user_cb_t user_cb, void *user_data) override;
        void unregisterUserCallback() override;
 };
index 03e8b0bf550c2bacf9e6847be8c43d738e744a1f..2dfd8c8bf9f996e8379ef0cef41eabe4c0f5b624 100644 (file)
@@ -192,6 +192,19 @@ int singleo_service_get_result_int(singleo_service_h handle, unsigned int idx, c
        return SINGLEO_ERROR_NONE;
 }
 
+int singleo_service_get_result_float(singleo_service_h handle, unsigned int idx, const char *key, float *value)
+{
+       try {
+               auto context = static_cast<Context *>(handle);
+               context->_service_handle->getResultFloat(idx, key, value);
+       } catch (const BaseException &e) {
+               SINGLEO_LOGE("%s", e.what());
+               return e.getError();
+       }
+
+       return SINGLEO_ERROR_NONE;
+}
+
 int singleo_service_register_user_callback(singleo_service_h handle, singleo_user_cb_t user_cb, void *user_data)
 {
        try {
index 8cb5dcf24d3aeb605f1e87362e5b355b386ddaed..33f21d0466447ba57942ab8455b162155adf2267 100644 (file)
@@ -41,6 +41,16 @@ IF (${USE_VISUALIZER})
     INSTALL(TARGETS test_singleo_on_screen DESTINATION ${CMAKE_INSTALL_BINDIR})
 ENDIF()
 
+SET(TEST_SOURCE_AIRGESTURE_LIST test_airgesture_on_screen.cpp)
+
+ADD_EXECUTABLE(test_singleo_airgesture ${TEST_SOURCE_AIRGESTURE_LIST})
+TARGET_COMPILE_DEFINITIONS(test_singleo_airgesture PRIVATE -DTEST_RES_PATH="${TEST_RES_PATH}")
+TARGET_INCLUDE_DIRECTORIES(test_singleo_airgesture PRIVATE ../../capi/ ../../common/include ../../log/include ../../visualizer/include)
+TARGET_LINK_LIBRARIES(test_singleo_airgesture
+    gtest gtest_main pthread singleo_service singleo_visualizer opencv_imgcodecs)
+
+INSTALL(TARGETS test_singleo_airgesture DESTINATION ${CMAKE_INSTALL_BINDIR})
+
 SET(TEST_SOURCE_TASK_MANAGER_LIST test_task_manager.cpp)
 
 ADD_EXECUTABLE(test_singleo_task_manger ${TEST_SOURCE_TASK_MANAGER_LIST})
diff --git a/test/services/test_airgesture_on_screen.cpp b/test/services/test_airgesture_on_screen.cpp
new file mode 100644 (file)
index 0000000..1613bad
--- /dev/null
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * 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 <iostream>
+#include <thread>
+#include <opencv2/opencv.hpp>
+
+#include <mutex>
+#include "gtest/gtest.h"
+#include "singleo_native_capi_internal.h"
+#include "singleo_util_visualizer_2d.h"
+#include "singleo_error.h"
+#include "SingleoCommonTypes.h"
+
+using namespace testing;
+using namespace singleo;
+using namespace std;
+
+struct Context {
+       std::mutex _mutex;
+       singleo_service_h handle {};
+       vector<Point> points;
+};
+
+void airgesture_callback(void *user_data)
+{
+       Context *context = static_cast<Context *>(user_data);
+       singleo_service_h handle = static_cast<singleo_service_h>(context->handle);
+       bool is_loop_exit = false;
+       unsigned long frame_number = 0;
+
+       while (!is_loop_exit) {
+               unsigned int cnt;
+
+               int ret = singleo_service_get_result_cnt(handle, &cnt);
+               if (ret != SINGLEO_ERROR_NONE)
+                       break;
+
+               cout << "cnt = " << cnt << " frame number = " << frame_number << endl;
+
+               for (unsigned int idx = 0; idx < cnt; ++idx) {
+                       float x {}, y {};
+
+                       ret = singleo_service_get_result_float(handle, idx, "X", &x);
+                       ASSERT_EQ(ret, SINGLEO_ERROR_NONE);
+
+                       ret = singleo_service_get_result_float(handle, idx, "Y", &y);
+                       ASSERT_EQ(ret, SINGLEO_ERROR_NONE);
+
+                       cout << "x = " << x << " y = " << y << endl;
+
+                       Point point = { static_cast<int>(x), static_cast<int>(y) };
+
+                       std::unique_lock<std::mutex> lock(context->_mutex);
+                       context->points.push_back(point);
+               }
+
+               if (++frame_number > 5000 && cnt > 0)
+                       is_loop_exit = true;
+       }
+}
+
+void user_callback(unsigned char *buffer, unsigned int width, unsigned int height, unsigned int bytes_per_pixel,
+                                  void *user_data)
+{
+       Context *context = static_cast<Context *>(user_data);
+       cv::Mat result(cv::Size(width, height), CV_MAKETYPE(CV_8U, 3), buffer);
+
+       cv::Mat resized, flipped;
+
+       cv::flip(result, flipped, 1);
+       cv::resize(flipped, resized, cv::Size(1600, 900));
+
+       std::unique_lock<std::mutex> lock(context->_mutex);
+
+       if (!context->points.empty()) {
+               cv::circle(resized, cv::Point(context->points[0].x, context->points[0].y), 3, cv::Scalar(0, 255, 0), 5);
+               singleo_util_visualizer_2d(resized, NULL);
+
+               context->points.clear();
+       }
+}
+
+TEST(AirGestureAsyncOnScreenTest, InferenceRequestWithCameraInputFeedShouldBeOk)
+{
+       Context context {};
+
+       int ret = singleo_service_create("service=AirGesture, input_feed=camera, camera_id=0, fps=30, async=1",
+                                                                        &context.handle);
+       ASSERT_EQ(ret, SINGLEO_ERROR_NONE);
+
+       ret = singleo_service_register_user_callback(context.handle, user_callback, &context);
+       ASSERT_EQ(ret, SINGLEO_ERROR_NONE);
+
+       ret = singleo_service_perform(context.handle);
+       ASSERT_EQ(ret, SINGLEO_ERROR_NONE);
+
+       unique_ptr<thread> thread_handle = make_unique<thread>(&airgesture_callback, static_cast<void *>(&context));
+
+       thread_handle->join();
+
+       ret = singleo_service_unregister_user_callback(context.handle);
+       ASSERT_EQ(ret, SINGLEO_ERROR_NONE);
+
+       ret = singleo_service_destroy(context.handle);
+       ASSERT_EQ(ret, SINGLEO_ERROR_NONE);
+}
index 7ac19930d76fed4db29340b86e0fdf28f23a32f5..c55915ac942d3e4fdc3f2327200272e9c8c140ea 100644 (file)
@@ -18,7 +18,6 @@
 #define __SINGLEO_UTIL_RENDER_2D_H__
 
 #include <opencv2/opencv.hpp>
-#include <mv_common.h>
 #include <cmath>
 
 #define pixfmt_fourcc(a, b, c, d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))