[MediaVision] Add APIs for inference detectors and classifier (#985)
authorhsgwon <haesu.gwon@samsung.com>
Tue, 1 Oct 2019 02:37:22 +0000 (11:37 +0900)
committerGitHub <noreply@github.com>
Tue, 1 Oct 2019 02:37:22 +0000 (11:37 +0900)
* [MediaVision] Add APIs for inference detectors and classifier

19 files changed:
src/Tizen.Multimedia.Vision/Interop/Interop.MediaVision.Common.cs [changed mode: 0755->0644]
src/Tizen.Multimedia.Vision/Interop/Interop.MediaVision.Inference.cs [new file with mode: 0644]
src/Tizen.Multimedia.Vision/MediaVision/EngineConfiguration.cs [changed mode: 0644->0755]
src/Tizen.Multimedia.Vision/MediaVision/FaceDetectionResult.cs [new file with mode: 0644]
src/Tizen.Multimedia.Vision/MediaVision/FaceDetector.cs [changed mode: 0755->0644]
src/Tizen.Multimedia.Vision/MediaVision/FaceRecognitionModel.cs [changed mode: 0644->0755]
src/Tizen.Multimedia.Vision/MediaVision/FaceTrackingModel.cs [changed mode: 0644->0755]
src/Tizen.Multimedia.Vision/MediaVision/FacialLandmarkDetector.cs [new file with mode: 0644]
src/Tizen.Multimedia.Vision/MediaVision/ImageClassificationResult.cs [new file with mode: 0644]
src/Tizen.Multimedia.Vision/MediaVision/ImageClassifier.cs [new file with mode: 0644]
src/Tizen.Multimedia.Vision/MediaVision/ImageObject.cs [changed mode: 0644->0755]
src/Tizen.Multimedia.Vision/MediaVision/ImageTrackingModel.cs [changed mode: 0644->0755]
src/Tizen.Multimedia.Vision/MediaVision/InferenceModelConfiguration.cs [new file with mode: 0644]
src/Tizen.Multimedia.Vision/MediaVision/InferenceType.cs [new file with mode: 0755]
src/Tizen.Multimedia.Vision/MediaVision/MediaVisionError.cs
src/Tizen.Multimedia.Vision/MediaVision/MediaVisionSource.cs [changed mode: 0644->0755]
src/Tizen.Multimedia.Vision/MediaVision/ObjectDetectionResult.cs [new file with mode: 0644]
src/Tizen.Multimedia.Vision/MediaVision/ObjectDetector.cs [new file with mode: 0644]
src/Tizen.Multimedia.Vision/MediaVision/VisionFeatures.cs [new file with mode: 0644]

old mode 100755 (executable)
new mode 100644 (file)
index 3762322..a6e1645
@@ -80,7 +80,7 @@ internal static partial class Interop
         };
     }
 
-    internal static Tizen.Multimedia.Rectangle[] ToApiStruct(MediaVision.Rectangle[] rects)
+    internal static Tizen.Multimedia.Rectangle[] ToApiStruct(this MediaVision.Rectangle[] rects)
     {
         var result = new Tizen.Multimedia.Rectangle[rects.Length];
 
@@ -91,6 +91,17 @@ internal static partial class Interop
         return result;
     }
 
+    internal static MediaVision.Rectangle[] ToMarShalable(this Tizen.Multimedia.Rectangle[] rects)
+    {
+        var result = new MediaVision.Rectangle[rects.Length];
+
+        for (int i = 0; i < rects.Length; i++)
+        {
+            result[i] = rects[i].ToMarshalable();
+        }
+        return result;
+    }
+
     /// <summary>
     /// Interop for Media Vision APIs.
     /// </summary>
@@ -176,6 +187,10 @@ internal static partial class Interop
             [DllImport(Libraries.MediaVision, EntryPoint = "mv_engine_config_set_string_attribute")]
             internal static extern MediaVisionError SetString(IntPtr handle, string name, string value);
 
+            [DllImport(Libraries.MediaVision, EntryPoint = "mv_engine_config_set_array_string_attribute")]
+            internal static extern MediaVisionError SetStringArray(IntPtr handle, string name,
+                [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] string[] value, int size);
+
             [DllImport(Libraries.MediaVision, EntryPoint = "mv_engine_config_get_double_attribute")]
             internal static extern MediaVisionError GetDouble(IntPtr handle, string name, out double value);
 
@@ -187,6 +202,10 @@ internal static partial class Interop
 
             [DllImport(Libraries.MediaVision, EntryPoint = "mv_engine_config_get_string_attribute")]
             internal static extern MediaVisionError GetString(IntPtr handle, string name, out IntPtr value);
+
+            [DllImport(Libraries.MediaVision, EntryPoint = "mv_engine_config_get_array_string_attribute")]
+            internal static extern MediaVisionError GetStringArray(IntPtr handle, string name,
+                out IntPtr value, out int size);
         }
     }
 }
