[MediaVision] Refactoring
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia / MediaVision / FaceRecognizer.cs
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 using System;
18 using System.Runtime.InteropServices;
19 using System.Threading.Tasks;
20 using InteropFace = Interop.MediaVision.Face;
21
22 namespace Tizen.Multimedia
23 {
24     /// <summary>
25     /// Provides the ability to recognize faces, face expressions and eye condition on image sources.
26     /// </summary>
27     public static class FaceRecognizer
28     {
29
30         /// <summary>
31         /// Performs face recognition on the source with <see cref="FaceRecognitionModel"/>.
32         /// </summary>
33         /// <param name="source">The <see cref="MediaVisionSource"/> of the media to recognize faces for.</param>
34         /// <param name="recognitionModel">The <see cref="FaceRecognitionConfiguration"/> to be used for recognition.</param>
35         /// <returns>A task that represents the asynchronous recognition operation.</returns>
36         /// <exception cref="ArgumentNullException">
37         ///     <paramref name="source"/> is null.\n
38         ///     - or -\n
39         ///     <paramref name="recognitionModel"/> is null.
40         /// </exception>
41         /// <exception cref="NotSupportedException">The feature is not supported.</exception>
42         /// <exception cref="ObjectDisposedException"><paramref name="source"/> has already been disposed of.</exception>
43         /// <exception cref="InvalidOperationException"><paramref name="recognitionModel"/> is untrained model.</exception>
44         public static async Task<FaceRecognitionResult> RecognizeAsync(MediaVisionSource source,
45             FaceRecognitionModel recognitionModel)
46         {
47             return await InvokeRecognizeAsync(source, recognitionModel, null, null);
48         }
49
50         /// <summary>
51         /// Performs face recognition on the source with <see cref="FaceRecognitionModel"/> and a bounding box.
52         /// </summary>
53         /// <param name="source">The <see cref="MediaVisionSource"/> of the media to recognize faces for.</param>
54         /// <param name="recognitionModel">The <see cref="FaceRecognitionModel"/> to be used for recognition.</param>
55         /// <param name="bound">Rectangular box bounding face image on the source.</param>
56         /// <returns>A task that represents the asynchronous recognition operation.</returns>
57         /// <exception cref="ArgumentNullException">
58         ///     <paramref name="source"/> is null.\n
59         ///     - or -\n
60         ///     <paramref name="recognitionModel"/> is null.
61         /// </exception>
62         /// <exception cref="NotSupportedException">The feature is not supported.</exception>
63         /// <exception cref="ObjectDisposedException"><paramref name="source"/> has already been disposed of.</exception>
64         /// <exception cref="InvalidOperationException"><paramref name="recognitionModel"/> is untrained model.</exception>
65         public static async Task<FaceRecognitionResult> RecognizeAsync(MediaVisionSource source,
66             FaceRecognitionModel recognitionModel, Rectangle bound)
67         {
68             return await InvokeRecognizeAsync(source, recognitionModel, bound, null);
69         }
70
71         /// <summary>
72         /// Performs face recognition on the source with <see cref="FaceRecognitionModel"/> and <see cref="FaceRecognitionConfiguration"/>.
73         /// </summary>
74         /// <param name="source">The <see cref="MediaVisionSource"/> of the media to recognize faces for.</param>
75         /// <param name="recognitionModel">The <see cref="FaceRecognitionModel"/> to be used for recognition.</param>
76         /// <param name="config">The configuration used for recognition. This value can be null.</param>
77         /// <returns>A task that represents the asynchronous recognition operation.</returns>
78         /// <exception cref="ArgumentNullException">
79         ///     <paramref name="source"/> is null.\n
80         ///     - or -\n
81         ///     <paramref name="recognitionModel"/> is null.
82         /// </exception>
83         /// <exception cref="NotSupportedException">The feature is not supported.</exception>
84         /// <exception cref="ObjectDisposedException">
85         ///     <paramref name="source"/> has already been disposed of.\n
86         ///     - or -\n
87         ///     <paramref name="config"/> has already been disposed of.
88         /// </exception>
89         /// <exception cref="InvalidOperationException"><paramref name="recognitionModel"/> is untrained model.</exception>
90         public static async Task<FaceRecognitionResult> RecognizeAsync(MediaVisionSource source,
91             FaceRecognitionModel recognitionModel, FaceRecognitionConfiguration config)
92         {
93             return await InvokeRecognizeAsync(source, recognitionModel, null, config);
94         }
95
96
97         /// <summary>
98         /// Performs face recognition on the source  with <see cref="FaceRecognitionModel"/>, <see cref="FaceRecognitionConfiguration"/>
99         /// and a bounding box.
100         /// </summary>
101         /// <param name="source">The <see cref="MediaVisionSource"/> of the media to recognize faces for.</param>
102         /// <param name="recognitionModel">The <see cref="FaceRecognitionModel"/> to be used for recognition.</param>
103         /// <param name="bound">Rectangular box bounding face image on the source.</param>
104         /// <param name="config">The <see cref="FaceRecognitionConfiguration"/> used for recognition. This value can be null.</param>
105         /// <returns>A task that represents the asynchronous recognition operation.</returns>
106         /// <exception cref="ArgumentNullException">
107         ///     <paramref name="source"/> is null.\n
108         ///     - or -\n
109         ///     <paramref name="recognitionModel"/> is null.
110         /// </exception>
111         /// <exception cref="NotSupportedException">The feature is not supported.</exception>
112         /// <exception cref="ObjectDisposedException">
113         ///     <paramref name="source"/> has already been disposed of.\n
114         ///     - or -\n
115         ///     <paramref name="config"/> has already been disposed of.
116         /// </exception>
117         /// <exception cref="InvalidOperationException"><paramref name="recognitionModel"/> is untrained model.</exception>
118         public static async Task<FaceRecognitionResult> RecognizeAsync(MediaVisionSource source,
119             FaceRecognitionModel recognitionModel, Rectangle bound, FaceRecognitionConfiguration config)
120         {
121             return await InvokeRecognizeAsync(source, recognitionModel, bound, config);
122         }
123
124         private static MediaVisionError InvokeRecognize(IntPtr sourceHandle, IntPtr modelHandle,
125             IntPtr configHandle, InteropFace.RecognizedCallback cb, Rectangle? area)
126         {
127             if (area == null)
128             {
129                 return InteropFace.Recognize(sourceHandle, modelHandle, configHandle, IntPtr.Zero, cb);
130             }
131
132             var rect = area.Value.ToMarshalable();
133             return InteropFace.Recognize(sourceHandle, modelHandle, configHandle, ref rect, cb);
134         }
135
136         private static async Task<FaceRecognitionResult> InvokeRecognizeAsync(MediaVisionSource source,
137             FaceRecognitionModel recognitionModel, Rectangle? area,
138             FaceRecognitionConfiguration config)
139         {
140             if (source == null)
141             {
142                 throw new ArgumentNullException(nameof(source));
143             }
144             if (recognitionModel == null)
145             {
146                 throw new ArgumentNullException(nameof(recognitionModel));
147             }
148
149             TaskCompletionSource<FaceRecognitionResult> tcs = new TaskCompletionSource<FaceRecognitionResult>();
150
151             using (var cb = ObjectKeeper.Get(GetRecognizedCallback(tcs)))
152             {
153                 InvokeRecognize(source.Handle, recognitionModel.Handle, EngineConfiguration.GetHandle(config),
154                     cb.Target, area).Validate("Failed to perform face recognition.");
155
156                 return await tcs.Task;
157             }
158         }
159
160         private static FaceRecognitionResult CreateRecognitionResult(
161              IntPtr faceLocationPtr, IntPtr faceLabelPtr, double confidence)
162         {
163             int faceLabel = 0;
164             if (faceLabelPtr != IntPtr.Zero)
165             {
166                 faceLabel = Marshal.ReadInt32(faceLabelPtr);
167             }
168
169             Rectangle? faceLocation = null;
170             if (faceLocationPtr != IntPtr.Zero)
171             {
172                 var area = Marshal.PtrToStructure<Interop.MediaVision.Rectangle>(faceLocationPtr);
173                 faceLocation = area.ToApiStruct();
174             }
175
176             return new FaceRecognitionResult(faceLabelPtr != IntPtr.Zero, confidence, faceLabel, faceLocation);
177         }
178
179         private static InteropFace.RecognizedCallback GetRecognizedCallback(
180             TaskCompletionSource<FaceRecognitionResult> tcs)
181         {
182             return (IntPtr sourceHandle, IntPtr recognitionModelHandle,
183                 IntPtr engineCfgHandle, IntPtr faceLocationPtr, IntPtr faceLabelPtr, double confidence, IntPtr _) =>
184             {
185                 try
186                 {
187                     if (!tcs.TrySetResult(CreateRecognitionResult(faceLocationPtr, faceLabelPtr, confidence)))
188                     {
189                         Log.Error(MediaVisionLog.Tag, "Failed to set result");
190                     }
191                 }
192                 catch (Exception e)
193                 {
194                     MultimediaLog.Error(MediaVisionLog.Tag, "Setting recognition result failed.", e);
195                     tcs.TrySetException(e);
196                 }
197             };
198         }
199
200         /// <summary>
201         /// Determines eye-blink condition on media source.
202         /// </summary>
203         /// <param name="source">The source of the media to recognize eye-blink condition for.</param>
204         /// <param name="bound">The bounding the face at the source.</param>
205         /// <param name="config">The configuration used for eye-blink condition recognition. This value can be null.</param>
206         /// <returns>A task that represents the asynchronous recognition operation.</returns>
207         /// <exception cref="ArgumentNullException"><paramref name="source"/> is null.</exception>
208         /// <exception cref="ObjectDisposedException"><paramref name="source"/> has already been disposed of.</exception>
209         /// <exception cref="NotSupportedException">The feature is not supported.</exception>
210         public static async Task<EyeCondition> RecognizeEyeConditionAsync(MediaVisionSource source,
211             Rectangle bound)
212         {
213             return await RecognizeEyeConditionAsync(source, bound, null);
214         }
215
216         /// <summary>
217         /// Determines eye-blink condition on media source.
218         /// </summary>
219         /// <param name="source">The source of the media to recognize eye-blink condition for.</param>
220         /// <param name="bound">The bounding the face at the source.</param>
221         /// <param name="config">The configuration used for eye-blink condition recognition. This value can be null.</param>
222         /// <returns>A task that represents the asynchronous recognition operation.</returns>
223         /// <exception cref="ArgumentNullException"><paramref name="source"/> is null.</exception>
224         /// <exception cref="ObjectDisposedException">
225         ///     <paramref name="source"/> has already been disposed of.\n
226         ///     - or -\n
227         ///     <paramref name="config"/> has already been disposed of.
228         /// </exception>
229         /// <exception cref="NotSupportedException">The feature is not supported.</exception>
230         public static async Task<EyeCondition> RecognizeEyeConditionAsync(MediaVisionSource source,
231             Rectangle bound, FaceRecognitionConfiguration config)
232         {
233             if (source == null)
234             {
235                 throw new ArgumentNullException(nameof(source));
236             }
237
238             TaskCompletionSource<EyeCondition> tcs = new TaskCompletionSource<EyeCondition>();
239
240             InteropFace.EyeConditionRecognizedCallback cb = (IntPtr sourceHandle, IntPtr engineCfgHandle,
241                 Interop.MediaVision.Rectangle faceLocation, EyeCondition eyeCondition, IntPtr _) =>
242             {
243                 Log.Info(MediaVisionLog.Tag, $"Eye condition recognized, eye condition : {eyeCondition}");
244                 if (!tcs.TrySetResult(eyeCondition))
245                 {
246                     Log.Error(MediaVisionLog.Tag, "Failed to set eye condition result");
247                 }
248             };
249
250             using (var cbKeeper = ObjectKeeper.Get(cb))
251             {
252                 InteropFace.RecognizeEyeCondition(source.Handle, EngineConfiguration.GetHandle(config),
253                     bound.ToMarshalable(), cb).Validate("Failed to perform eye condition recognition.");
254
255                 return await tcs.Task;
256             }
257         }
258
259         /// <summary>
260         /// Determines facial expression on media source.
261         /// </summary>
262         /// <param name="source">The source of the media to recognize facial expression for.</param>
263         /// <param name="bound">The location bounding the face at the source.</param>
264         /// <param name="config">The configuration used for expression recognition.</param>
265         /// <returns>A task that represents the asynchronous recognition operation.</returns>
266         /// <exception cref="ArgumentNullException"><paramref name="source"/> is null.</exception>
267         /// <exception cref="ObjectDisposedException"><paramref name="source"/> has already been disposed of.</exception>
268         /// <exception cref="NotSupportedException">The feature is not supported.</exception>
269         public static async Task<FacialExpression> RecognizeFacialExpressionAsync(MediaVisionSource source,
270             Rectangle bound)
271         {
272             return await RecognizeFacialExpressionAsync(source, bound, null);
273         }
274
275         /// <summary>
276         /// Determines facial expression on media source.
277         /// </summary>
278         /// <param name="source">The source of the media to recognize facial expression for.</param>
279         /// <param name="bound">The location bounding the face at the source.</param>
280         /// <param name="config">The configuration used for expression recognition. This value can be null.</param>
281         /// <returns>A task that represents the asynchronous recognition operation.</returns>
282         /// <exception cref="ArgumentNullException"><paramref name="source"/> is null.</exception>
283         /// <exception cref="ObjectDisposedException">
284         ///     <paramref name="source"/> has already been disposed of.\n
285         ///     - or -\n
286         ///     <paramref name="config"/> has already been disposed of.
287         /// </exception>
288         /// <exception cref="NotSupportedException">The feature is not supported.</exception>
289         public static async Task<FacialExpression> RecognizeFacialExpressionAsync(MediaVisionSource source,
290             Rectangle bound, FaceRecognitionConfiguration config)
291         {
292             if (source == null)
293             {
294                 throw new ArgumentNullException(nameof(source));
295             }
296
297             TaskCompletionSource<FacialExpression> tcsResult = new TaskCompletionSource<FacialExpression>();
298
299            InteropFace.MvFaceFacialExpressionRecognizedCallback cb = (IntPtr sourceHandle, IntPtr engineCfgHandle,
300                Interop.MediaVision.Rectangle faceLocation, FacialExpression facialExpression, IntPtr _) =>
301             {
302                 Log.Info(MediaVisionLog.Tag, $"Facial expression recognized, expression : {facialExpression}");
303                 if (!tcsResult.TrySetResult(facialExpression))
304                 {
305                     Log.Error(MediaVisionLog.Tag, "Failed to set facial result");
306                 }
307             };
308
309             using (var cbKeeper = ObjectKeeper.Get(cb))
310             {
311                 InteropFace.RecognizeFacialExpression(source.Handle, EngineConfiguration.GetHandle(config),
312                     bound.ToMarshalable(), cb).
313                     Validate("Failed to perform facial expression recognition.");
314
315                 return await tcsResult.Task;
316             }
317         }
318     }
319 }
320