diff --git a/src/Tizen.Multimedia.Vision/Interop/Interop.MediaVision.Inference.cs b/src/Tizen.Multimedia.Vision/Interop/Interop.MediaVision.Inference.cs
new file mode 100644 (file)
index 0000000..9f93b0e
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2019 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.
+ */
+
+using System;
+using System.Runtime.InteropServices;
+using Tizen.Multimedia.Vision;
+
+/// <summary>
+/// Interop APIs.
+/// </summary>
+internal static partial class Interop
+{
+    /// <summary>
+    /// Interop for Media Vision APIs.
+    /// </summary>
+    internal static partial class MediaVision
+    {
+        /// <summary>
+        /// Interop for Face APIs.
+        /// </summary>
+        internal static partial class Inference
+        {
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+            internal delegate void FaceDetectedCallback(IntPtr source, int numberOfFaces,
+                [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] float[] confidences,
+                [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Rectangle[] location,
+                IntPtr userData = default(IntPtr));
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+            internal delegate void FacialLandmarkDetectedCallback(IntPtr source, int numberOfLandmarks,
+                [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Point[] locations,
+                IntPtr userData = default(IntPtr));
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+            internal delegate void ImageClassifedCallback(IntPtr source, int numberOfClasses,
+                [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] int[] indices,
+                [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] string[] names,
+                [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] float[] confidences,
+                IntPtr userData = default(IntPtr));
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+            internal delegate void ObjectDetectedCallback(IntPtr source, int numberOfObjects,
+                [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] int[] indices,
+                [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] string[] names,
+                [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] float[] confidences,
+                [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Rectangle[] location,
+                IntPtr userData = default(IntPtr));
+
+            [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+            internal delegate bool SupportedBackendCallback(string backend, bool isSupported,
+                IntPtr userData = default(IntPtr));
+
+
+            [DllImport(Libraries.MediaVision, EntryPoint = "mv_inference_create")]
+            internal static extern MediaVisionError Create(out IntPtr handle);
+
+            [DllImport(Libraries.MediaVision, EntryPoint = "mv_inference_destroy")]
+            internal static extern MediaVisionError Destroy(IntPtr handle);
+
+            [DllImport(Libraries.MediaVision, EntryPoint = "mv_inference_configure")]
+            internal static extern MediaVisionError Configure(IntPtr handle, IntPtr engineConfig);
+
+            [DllImport(Libraries.MediaVision, EntryPoint = "mv_inference_prepare")]
+            internal static extern MediaVisionError Load(IntPtr handle);
+
+            [DllImport(Libraries.MediaVision, EntryPoint = "mv_inference_foreach_supported_engine")]
+            internal static extern MediaVisionError ForeachSupportedBackend(IntPtr handle,
+                SupportedBackendCallback callback, IntPtr userData = default(IntPtr));
+
+            [DllImport(Libraries.MediaVision, EntryPoint = "mv_inference_image_classify")]
+            internal static extern MediaVisionError ClassifyImage(IntPtr source, IntPtr inference,
+                IntPtr roi, ImageClassifedCallback callback, IntPtr userData = default(IntPtr));
+
+            [DllImport(Libraries.MediaVision, EntryPoint = "mv_inference_object_detect")]
+            internal static extern MediaVisionError DetectObject(IntPtr source, IntPtr inference,
+                ObjectDetectedCallback callback, IntPtr userData = default(IntPtr));
+
+            [DllImport(Libraries.MediaVision, EntryPoint = "mv_inference_face_detect")]
+            internal static extern MediaVisionError DetectFace(IntPtr source, IntPtr inference,
+                FaceDetectedCallback callback, IntPtr userData = default(IntPtr));
+
+            [DllImport(Libraries.MediaVision, EntryPoint = "mv_inference_facial_landmark_detect")]
+            internal static extern MediaVisionError DetectFacialLandmark(IntPtr source, IntPtr inference,
+                IntPtr roi, FacialLandmarkDetectedCallback callback, IntPtr userData = default(IntPtr));
+        }
+    }
+}
old mode 100644 (file)
new mode 100755 (executable)
index 964f00f..38cda12
@@ -111,6 +111,11 @@ namespace Tizen.Multimedia.Vision
             EngineConfig.SetString(Handle, key, value).Validate("Failed to set attribute");
         }
 
+        internal void Set(string key, string [] value)
+        {
+            EngineConfig.SetStringArray(Handle, key, value, value.Length).Validate("Failed to set attribute");
+        }
+
         internal int GetInt(string key)
         {
             int value = 0;
@@ -147,6 +152,38 @@ namespace Tizen.Multimedia.Vision
             }
         }
 
+        internal string[] GetStringArray(string key)
+        {
+            IntPtr values = IntPtr.Zero;
+            int size = 0;
+
+            try
+            {
+                EngineConfig.GetStringArray(Handle, key, out values, out size).
+                    Validate("Failed to get the value");
+
+                var current = values;
+                var result = new string[size];
+
+                for (int i = 0; i < size; i++)
+                {
+                    result[i] = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(current));
+                    current = (IntPtr)((long)current + Marshal.SizeOf(typeof(IntPtr)));
+                }
+
+                return result;
+            }
+            finally
+            {
+                var current = values;
+                for (int i = 0; i < size; i++)
+                {
+                    LibcSupport.Free(Marshal.ReadIntPtr(current));
+                    current = (IntPtr)((long)current + Marshal.SizeOf(typeof(IntPtr)));
+                }
+            }
+        }
+
         /// <summary>
         /// Releases all resources used by the <see cref="EngineConfiguration"/> object.
         /// </summary>
diff --git a/src/Tizen.Multimedia.Vision/MediaVision/FaceDetectionResult.cs b/src/Tizen.Multimedia.Vision/MediaVision/FaceDetectionResult.cs
new file mode 100644 (file)
index 0000000..899e1e6
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2019 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.
+ */
+
+using System.Collections.ObjectModel;
+
+namespace Tizen.Multimedia.Vision
+{
+    /// <summary>
+    /// Provides the ability to get the result of face detection using <see cref="InferenceModelConfiguration"/> and
+    /// <see cref="FaceDetector.DetectAsync(MediaVisionSource, InferenceModelConfiguration)"/>.
+    /// </summary>
+    /// <since_tizen> 6 </since_tizen>
+    public class FaceDetectionResult
+    {
+        internal FaceDetectionResult(float confidence, global::Interop.MediaVision.Rectangle location)
+        {
+            Confidence = confidence;
+            Location = location.ToApiStruct();
+        }
+
+        /// <summary>
+        /// Gets the confidence of detected face.
+        /// </summary>
+        /// <since_tizen> 6 </since_tizen>
+        public float Confidence { get; }
+
+        /// <summary>
+        /// Gets the location of detected face.
+        /// </summary>
+        /// <since_tizen> 6 </since_tizen>
+        public Rectangle Location { get; }
+    }
+}
old mode 100755 (executable)
new mode 100644 (file)
index 71c4d8c..bec227f
  */
 
 using System;
+using System.Collections.Generic;
+using System.Linq;
 using System.Threading.Tasks;
 using InteropFace = Interop.MediaVision.Face;
+using InteropInference = Interop.MediaVision.Inference;
 
 namespace Tizen.Multimedia.Vision
 {
     /// <summary>
     /// Provides the ability to detect faces on image sources.
     /// </summary>
+    /// <remarks>
+    /// If you want to use face detection based on inference engine(<see cref="InferenceBackendType"/>),
+    /// please use <see cref="DetectAsync(MediaVisionSource, InferenceModelConfiguration)"/>.
+    /// </remarks>
     /// <since_tizen> 4 </since_tizen>
     public static class FaceDetector
     {
-
         /// <summary>
         /// Detects faces on the source.<br/>
         /// Each time when DetectAsync is called, a set of the detected faces at the media source are received asynchronously.
@@ -43,7 +49,7 @@ namespace Tizen.Multimedia.Vision
         /// <since_tizen> 4 </since_tizen>
         public static async Task<Rectangle[]> DetectAsync(MediaVisionSource source)
         {
-            return await DetectAsync(source, null);
+            return await DetectAsync(source, (FaceDetectionConfiguration)null);
         }
 
         /// <summary>
@@ -103,5 +109,89 @@ namespace Tizen.Multimedia.Vision
                 }
             };
         }
+
+        /// <summary>
+        /// Detects faces on the source image using inference engine set in <paramref name="config"/>.<br/>
+        /// Each time when DetectAsync is called, a set of the detected faces at the media source are received asynchronously.
+        /// </summary>
+        /// <remarks>
+        /// If there's no detected face, empty collection will be returned.
+        /// </remarks>
+        /// <feature>http://tizen.org/feature/vision.inference</feature>
+        /// <feature>http://tizen.org/feature/vision.inference.face</feature>
+        /// <param name="source">The source of the media where faces will be detected.</param>
+        /// <param name="config">The engine's configuration that will be used for detecting.</param>
+        /// <returns>
+        /// A task that represents the asynchronous detect operation.<br/>
+        /// If there's no detected face, empty collection will be returned.
+        /// </returns>
+        /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="config"/> is null.</exception>
+        /// <exception cref="InvalidOperationException">Internal error.</exception>
+        /// <exception cref="NotSupportedException">The feature is not supported.</exception>
+        /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
+        /// <seealso cref="InferenceModelConfiguration"/>
+        /// <since_tizen> 6 </since_tizen>
+        public static async Task<IEnumerable<FaceDetectionResult>> DetectAsync(MediaVisionSource source,
+            InferenceModelConfiguration config)
+        {
+            // `vision.inference` feature is already checked, when config is created.
+            ValidationUtil.ValidateFeatureSupported(VisionFeatures.InferenceFace);
+
+            if (source == null)
+            {
+                throw new ArgumentNullException(nameof(source));
+            }
+            if (config == null)
+            {
+                throw new ArgumentNullException(nameof(config));
+            }
+
+            var tcs = new TaskCompletionSource<IEnumerable<FaceDetectionResult>>();
+
+            using (var cb = ObjectKeeper.Get(GetCallback(tcs)))
+            {
+                InteropInference.DetectFace(source.Handle, config.GetHandle(), cb.Target).
+                    Validate("Failed to detect face.");
+
+                return await tcs.Task;
+            }
+        }
+
+        private static InteropInference.FaceDetectedCallback GetCallback(TaskCompletionSource<IEnumerable<FaceDetectionResult>> tcs)
+        {
+            return (IntPtr sourceHandle, int numberOfFaces, float[] confidences,
+                global::Interop.MediaVision.Rectangle[] locations, IntPtr _) =>
+            {
+                try
+                {
+                    if (!tcs.TrySetResult(GetResults(numberOfFaces, confidences, locations)))
+                    {
+                        Log.Error(MediaVisionLog.Tag, "Failed to set face detection result.");
+                    }
+                }
+                catch (Exception e)
+                {
+                    tcs.TrySetException(e);
+                }
+            };
+        }
+
+        private static IEnumerable<FaceDetectionResult> GetResults(int number, float[] confidences,
+            global::Interop.MediaVision.Rectangle[] locations)
+        {
+            if (number == 0)
+            {
+                return Enumerable.Empty<FaceDetectionResult>();
+            }
+
+            var results = new List<FaceDetectionResult>();
+
+            for (int i = 0; i < number; i++)
+            {
+                results.Add(new FaceDetectionResult(confidences[i], locations[i]));
+            }
+
+            return results;
+        }
     }
 }
diff --git a/src/Tizen.Multimedia.Vision/MediaVision/FacialLandmarkDetector.cs b/src/Tizen.Multimedia.Vision/MediaVision/FacialLandmarkDetector.cs
new file mode 100644 (file)
index 0000000..7a50bdf
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2019 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.
+ */
+
+using System;
+using System.Collections;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using InteropInference = Interop.MediaVision.Inference;
+
+namespace Tizen.Multimedia.Vision
+{
+    /// <summary>
+    /// Provides the ability to detect facial landmarks on image source using inference engine.
+    /// </summary>
+    /// <since_tizen> 6 </since_tizen>
+    public static class FacialLandmarkDetector
+    {
+        /// <summary>
+        /// Detects facial landmarks on the source image using inference engine set in <paramref name="config"/>.<br/>
+        /// </summary>
+        /// <remarks>
+        /// To set region-of-interest area in source image, please set <see cref="InferenceModelConfiguration.Roi"/>.
+        /// If not set, full image area will be used to detect facial landmark.
+        /// </remarks>
+        /// <feature>http://tizen.org/feature/vision.inference</feature>
+        /// <feature>http://tizen.org/feature/vision.inference.face</feature>
+        /// <param name="source">The source of the media where faces will be detected.</param>
+        /// <param name="config">The engine's configuration that will be used for detecting.</param>
+        /// <returns>
+        /// A task that represents the asynchronous detect operation.<br/>
+        /// If there's no detected facial landmark, empty collection will be returned.
+        /// </returns>
+        /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="config"/> is null.</exception>
+        /// <exception cref="InvalidOperationException">Internal error.</exception>
+        /// <exception cref="NotSupportedException">The feature is not supported.</exception>
+        /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
+        /// <seealso cref="InferenceModelConfiguration"/>
+        /// <since_tizen> 6 </since_tizen>
+        public static async Task<IEnumerable<Point>> DetectAsync(MediaVisionSource source,
+            InferenceModelConfiguration config)
+        {
+            // `vision.inference` feature is already checked, when config is created.
+            ValidationUtil.ValidateFeatureSupported(VisionFeatures.InferenceFace);
+
+            if (source == null)
+            {
+                throw new ArgumentNullException(nameof(source));
+            }
+            if (config == null)
+            {
+                throw new ArgumentNullException(nameof(config));
+            }
+
+            var tcs = new TaskCompletionSource<IEnumerable<Point>>();
+
+            using (var cb = ObjectKeeper.Get(GetCallback(tcs)))
+            {
+                IntPtr roiUnmanaged = IntPtr.Zero;
+
+                try
+                {
+                    if (config.Roi.HasValue)
+                    {
+                        var roi = config.Roi.Value.ToMarshalable();
+
+                        roiUnmanaged = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(global::Interop.MediaVision.Rectangle)));
+                        Marshal.WriteIntPtr(roiUnmanaged, IntPtr.Zero);
+                        Marshal.StructureToPtr(roi, roiUnmanaged, false);
+                    }
+
+                    InteropInference.DetectFacialLandmark(source.Handle, config.GetHandle(), roiUnmanaged, cb.Target).
+                        Validate("Failed to detect facial landmark.");
+                }
+                finally
+                {
+                    if (roiUnmanaged != IntPtr.Zero)
+                    {
+                        Marshal.FreeHGlobal(roiUnmanaged);
+                    }
+                }
+
+                return await tcs.Task;
+            }
+        }
+
+        private static InteropInference.FacialLandmarkDetectedCallback GetCallback(TaskCompletionSource<IEnumerable<Point>> tcs)
+        {
+            return (IntPtr sourceHandle, int numberOfLandmarks, global::Interop.MediaVision.Point[] locations, IntPtr _) =>
+            {
+                try
+                {
+
+                    if (!tcs.TrySetResult(GetResults(numberOfLandmarks, locations)))
+                    {
+                        Log.Error(MediaVisionLog.Tag, "Failed to set facial landmark detection result.");
+                    }
+                }
+                catch (Exception e)
+                {
+                    tcs.TrySetException(e);
+                }
+            };
+        }
+
+        private static IEnumerable<Point> GetResults(int number, global::Interop.MediaVision.Point[] locations)
+        {
+            if (number == 0)
+            {
+                return Enumerable.Empty<Point>();
+            }
+
+            var results = new List<Point>();
+
+            for (int i = 0; i < number; i++)
+            {
+                results.Add(locations[i].ToApiStruct());
+            }
+
+            return results;
+        }
+    }
+}
diff --git a/src/Tizen.Multimedia.Vision/MediaVision/ImageClassificationResult.cs b/src/Tizen.Multimedia.Vision/MediaVision/ImageClassificationResult.cs
new file mode 100644 (file)
index 0000000..d7741b1
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2019 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.
+ */
+using System.Collections.ObjectModel;
+
+namespace Tizen.Multimedia.Vision
+{
+    /// <summary>
+    /// Provides the ability to get the result of face detection using <see cref="InferenceModelConfiguration"/> and
+    /// <see cref="ImageClassifier"/>.
+    /// </summary>
+    /// <since_tizen> 6 </since_tizen>
+    public class ImageClassificationResult
+    {
+        internal ImageClassificationResult(int indice, string name, float confidence)
+        {
+            Indice = indice;
+            Name = name;
+            Confidence = confidence;
+        }
+
+        /// <summary>
+        /// Gets the indice of detected object.
+        /// </summary>
+        /// <since_tizen> 6 </since_tizen>
+        public int Indice { get; }
+
+        /// <summary>
+        /// Gets the name of detected object.
+        /// </summary>
+        /// <since_tizen> 6 </since_tizen>
+        public string Name { get; }
+
+        /// <summary>
+        /// Gets the confidence of detected object.
+        /// </summary>
+        /// <since_tizen> 6 </since_tizen>
+        public float Confidence { get; }
+    }
+}
diff --git a/src/Tizen.Multimedia.Vision/MediaVision/ImageClassifier.cs b/src/Tizen.Multimedia.Vision/MediaVision/ImageClassifier.cs
new file mode 100644 (file)
index 0000000..44ca7b6
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2019 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.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using InteropInference = Interop.MediaVision.Inference;
+
+namespace Tizen.Multimedia.Vision
+{
+    /// <summary>
+    /// Provides the ability to classify image objects on image source using inference engine.
+    /// </summary>
+    /// <since_tizen> 6 </since_tizen>
+    public static class ImageClassifier
+    {
+        /// <summary>
+        /// Classifies image objects on the source image using inference engine set in <paramref name="config"/>.<br/>
+        /// Each time when DetectAsync is called, a set of the detected faces at the media source are received asynchronously.
+        /// </summary>
+        /// <feature>http://tizen.org/feature/vision.inference</feature>
+        /// <feature>http://tizen.org/feature/vision.inference.image</feature>
+        /// <param name="source">The source of the media where faces will be detected.</param>
+        /// <param name="config">The engine's configuration that will be used for classifying.</param>
+        /// <returns>
+        /// A task that represents the asynchronous classify operation.<br/>
+        /// If there's no classified image object, empty collection will be returned.
+        /// </returns>
+        /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="config"/> is null.</exception>
+        /// <exception cref="InvalidOperationException">Internal error.</exception>
+        /// <exception cref="NotSupportedException">The feature is not supported.</exception>
+        /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
+        /// <seealso cref="InferenceModelConfiguration"/>
+        /// <since_tizen> 6 </since_tizen>
+        public static async Task<IEnumerable<ImageClassificationResult>> ClassifyAsync(MediaVisionSource source,
+            InferenceModelConfiguration config)
+        {
+            // `vision.inference` feature is already checked, when config is created.
+            ValidationUtil.ValidateFeatureSupported(VisionFeatures.InferenceImage);
+
+            if (source == null)
+            {
+                throw new ArgumentNullException(nameof(source));
+            }
+            if (config == null)
+            {
+                throw new ArgumentNullException(nameof(config));
+            }
+
+            var tcs = new TaskCompletionSource<IEnumerable<ImageClassificationResult>>();
+
+            using (var cb = ObjectKeeper.Get(GetCallback(tcs)))
+            {
+                IntPtr roiUnmanaged = IntPtr.Zero;
+
+                try
+                {
+                    if (config.Roi.HasValue)
+                    {
+                        var roi = config.Roi.Value.ToMarshalable();
+
+                        roiUnmanaged = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(global::Interop.MediaVision.Rectangle)));
+                        Marshal.WriteIntPtr(roiUnmanaged, IntPtr.Zero);
+                        Marshal.StructureToPtr(roi, roiUnmanaged, false);
+                    }
+
+                    InteropInference.ClassifyImage(source.Handle, config.GetHandle(), roiUnmanaged, cb.Target).
+                        Validate("Failed to classify image.");
+                }
+                finally
+                {
+                    if (roiUnmanaged != IntPtr.Zero)
+                    {
+                        Marshal.FreeHGlobal(roiUnmanaged);
+                    }
+                }
+
+                return await tcs.Task;
+            }
+        }
+
+        private static InteropInference.ImageClassifedCallback GetCallback(TaskCompletionSource<IEnumerable<ImageClassificationResult>> tcs)
+        {
+            return (IntPtr sourceHandle, int numberOfClasses, int[] indices, string[] names, float[] confidences, IntPtr _) =>
+            {
+                try
+                {
+                    if (!tcs.TrySetResult(GetResults(numberOfClasses, indices, names, confidences)))
+                    {
+                        Log.Error(MediaVisionLog.Tag, "Failed to set image classification result.");
+                    }
+                }
+                catch (Exception e)
+                {
+                    tcs.TrySetException(e);
+                }
+            };
+        }
+
+        private static IEnumerable<ImageClassificationResult> GetResults(int number, int[] indices,
+            string[] names, float[] confidences)
+        {
+            if (number == 0)
+            {
+                return Enumerable.Empty<ImageClassificationResult>();
+            }
+
+            var results = new List<ImageClassificationResult>();
+
+            for (int i = 0; i < number; i++)
+            {
+                results.Add(new ImageClassificationResult(indices[i], names[i], confidences[i]));
+            }
+
+            return results;
+        }
+    }
+}
diff --git a/src/Tizen.Multimedia.Vision/MediaVision/InferenceModelConfiguration.cs b/src/Tizen.Multimedia.Vision/MediaVision/InferenceModelConfiguration.cs
new file mode 100644 (file)
index 0000000..2b53959
--- /dev/null
@@ -0,0 +1,583 @@
+/*
+ * Copyright (c) 2019 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.
+ */
+
+using System;
+using System.Linq;
+using System.IO;
+using System.Collections.Generic;
+using InteropInference = Interop.MediaVision.Inference;
+
+namespace Tizen.Multimedia.Vision
+{
+    /// <summary>
+    /// Represents a configuration of <see cref="FaceDetector"/>, <see cref="FacialLandmarkDetector"/>,
+    /// <see cref="ImageClassifier"/> and <see cref="ObjectDetector"/>.
+    /// </summary>
+    /// <remarks>
+    /// 'Inference model' means pre-learned data, which is represented by <see cref="ConfigurationFilePath"/> and
+    /// <see cref="WeightFilePath"/>, <see cref="CategoryFilePath"/>.<br/>
+    /// If user want to use tizen default inference model and its related value,
+    /// Please refer Tizen guide page(https://developer.tizen.org/development/guides/.net-application).
+    /// </remarks>
+    /// <feature>http://tizen.org/feature/vision.inference.face</feature>
+    /// <feature>http://tizen.org/feature/vision.inference.image</feature>
+    /// <since_tizen> 6 </since_tizen>
+    public class InferenceModelConfiguration : EngineConfiguration
+    {
+        private IntPtr _inferenceHandle = IntPtr.Zero;
+
+        private const string _keyModelConfigurationFilePath = "MV_INFERENCE_MODEL_CONFIGURATION_FILE_PATH";
+        private const string _keyModelWeightFilePath = "MV_INFERENCE_MODEL_WEIGHT_FILE_PATH";
+        private const string _keyModelUserFilePath = "MV_INFERENCE_MODEL_USER_FILE_PATH";
+        private const string _keyModelMeanValue = "MV_INFERENCE_MODEL_MEAN_VALUE";
+        private const string _keyModelStdValue = "MV_INFERENCE_MODEL_STD_VALUE";
+        private const string _keyBackendType = "MV_INFERENCE_BACKEND_TYPE";
+        private const string _keyTargetType = "MV_INFERENCE_TARGET_TYPE";
+        private const string _keyInputTensorWidth = "MV_INFERENCE_INPUT_TENSOR_WIDTH";
+        private const string _keyInputTensorHeight = "MV_INFERENCE_INPUT_TENSOR_HEIGHT";
+        private const string _keyInputTensorChannels = "MV_INFERENCE_INPUT_TENSOR_CHANNELS";
+        private const string _keyInputNodeName = "MV_INFERENCE_INPUT_NODE_NAME";
+        private const string _keyOutputNodeNames = "MV_INFERENCE_OUTPUT_NODE_NAMES";
+        private const string _keyOutputMaxNumber = "MV_INFERENCE_OUTPUT_MAX_NUMBER";
+        private const string _keyConfidenceThreshold = "MV_INFERENCE_CONFIDENCE_THRESHOLD";
+
+        // The following strings are fixed in native and will not be changed.
+        private const string _backendTypeOpenCV = "opencv";
+        private const string _backendTypeTFLite = "tflite";
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="InferenceModelConfiguration"/> class.
+        /// </summary>
+        /// <feature>http://tizen.org/feature/vision.inference.face</feature>
+        /// <feature>http://tizen.org/feature/vision.inference.image</feature>
+        /// <exception cref="NotSupportedException">The feature is not supported.</exception>
+        /// <since_tizen> 6 </since_tizen>
+        public InferenceModelConfiguration() : base("inference")
+        {
+            InteropInference.Create(out _inferenceHandle).Validate("Failed to create inference configuration");
+        }
+
+        /// <summary>
+        /// Loads inference model data and its related attributes.
+        /// </summary>
+        /// <remarks>
+        /// Before calling this method, user should set all properties which is required by each inference model.<br/>
+        /// The properties set after calling this method will not be affected in the result.
+        /// </remarks>
+        /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
+        /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
+        /// <feature>http://tizen.org/feature/vision.inference.face</feature>
+        /// <feature>http://tizen.org/feature/vision.inference.image</feature>
+        /// <exception cref="FileNotFoundException">
+        /// <see cref="ConfigurationFilePath"/>, <see cref="WeightFilePath"/> or <see cref="CategoryFilePath"/> have invalid path.
+        /// </exception>
+        /// <exception cref="FileFormatException">Invalid data type is used in inference model data.</exception>
+        /// <exception cref="InvalidDataException">
+        /// Inference model data contains unsupported operations in current backend version.
+        /// -or-<br/>
+        /// Invalid data type is used in inference model data.<br/>
+        /// </exception>
+        /// <exception cref="InvalidOperationException">Internal operation error.</exception>
+        /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
+        /// <since_tizen> 6 </since_tizen>
+        public void LoadInferenceModel()
+        {
+            InteropInference.Configure(_inferenceHandle, GetHandle(this)).
+                Validate("Failed to configure inference model.");
+
+            var ret = InteropInference.Load(_inferenceHandle);
+            if (ret == MediaVisionError.InvalidData)
+            {
+                throw new InvalidDataException("Inference model data contains unsupported operations in current backend version.");
+            }
+            else if (ret == MediaVisionError.NotSupportedFormat)
+            {
+                throw new FileFormatException("Invalid data type is used in inference model data.");
+            }
+            ret.Validate("Failed to load inference model.");
+        }
+
+        internal IntPtr GetHandle()
+        {
+            return _inferenceHandle;
+        }
+
+        private IEnumerable<InferenceBackendType> _supportedBackend;
+
+        /// <summary>
+        /// Gets the list of inference backend engine which is supported in the current device.
+        /// </summary>
+        /// <returns>If there's no supported backend, empty collection will be returned.</returns>
+        /// <since_tizen> 6 </since_tizen>
+        public IEnumerable<InferenceBackendType> SupportedBackend
+        {
+            get
+            {
+                if (_supportedBackend == null)
+                {
+                    GetSupportedBackend();
+                }
+
+                return _supportedBackend.Any() ? _supportedBackend : Enumerable.Empty<InferenceBackendType>();
+            }
+        }
+
+        private void GetSupportedBackend()
+        {
+            var supportedBackend = new List<InferenceBackendType>();
+
+            InteropInference.SupportedBackendCallback cb = (backend, isSupported, _) =>
+            {
+                if (isSupported && backend != null)
+                {
+                    switch (backend)
+                    {
+                        case _backendTypeOpenCV:
+                            supportedBackend.Add(InferenceBackendType.OpenCV);
+                            break;
+                        case _backendTypeTFLite:
+                            supportedBackend.Add(InferenceBackendType.TFLite);
+                            break;
+                    }
+                }
+
+                return true;
+            };
+
+            InteropInference.ForeachSupportedBackend(_inferenceHandle, cb, IntPtr.Zero).
+                Validate("Failed to get supported backend");
+
+            _supportedBackend = supportedBackend;
+        }
+
+        /// <summary>
+        /// Gets or sets the path of inference model's configuration data file.
+        /// </summary>
+        /// <exception cref="ArgumentNullException">Input file path is null.</exception>
+        /// <since_tizen> 6 </since_tizen>
+        public string ConfigurationFilePath
+        {
+            get
+            {
+                return GetString(_keyModelConfigurationFilePath);
+            }
+            set
+            {
+                if (value == null)
+                {
+                    throw new ArgumentNullException(nameof(value), "File path is null.");
+                }
+
+                Set(_keyModelConfigurationFilePath, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the path of inference model's weight file.
+        /// </summary>
+        /// <exception cref="ArgumentNullException">Input file path is null.</exception>
+        /// <since_tizen> 6 </since_tizen>
+        public string WeightFilePath
+        {
+            get
+            {
+                return GetString(_keyModelWeightFilePath);
+            }
+            set
+            {
+                if (value == null)
+                {
+                    throw new ArgumentNullException(nameof(value), "File path is null.");
+                }
+
+                Set(_keyModelWeightFilePath, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the path of inference model's category file.
+        /// </summary>
+        /// <remarks>
+        /// This value should be set to use <see cref="ImageClassifier"/> or <see cref="ObjectDetector"/>.
+        /// </remarks>
+        /// <exception cref="ArgumentNullException">Input file path is null.</exception>
+        /// <since_tizen> 6 </since_tizen>
+        public string CategoryFilePath
+        {
+            get
+            {
+                return GetString(_keyModelUserFilePath);
+            }
+            set
+            {
+                if (value == null)
+                {
+                    throw new ArgumentNullException(nameof(value), "File path is null.");
+                }
+
+                Set(_keyModelUserFilePath, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the inference model's mean value.
+        /// </summary>
+        /// <remarks>It should be greater than or equal to 0.</remarks>
+        /// <exception cref="ArgumentOutOfRangeException">The value is invalid.</exception>
+        /// <since_tizen> 6 </since_tizen>
+        public double MeanValue
+        {
+            get
+            {
+                return GetDouble(_keyModelMeanValue);
+            }
+            set
+            {
+                if (value < 0)
+                {
+                    throw new ArgumentOutOfRangeException(nameof(value), value,
+                        $"Value should be greater than or equal to 0");
+                }
+
+                Set(_keyModelMeanValue, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the inference model's STD(Standard deviation) value.
+        /// </summary>
+        /// <remarks>It should be greater than or equal to 0.</remarks>
+        /// <exception cref="ArgumentOutOfRangeException">The value is invalid.</exception>
+        /// <since_tizen> 6 </since_tizen>
+        public double StdValue
+        {
+            get
+            {
+                return GetDouble(_keyModelStdValue);
+            }
+            set
+            {
+                if (value < 0)
+                {
+                    throw new ArgumentOutOfRangeException(nameof(value), value,
+                        $"Value should be greater than or equal to 0");
+                }
+
+                Set(_keyModelStdValue, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the inference model's backend engine.
+        /// </summary>
+        /// <remarks>The default backend type is <see cref="InferenceBackendType.OpenCV"/></remarks>
+        /// <exception cref="ArgumentException"><paramref name="value"/> is not valid.</exception>
+        /// <exception cref="NotSupportedException">The engine type is not supported.</exception>
+        /// <seealso cref="SupportedBackend"/>
+        /// <since_tizen> 6 </since_tizen>
+        public InferenceBackendType Backend
+        {
+            get
+            {
+                return (InferenceBackendType)GetInt(_keyBackendType);
+            }
+            set
+            {
+                ValidationUtil.ValidateEnum(typeof(InferenceBackendType), value, nameof(Backend));
+
+                if (!SupportedBackend.Contains(value))
+                {
+                    throw new NotSupportedException("Not supported engine type. " +
+                        "Please check supported engine using 'SupportedBackendType'.");
+                }
+
+                Set(_keyBackendType, (int)value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the inference model's target.
+        /// </summary>
+        /// <remarks>
+        /// The default target is <see cref="InferenceTargetType.CPU"/>.<br/>
+        /// If target doesn't support <see cref="InferenceTargetType.GPU"/> and <see cref="InferenceTargetType.Custom"/>,
+        /// <see cref="InferenceTargetType.CPU"/> will be used internally, despite the user's choice.
+        /// </remarks>
+        /// <exception cref="ArgumentException"><paramref name="value"/> is not valid.</exception>
+        /// <since_tizen> 6 </since_tizen>
+        public InferenceTargetType Target
+        {
+            get
+            {
+                return (InferenceTargetType)GetInt(_keyTargetType);
+            }
+            set
+            {
+                ValidationUtil.ValidateEnum(typeof(InferenceTargetType), value, nameof(Target));
+
+                Set(_keyTargetType, (int)value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the size of inference model's tensor.
+        /// </summary>
+        /// <remarks>
+        /// Both width and height of tensor should be greater than 0.<br/>
+        /// 'Size(-1, -1) is allowed when the intention is to use original image source size as TensorSize.
+        /// </remarks>
+        /// <exception cref="ArgumentException">
+        /// Only one of <paramref name="value.Width"/> or <paramref name="value.Height"/> have -1.</exception>
+        /// <exception cref="ArgumentOutOfRangeException">The value is invalid.</exception>
+        /// <since_tizen> 6 </since_tizen>
+        public Size TensorSize
+        {
+            get
+            {
+                var width = GetInt(_keyInputTensorWidth);
+                var height = GetInt(_keyInputTensorHeight);
+
+                return new Size(width, height);
+            }
+            set
+            {
+                if ((value.Width == -1 && value.Height != -1) || (value.Height == -1 && value.Width != -1))
+                {
+                    throw new ArgumentException("Both width and height must be set to -1, or greater than 0.");
+                }
+
+                if (value.Width == 0 || value.Width <= -2)
+                {
+                    throw new ArgumentOutOfRangeException(nameof(value), value,
+                        "Both width and height must be set to -1, or greater than 0.");
+                }
+
+                if (value.Height == 0 || value.Height <= -2)
+                {
+                    throw new ArgumentOutOfRangeException(nameof(value), value,
+                        "Both width and height must be set to -1, or greater than 0.");
+                }
+
+                Set(_keyInputTensorWidth, value.Width);
+                Set(_keyInputTensorHeight, value.Height);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the number of inference model's tensor channel.
+        /// </summary>
+        /// <remarks>
+        /// For example, for RGB colorspace this value should be set to 3<br/>
+        /// It should be greater than 0.
+        /// </remarks>
+        /// <exception cref="ArgumentOutOfRangeException">The value is invalid.</exception>
+        /// <since_tizen> 6 </since_tizen>
+        public int TensorChannels
+        {
+            get
+            {
+                return GetInt(_keyInputTensorChannels);
+            }
+            set
+            {
+                if (value <= 0)
+                {
+                    throw new ArgumentOutOfRangeException(nameof(value), value, "Tensor channel should be greater than 0.");
+                }
+
+                Set(_keyInputTensorChannels, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the name of an input node
+        /// </summary>
+        /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
+        /// <since_tizen> 6 </since_tizen>
+        public string InputNodeName
+        {
+            get
+            {
+                return GetString(_keyInputNodeName);
+            }
+            set
+            {
+                if (value == null)
+                {
+                    throw new ArgumentNullException(nameof(value), "InputNodeName can't be null.");
+                }
+
+                Set(_keyInputNodeName, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the name of an output node
+        /// </summary>
+        /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
+        /// <since_tizen> 6 </since_tizen>
+        public IList<string> OutputNodeName
+        {
+            get
+            {
+                return GetStringArray(_keyOutputNodeNames);
+            }
+            set
+            {
+                if (value == null)
+                {
+                    throw new ArgumentNullException(nameof(value), "OutputNodeName can't be null.");
+                }
+
+                var name = new string[value.Count];
+                value.CopyTo(name, 0);
+                Set(_keyOutputNodeNames, name);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the maximum output number of detection or classification.
+        /// </summary>
+        /// <remarks>
+        /// The input value over 10 will be set to 10 and the input value under 1 will be set to 1.<br/>
+        /// This value can be used to decide the size of <see cref="Roi"/>, it's length should be the same.
+        /// </remarks>
+        /// <since_tizen> 6 </since_tizen>
+        public int MaxOutputNumber
+        {
+            get
+            {
+                return GetInt(_keyOutputMaxNumber);
+            }
+            set
+            {
+                Set(_keyOutputMaxNumber, value > 10 ? 10 : value < 1 ? 1 : value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the threshold of confidence.
+        /// </summary>
+        /// <remarks>
+        /// The vaild range is greater than or equal to 0.0 and less than or equal to 1.0.<br/>
+        /// The value 1.0 means maximum accuracy.
+        /// </remarks>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="value"/>is out of range.</exception>
+        /// <since_tizen> 6 </since_tizen>
+        public double ConfidenceThreshold
+        {
+            get
+            {
+                return GetDouble(_keyConfidenceThreshold);
+            }
+            set
+            {
+                if (value < 0.0)
+                {
+                    throw new ArgumentOutOfRangeException(nameof(value), value,
+                        "Confidence threshold should be greater than or equal to 0.0.");
+                }
+                if (value > 1.0)
+                {
+                    throw new ArgumentOutOfRangeException(nameof(value), value,
+                        "Confidence threshold should be less than or equal to 1.0.");
+                }
+
+                Set(_keyConfidenceThreshold, value);
+            }
+        }
+
+        private Rectangle? _roi;
+
+        /// <summary>
+        /// Gets or sets the ROI(Region Of Interest) of <see cref="ImageClassifier"/> and <see cref="FacialLandmarkDetector"/>
+        /// </summary>
+        /// <remarks>
+        /// Default value is null. If Roi is null, the entire region of <see cref="MediaVisionSource"/> will be analyzed.
+        /// </remarks>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///     The width of <paramref name="value"/> is less than or equal to zero.<br/>
+        ///     -or-<br/>
+        ///     The height of <paramref name="value"/> is less than or equal to zero.<br/>
+        ///     -or-<br/>
+        ///     The x position of <paramref name="value"/> is less than zero.<br/>
+        ///     -or-<br/>
+        ///     The y position of <paramref name="value"/> is less than zero.
+        /// </exception>
+        /// <seealso cref="MaxOutputNumber"/>
+        /// <since_tizen> 6 </since_tizen>
+        public Rectangle? Roi
+        {
+            get
+            {
+                return _roi;
+            }
+            set
+            {
+                if (value != null)
+                {
+                    ValidateRoi(value.Value);
+                    _roi = value;
+                }
+            }
+        }
+
+        private static void ValidateRoi(Rectangle roi)
+        {
+            if (roi.Width <= 0)
+            {
+                throw new ArgumentOutOfRangeException("Roi.Width", roi.Width,
+                    "The width of roi can't be less than or equal to zero.");
+            }
+
+            if (roi.Height <= 0)
+            {
+                throw new ArgumentOutOfRangeException("Roi.Height", roi.Height,
+                    "The height of roi can't be less than or equal to zero.");
+            }
+
+            if (roi.X < 0)
+            {
+                throw new ArgumentOutOfRangeException("Roi.X", roi.X,
+                    "The x position of roi can't be less than zero.");
+            }
+
+            if (roi.Y < 0)
+            {
+                throw new ArgumentOutOfRangeException("Roi.Y", roi.Y,
+                    "The y position of roi can't be less than zero.");
+            }
+        }
+
+        /// <summary>
+        /// Releases the resources used by the <see cref="InferenceModelConfiguration"/> object.
+        /// </summary>
+        /// <param name="disposing">
+        /// true to release both managed and unmanaged resources, otherwise false to release only unmanaged resources.
+        /// </param>
+        /// <since_tizen> 6 </since_tizen>
+        protected override void Dispose(bool disposing)
+        {
+            base.Dispose(disposing);
+
+            if (_inferenceHandle != IntPtr.Zero)
+            {
+                InteropInference.Destroy(_inferenceHandle).Validate("Failed to destroy inference configuration");
+                _inferenceHandle = IntPtr.Zero;
+            }
+        }
+    }
+}
diff --git a/src/Tizen.Multimedia.Vision/MediaVision/InferenceType.cs b/src/Tizen.Multimedia.Vision/MediaVision/InferenceType.cs
new file mode 100755 (executable)
index 0000000..d2519f7
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2019 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.
+ */
+
+namespace Tizen.Multimedia.Vision
+{
+    /// <summary>
+    /// Specifies the type of inference backend.
+    /// </summary>
+    /// <since_tizen> 6 </since_tizen>
+    public enum InferenceBackendType
+    {
+        /// <summary>
+        /// OpenCV backend type
+        /// </summary>
+        OpenCV,
+
+        /// <summary>
+        /// Tensor Flow Lite backend type
+        /// </summary>
+        TFLite
+    }
+
+    /// <summary>
+    /// Specifies the type of target. It's used for running inference backend.
+    /// </summary>
+    /// <since_tizen> 6 </since_tizen>
+    public enum InferenceTargetType
+    {
+        /// <summary>
+        /// CPU target
+        /// </summary>
+        CPU,
+
+        /// <summary>
+        /// GPU target
+        /// </summary>
+        GPU,
+
+        /// <summary>
+        /// Custom target
+        /// </summary>
+        Custom
+    }
+}
index ef43a24..40e3a74 100755 (executable)
@@ -82,7 +82,12 @@ namespace Tizen.Multimedia.Vision
         /// <summary>
         /// Invalid path (Since 3.0).
         /// </summary>
-        InvalidPath = MediaVisionErrorCode | 0x04
+        InvalidPath = MediaVisionErrorCode | 0x04,
+        /// <summary>
+        /// Not supported engine.
+        /// </summary>
+        /// <since_tizen> 6 </since_tizen>
+        NotSupportedEngine = MediaVisionErrorCode | 0x05
     }
 
     internal static class MediaVisionErrorExtensions
@@ -97,6 +102,7 @@ namespace Tizen.Multimedia.Vision
             switch (error)
             {
                 case MediaVisionError.NotSupported:
+                case MediaVisionError.NotSupportedEngine:
                     throw new NotSupportedException(msg);
                 case MediaVisionError.MsgTooLong:
                     throw new ArgumentException($"{msg} : Message too long.");
diff --git a/src/Tizen.Multimedia.Vision/MediaVision/ObjectDetectionResult.cs b/src/Tizen.Multimedia.Vision/MediaVision/ObjectDetectionResult.cs
new file mode 100644 (file)
index 0000000..9922341
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019 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.
+ */
+using System.Collections.ObjectModel;
+
+namespace Tizen.Multimedia.Vision
+{
+    /// <summary>
+    /// Provides the ability to get the result of object detection using <see cref="InferenceModelConfiguration"/> and
+    /// <see cref="ObjectDetector"/>.
+    /// </summary>
+    /// <since_tizen> 6 </since_tizen>
+    public class ObjectDetectionResult
+    {
+        internal ObjectDetectionResult(int indice, string name, float confidence,
+            global::Interop.MediaVision.Rectangle location)
+        {
+            Indice = indice;
+            Name = name;
+            Confidence = confidence;
+            Location = location.ToApiStruct();
+        }
+
+        /// <summary>
+        /// Gets the indice of detected object.
+        /// </summary>
+        /// <since_tizen> 6 </since_tizen>
+        public int Indice { get; }
+
+        /// <summary>
+        /// Gets the name of detected object.
+        /// </summary>
+        /// <since_tizen> 6 </since_tizen>
+        public string Name { get; }
+
+        /// <summary>
+        /// Gets the confidence of detected object.
+        /// </summary>
+        /// <since_tizen> 6 </since_tizen>
+        public float Confidence { get; }
+
+        /// <summary>
+        /// Gets the location of detected object.
+        /// </summary>
+        /// <since_tizen> 6 </since_tizen>
+        public Rectangle Location { get; }
+    }
+}
diff --git a/src/Tizen.Multimedia.Vision/MediaVision/ObjectDetector.cs b/src/Tizen.Multimedia.Vision/MediaVision/ObjectDetector.cs
new file mode 100644 (file)
index 0000000..31caef6
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2019 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.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using InteropInference = Interop.MediaVision.Inference;
+
+namespace Tizen.Multimedia.Vision
+{
+    /// <summary>
+    /// Provides the ability to detect objects and get its locations on image source using inference engine.
+    /// </summary>
+    /// <since_tizen> 6 </since_tizen>
+    public static class ObjectDetector
+    {
+        /// <summary>
+        /// Detects objects and gets its locations on the source image using inference engine set in <paramref name="config"/>.<br/>
+        /// Each time when DetectAsync is called, a set of the detected objects at the media source are received asynchronously.
+        /// </summary>
+        /// <feature>http://tizen.org/feature/vision.inference</feature>
+        /// <feature>http://tizen.org/feature/vision.inference.image</feature>
+        /// <param name="source">The source of the media where faces will be detected.</param>
+        /// <param name="config">The engine's configuration that will be used for detecting.</param>
+        /// <returns>
+        /// A task that represents the asynchronous detect operation.<br/>
+        /// If there's no detected object, empty collection will be returned.
+        /// </returns>
+        /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="config"/> is null.</exception>
+        /// <exception cref="InvalidOperationException">Internal error.</exception>
+        /// <exception cref="NotSupportedException">The feature is not supported.</exception>
+        /// <exception cref="UnauthorizedAccessException">The caller has no required privilege.</exception>
+        /// <seealso cref="InferenceModelConfiguration"/>
+        /// <since_tizen> 6 </since_tizen>
+        public static async Task<IEnumerable<ObjectDetectionResult>> DetectAsync(MediaVisionSource source,
+            InferenceModelConfiguration config)
+        {
+            // `vision.inference` feature is already checked, when config is created.
+            ValidationUtil.ValidateFeatureSupported(VisionFeatures.InferenceImage);
+
+            if (source == null)
+            {
+                throw new ArgumentNullException(nameof(source));
+            }
+            if (config == null)
+            {
+                throw new ArgumentNullException(nameof(config));
+            }
+
+            var tcs = new TaskCompletionSource<IEnumerable<ObjectDetectionResult>>();
+
+            using (var cb = ObjectKeeper.Get(GetCallback(tcs)))
+            {
+                InteropInference.DetectObject(source.Handle, config.GetHandle(), cb.Target).
+                    Validate("Failed to detect object.");
+
+                return await tcs.Task;
+            }
+        }
+
+        private static InteropInference.ObjectDetectedCallback GetCallback(TaskCompletionSource<IEnumerable<ObjectDetectionResult>> tcs)
+        {
+            return (IntPtr sourceHandle, int numberOfObjects, int[] indices, string[] names, float[] confidences,
+                global::Interop.MediaVision.Rectangle[] locations, IntPtr _) =>
+            {
+                try
+                {
+                    if (!tcs.TrySetResult(GetResults(numberOfObjects, indices, names, confidences, locations)))
+                    {
+                        Log.Error(MediaVisionLog.Tag, "Failed to set object detection result.");
+                    }
+                }
+                catch (Exception e)
+                {
+                    tcs.TrySetException(e);
+                }
+            };
+        }
+
+        private static IEnumerable<ObjectDetectionResult> GetResults(int number, int[] indices,
+            string[] names, float[] confidences, global::Interop.MediaVision.Rectangle[] locations)
+        {
+            if (number == 0)
+            {
+                return Enumerable.Empty<ObjectDetectionResult>();
+            }
+
+            var results = new List<ObjectDetectionResult>();
+
+            for (int i = 0; i < number; i++)
+            {
+                results.Add(new ObjectDetectionResult(indices[i], names[i], confidences[i], locations[i]));
+            }
+
+            return results;
+        }
+    }
+}
diff --git a/src/Tizen.Multimedia.Vision/MediaVision/VisionFeatures.cs b/src/Tizen.Multimedia.Vision/MediaVision/VisionFeatures.cs
new file mode 100644 (file)
index 0000000..31e25cd
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2019 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.
+ */
+
+namespace Tizen.Multimedia
+{
+    internal static class VisionFeatures
+    {
+        internal const string InferenceFace = "http://tizen.org/feature/vision.inference.face";
+        internal const string InferenceImage = "http://tizen.org/feature/vision.inference.image";
+    }
+}