1 // This file is part of OpenCV project.
2 // It is subject to the license terms in the LICENSE file found in the top-level directory
3 // of this distribution and at http://opencv.org/license.html.
4 //
5 // Copyright (C) 2020 Intel Corporation
6
7 #ifndef OPENCV_GAPI_VIDEO_TESTS_COMMON_HPP
8 #define OPENCV_GAPI_VIDEO_TESTS_COMMON_HPP
9
10 #include "gapi_tests_common.hpp"
11 #include "../../include/opencv2/gapi/video.hpp"
12
13 #ifdef HAVE_OPENCV_VIDEO
14 #include <opencv2/video.hpp>
15 #endif // HAVE_OPENCV_VIDEO
16
17
18 namespace opencv_test
19 {
20 namespace
21 {
22 G_TYPED_KERNEL(GMinScalar, <GScalar(GScalar,GScalar)>, "custom.MinScalar") {
outMeta(GScalarDesc,GScalarDesc)23 static GScalarDesc outMeta(GScalarDesc,GScalarDesc) { return empty_scalar_desc(); }
24 };
GAPI_OCV_KERNEL(GCPUMinScalar,GMinScalar)25 GAPI_OCV_KERNEL(GCPUMinScalar, GMinScalar) {
26 static void run(const Scalar &sc1, const Scalar &sc2, Scalar &scOut) {
27 scOut = Scalar(std::min(sc1[0], sc2[0]));
28 }
29 };
30
initTrackingPointsArray(std::vector<cv::Point2f> & points,int width,int height,int nPointsX,int nPointsY)31 inline void initTrackingPointsArray(std::vector<cv::Point2f>& points, int width, int height,
32 int nPointsX, int nPointsY)
33 {
34 if (nPointsX > width || nPointsY > height)
35 {
36 FAIL() << "Specified points number is too big";
37 }
38
39 int stepX = width / nPointsX;
40 int stepY = height / nPointsY;
41
42
43 points.clear();
44 GAPI_Assert((nPointsX >= 0) && (nPointsY) >= 0);
45 points.reserve(nPointsX * nPointsY);
46
47 for (int x = stepX / 2; x < width; x += stepX)
48 {
49 for (int y = stepY / 2; y < height; y += stepY)
50 {
51 Point2f pt(static_cast<float>(x), static_cast<float>(y));
52 points.push_back(pt);
53 }
54 }
55 }
56
57 struct BuildOpticalFlowPyramidTestOutput
58 {
BuildOpticalFlowPyramidTestOutputopencv_test::__anon0e6333e90111::BuildOpticalFlowPyramidTestOutput59 BuildOpticalFlowPyramidTestOutput(std::vector<Mat> &pyr, int maxLvl) :
60 pyramid(pyr), maxLevel(maxLvl) { }
61 std::vector<Mat> &pyramid;
62 int maxLevel = 0;
63 };
64
65 template<typename Type>
66 struct OptFlowLKTestInput
67 {
68 Type& prevData;
69 Type& nextData;
70 std::vector<cv::Point2f>& prevPoints;
71 };
72
73 struct OptFlowLKTestOutput
74 {
75 std::vector<cv::Point2f> &nextPoints;
76 std::vector<uchar> &statuses;
77 std::vector<float> &errors;
78 };
79
80 struct BuildOpticalFlowPyramidTestParams
81 {
82 BuildOpticalFlowPyramidTestParams() = default;
83
BuildOpticalFlowPyramidTestParamsopencv_test::__anon0e6333e90111::BuildOpticalFlowPyramidTestParams84 BuildOpticalFlowPyramidTestParams(const std::string& name, int winSz, int maxLvl,
85 bool withDeriv, int pBorder, int dBorder,
86 bool tryReuse, const GCompileArgs& compArgs):
87
88 fileName(name), winSize(winSz), maxLevel(maxLvl),
89 withDerivatives(withDeriv), pyrBorder(pBorder),
90 derivBorder(dBorder), tryReuseInputImage(tryReuse),
91 compileArgs(compArgs) { }
92
93 std::string fileName = "";
94 int winSize = -1;
95 int maxLevel = -1;
96 bool withDerivatives = false;
97 int pyrBorder = -1;
98 int derivBorder = -1;
99 bool tryReuseInputImage = false;
100 cv::GCompileArgs compileArgs;
101 };
102
103 struct OptFlowLKTestParams
104 {
OptFlowLKTestParamsopencv_test::__anon0e6333e90111::OptFlowLKTestParams105 OptFlowLKTestParams(): fileNamePattern(""), format(1), channels(0), pointsNum{0, 0},
106 winSize(0), maxLevel(3), minEigThreshold(1e-4), flags(0) { }
107
OptFlowLKTestParamsopencv_test::__anon0e6333e90111::OptFlowLKTestParams108 OptFlowLKTestParams(const std::string& namePat, int chans,
109 const std::tuple<int,int>& ptsNum, int winSz,
110 const cv::TermCriteria& crit, const cv::GCompileArgs& compArgs,
111 int flgs = 0, int fmt = 1, int maxLvl = 3, double minEigThresh = 1e-4):
112
113 fileNamePattern(namePat), format(fmt), channels(chans),
114 pointsNum(ptsNum), winSize(winSz), maxLevel(maxLvl),
115 criteria(crit), minEigThreshold(minEigThresh), compileArgs(compArgs),
116 flags(flgs) { }
117
118 std::string fileNamePattern = "";
119 int format = 1;
120 int channels = 0;
121 std::tuple<int,int> pointsNum = std::make_tuple(0, 0);
122 int winSize = 0;
123 int maxLevel = 3;
124 cv::TermCriteria criteria;
125 double minEigThreshold = 1e-4;
126 cv::GCompileArgs compileArgs;
127 int flags = 0;
128 };
129
compareOutputPyramids(const BuildOpticalFlowPyramidTestOutput & outGAPI,const BuildOpticalFlowPyramidTestOutput & outOCV)130 inline void compareOutputPyramids(const BuildOpticalFlowPyramidTestOutput& outGAPI,
131 const BuildOpticalFlowPyramidTestOutput& outOCV)
132 {
133 GAPI_Assert(outGAPI.maxLevel == outOCV.maxLevel);
134 GAPI_Assert(outOCV.maxLevel >= 0);
135 const size_t maxLevel = static_cast<size_t>(outOCV.maxLevel);
136 for (size_t i = 0; i <= maxLevel; i++)
137 {
138 EXPECT_TRUE(AbsExact().to_compare_f()(outGAPI.pyramid[i], outOCV.pyramid[i]));
139 }
140 }
141
142 template <typename Elem>
compareVectorsAbsExactForOptFlow(const std::vector<Elem> & outGAPI,const std::vector<Elem> & outOCV)143 inline bool compareVectorsAbsExactForOptFlow(const std::vector<Elem>& outGAPI,
144 const std::vector<Elem>& outOCV)
145 {
146 return AbsExactVector<Elem>().to_compare_f()(outGAPI, outOCV);
147 }
148
compareOutputsOptFlow(const OptFlowLKTestOutput & outGAPI,const OptFlowLKTestOutput & outOCV)149 inline void compareOutputsOptFlow(const OptFlowLKTestOutput& outGAPI,
150 const OptFlowLKTestOutput& outOCV)
151 {
152 EXPECT_TRUE(compareVectorsAbsExactForOptFlow(outGAPI.nextPoints, outOCV.nextPoints));
153 EXPECT_TRUE(compareVectorsAbsExactForOptFlow(outGAPI.statuses, outOCV.statuses));
154 EXPECT_TRUE(compareVectorsAbsExactForOptFlow(outGAPI.errors, outOCV.errors));
155 }
156
operator <<(std::ostream & os,const cv::TermCriteria & criteria)157 inline std::ostream& operator<<(std::ostream& os, const cv::TermCriteria& criteria)
158 {
159 os << "{";
160 switch (criteria.type) {
161 case cv::TermCriteria::COUNT:
162 os << "COUNT; ";
163 break;
164 case cv::TermCriteria::EPS:
165 os << "EPS; ";
166 break;
167 case cv::TermCriteria::COUNT | cv::TermCriteria::EPS:
168 os << "COUNT | EPS; ";
169 break;
170 default:
171 os << "TypeUndefined; ";
172 break;
173 };
174
175 return os << criteria.maxCount << "; " << criteria.epsilon <<"}";
176 }
177
178 #ifdef HAVE_OPENCV_VIDEO
179
runOCVnGAPIBuildOptFlowPyramid(TestFunctional & testInst,const BuildOpticalFlowPyramidTestParams & params,BuildOpticalFlowPyramidTestOutput & outOCV,BuildOpticalFlowPyramidTestOutput & outGAPI)180 inline GComputation runOCVnGAPIBuildOptFlowPyramid(TestFunctional& testInst,
181 const BuildOpticalFlowPyramidTestParams& params,
182 BuildOpticalFlowPyramidTestOutput& outOCV,
183 BuildOpticalFlowPyramidTestOutput& outGAPI)
184 {
185 testInst.initMatFromImage(CV_8UC1, params.fileName);
186
187 // OpenCV code /////////////////////////////////////////////////////////////
188 {
189 outOCV.maxLevel = cv::buildOpticalFlowPyramid(testInst.in_mat1, outOCV.pyramid,
190 Size(params.winSize, params.winSize),
191 params.maxLevel, params.withDerivatives,
192 params.pyrBorder, params.derivBorder,
193 params.tryReuseInputImage);
194 }
195
196 // G-API code //////////////////////////////////////////////////////////////
197 GMat in;
198 GArray<GMat> out;
199 GScalar outMaxLevel;
200 std::tie(out, outMaxLevel) =
201 cv::gapi::buildOpticalFlowPyramid(in, Size(params.winSize, params.winSize),
202 params.maxLevel, params.withDerivatives,
203 params.pyrBorder, params.derivBorder,
204 params.tryReuseInputImage);
205
206 GComputation c(GIn(in), GOut(out, outMaxLevel));
207
208 Scalar outMaxLevelSc;
209 c.apply(gin(testInst.in_mat1), gout(outGAPI.pyramid, outMaxLevelSc),
210 std::move(const_cast<GCompileArgs&>(params.compileArgs)));
211 outGAPI.maxLevel = static_cast<int>(outMaxLevelSc[0]);
212
213 return c;
214 }
215
216 template<typename GType, typename Type>
runOCVnGAPIOptFlowLK(OptFlowLKTestInput<Type> & in,int width,int height,const OptFlowLKTestParams & params,OptFlowLKTestOutput & ocvOut,OptFlowLKTestOutput & gapiOut)217 cv::GComputation runOCVnGAPIOptFlowLK(OptFlowLKTestInput<Type>& in,
218 int width, int height,
219 const OptFlowLKTestParams& params,
220 OptFlowLKTestOutput& ocvOut,
221 OptFlowLKTestOutput& gapiOut)
222 {
223
224 int nPointsX = 0, nPointsY = 0;
225 std::tie(nPointsX, nPointsY) = params.pointsNum;
226
227 initTrackingPointsArray(in.prevPoints, width, height, nPointsX, nPointsY);
228
229 cv::Size winSize(params.winSize, params.winSize);
230
231 // OpenCV code /////////////////////////////////////////////////////////////
232 {
233 cv::calcOpticalFlowPyrLK(in.prevData, in.nextData, in.prevPoints,
234 ocvOut.nextPoints, ocvOut.statuses, ocvOut.errors,
235 winSize, params.maxLevel, params.criteria,
236 params.flags, params.minEigThreshold);
237 }
238
239 // G-API code //////////////////////////////////////////////////////////////
240 {
241 GType inPrev, inNext;
242 GArray<cv::Point2f> prevPts, predPts, nextPts;
243 GArray<uchar> statuses;
244 GArray<float> errors;
245 std::tie(nextPts, statuses, errors) = cv::gapi::calcOpticalFlowPyrLK(
246 inPrev, inNext,
247 prevPts, predPts, winSize,
248 params.maxLevel, params.criteria,
249 params.flags, params.minEigThreshold);
250
251 cv::GComputation c(cv::GIn(inPrev, inNext, prevPts, predPts),
252 cv::GOut(nextPts, statuses, errors));
253
254 c.apply(cv::gin(in.prevData, in.nextData, in.prevPoints, std::vector<cv::Point2f>{ }),
255 cv::gout(gapiOut.nextPoints, gapiOut.statuses, gapiOut.errors),
256 std::move(const_cast<cv::GCompileArgs&>(params.compileArgs)));
257
258 return c;
259 }
260 }
261
runOCVnGAPIOptFlowLK(TestFunctional & testInst,std::vector<cv::Point2f> & inPts,const OptFlowLKTestParams & params,OptFlowLKTestOutput & ocvOut,OptFlowLKTestOutput & gapiOut)262 inline cv::GComputation runOCVnGAPIOptFlowLK(TestFunctional& testInst,
263 std::vector<cv::Point2f>& inPts,
264 const OptFlowLKTestParams& params,
265 OptFlowLKTestOutput& ocvOut,
266 OptFlowLKTestOutput& gapiOut)
267 {
268 testInst.initMatsFromImages(params.channels,
269 params.fileNamePattern,
270 params.format);
271
272 OptFlowLKTestInput<cv::Mat> in{ testInst.in_mat1, testInst.in_mat2, inPts };
273
274 return runOCVnGAPIOptFlowLK<cv::GMat>(in,
275 testInst.in_mat1.cols,
276 testInst.in_mat1.rows,
277 params,
278 ocvOut,
279 gapiOut);
280 }
281
runOCVnGAPIOptFlowLKForPyr(TestFunctional & testInst,OptFlowLKTestInput<std::vector<cv::Mat>> & in,const OptFlowLKTestParams & params,bool withDeriv,OptFlowLKTestOutput & ocvOut,OptFlowLKTestOutput & gapiOut)282 inline cv::GComputation runOCVnGAPIOptFlowLKForPyr(TestFunctional& testInst,
283 OptFlowLKTestInput<std::vector<cv::Mat>>& in,
284 const OptFlowLKTestParams& params,
285 bool withDeriv,
286 OptFlowLKTestOutput& ocvOut,
287 OptFlowLKTestOutput& gapiOut)
288 {
289 testInst.initMatsFromImages(params.channels,
290 params.fileNamePattern,
291 params.format);
292
293 cv::Size winSize(params.winSize, params.winSize);
294
295 OptFlowLKTestParams updatedParams(params);
296 updatedParams.maxLevel = cv::buildOpticalFlowPyramid(testInst.in_mat1, in.prevData,
297 winSize, params.maxLevel, withDeriv);
298 updatedParams.maxLevel = cv::buildOpticalFlowPyramid(testInst.in_mat2, in.nextData,
299 winSize, params.maxLevel, withDeriv);
300
301
302 return runOCVnGAPIOptFlowLK<cv::GArray<cv::GMat>>(in,
303 testInst.in_mat1.cols,
304 testInst.in_mat1.rows,
305 updatedParams,
306 ocvOut,
307 gapiOut);
308 }
309
runOCVnGAPIOptFlowPipeline(TestFunctional & testInst,const BuildOpticalFlowPyramidTestParams & params,OptFlowLKTestOutput & outOCV,OptFlowLKTestOutput & outGAPI,std::vector<Point2f> & prevPoints)310 inline GComputation runOCVnGAPIOptFlowPipeline(TestFunctional& testInst,
311 const BuildOpticalFlowPyramidTestParams& params,
312 OptFlowLKTestOutput& outOCV,
313 OptFlowLKTestOutput& outGAPI,
314 std::vector<Point2f>& prevPoints)
315 {
316 testInst.initMatsFromImages(3, params.fileName, 1);
317
318 initTrackingPointsArray(prevPoints, testInst.in_mat1.cols, testInst.in_mat1.rows, 15, 15);
319
320 Size winSize = Size(params.winSize, params.winSize);
321
322 // OpenCV code /////////////////////////////////////////////////////////////
323 {
324 std::vector<Mat> pyr1, pyr2;
325 int maxLevel1 = cv::buildOpticalFlowPyramid(testInst.in_mat1, pyr1, winSize,
326 params.maxLevel, params.withDerivatives,
327 params.pyrBorder, params.derivBorder,
328 params.tryReuseInputImage);
329 int maxLevel2 = cv::buildOpticalFlowPyramid(testInst.in_mat2, pyr2, winSize,
330 params.maxLevel, params.withDerivatives,
331 params.pyrBorder, params.derivBorder,
332 params.tryReuseInputImage);
333 cv::calcOpticalFlowPyrLK(pyr1, pyr2, prevPoints,
334 outOCV.nextPoints, outOCV.statuses, outOCV.errors,
335 winSize, std::min(maxLevel1, maxLevel2));
336 }
337
338 // G-API code //////////////////////////////////////////////////////////////
339 GMat in1, in2;
340 GArray<GMat> gpyr1, gpyr2;
341 GScalar gmaxLevel1, gmaxLevel2;
342 GArray<cv::Point2f> gprevPts, gpredPts, gnextPts;
343 GArray<uchar> gstatuses;
344 GArray<float> gerrors;
345
346 std::tie(gpyr1, gmaxLevel1) = cv::gapi::buildOpticalFlowPyramid(
347 in1, winSize, params.maxLevel,
348 params.withDerivatives, params.pyrBorder,
349 params.derivBorder, params.tryReuseInputImage);
350
351 std::tie(gpyr2, gmaxLevel2) = cv::gapi::buildOpticalFlowPyramid(
352 in2, winSize, params.maxLevel,
353 params.withDerivatives, params.pyrBorder,
354 params.derivBorder, params.tryReuseInputImage);
355
356 GScalar gmaxLevel = GMinScalar::on(gmaxLevel1, gmaxLevel2);
357
358 std::tie(gnextPts, gstatuses, gerrors) = cv::gapi::calcOpticalFlowPyrLK(
359 gpyr1, gpyr2, gprevPts, gpredPts, winSize,
360 gmaxLevel);
361
362 cv::GComputation c(GIn(in1, in2, gprevPts, gpredPts), cv::GOut(gnextPts, gstatuses, gerrors));
363
364 c.apply(cv::gin(testInst.in_mat1, testInst.in_mat2, prevPoints, std::vector<cv::Point2f>{ }),
365 cv::gout(outGAPI.nextPoints, outGAPI.statuses, outGAPI.errors),
366 std::move(const_cast<cv::GCompileArgs&>(params.compileArgs)));
367
368 return c;
369 }
370
testBackgroundSubtractorStreaming(cv::GStreamingCompiled & gapiBackSub,const cv::Ptr<cv::BackgroundSubtractor> & pOCVBackSub,const int diffPercent,const int tolerance,const double lRate,const std::size_t testNumFrames)371 inline void testBackgroundSubtractorStreaming(cv::GStreamingCompiled& gapiBackSub,
372 const cv::Ptr<cv::BackgroundSubtractor>& pOCVBackSub,
373 const int diffPercent, const int tolerance,
374 const double lRate, const std::size_t testNumFrames)
375 {
376 cv::Mat frame, gapiForeground, ocvForeground;
377 double numDiff = diffPercent / 100.0;
378
379 gapiBackSub.start();
380 EXPECT_TRUE(gapiBackSub.running());
381
382 compare_f cmpF = AbsSimilarPoints(tolerance, numDiff).to_compare_f();
383
384 // Comparison of G-API and OpenCV substractors
385 std::size_t frames = 0u;
386 while (frames <= testNumFrames && gapiBackSub.pull(cv::gout(frame, gapiForeground)))
387 {
388 pOCVBackSub->apply(frame, ocvForeground, lRate);
389 EXPECT_TRUE(cmpF(gapiForeground, ocvForeground));
390 frames++;
391 }
392
393 if (gapiBackSub.running())
394 gapiBackSub.stop();
395
396 EXPECT_LT(0u, frames);
397 EXPECT_FALSE(gapiBackSub.running());
398 }
399
initKalmanParams(const int type,const int dDim,const int mDim,const int cDim,cv::gapi::KalmanParams & kp)400 inline void initKalmanParams(const int type, const int dDim, const int mDim, const int cDim,
401 cv::gapi::KalmanParams& kp)
402 {
403 kp.state = Mat::zeros(dDim, 1, type);
404 cv::randu(kp.state, Scalar::all(0), Scalar::all(0.1));
405 kp.errorCov = Mat::eye(dDim, dDim, type);
406
407 kp.transitionMatrix = Mat::ones(dDim, dDim, type) * 2;
408 kp.processNoiseCov = Mat::eye(dDim, dDim, type) * (1e-5);
409 kp.measurementMatrix = Mat::eye(mDim, dDim, type) * 2;
410 kp.measurementNoiseCov = Mat::eye(mDim, mDim, type) * (1e-5);
411
412 if (cDim > 0)
413 kp.controlMatrix = Mat::eye(dDim, cDim, type) * (1e-3);
414 }
415
initKalmanFilter(const cv::gapi::KalmanParams & kp,const bool control,cv::KalmanFilter & ocvKalman)416 inline void initKalmanFilter(const cv::gapi::KalmanParams& kp, const bool control,
417 cv::KalmanFilter& ocvKalman)
418 {
419 kp.state.copyTo(ocvKalman.statePost);
420 kp.errorCov.copyTo(ocvKalman.errorCovPost);
421
422 kp.transitionMatrix.copyTo(ocvKalman.transitionMatrix);
423 kp.measurementMatrix.copyTo(ocvKalman.measurementMatrix);
424 kp.measurementNoiseCov.copyTo(ocvKalman.measurementNoiseCov);
425 kp.processNoiseCov.copyTo(ocvKalman.processNoiseCov);
426
427 if (control)
428 kp.controlMatrix.copyTo(ocvKalman.controlMatrix);
429 }
430
431 #else // !HAVE_OPENCV_VIDEO
432
runOCVnGAPIBuildOptFlowPyramid(TestFunctional &,const BuildOpticalFlowPyramidTestParams &,BuildOpticalFlowPyramidTestOutput &,BuildOpticalFlowPyramidTestOutput &)433 inline cv::GComputation runOCVnGAPIBuildOptFlowPyramid(TestFunctional&,
434 const BuildOpticalFlowPyramidTestParams&,
435 BuildOpticalFlowPyramidTestOutput&,
436 BuildOpticalFlowPyramidTestOutput&)
437 {
438 GAPI_Assert(0 && "This function shouldn't be called without opencv_video");
439 }
440
runOCVnGAPIOptFlowLK(TestFunctional &,std::vector<cv::Point2f> &,const OptFlowLKTestParams &,OptFlowLKTestOutput &,OptFlowLKTestOutput &)441 inline cv::GComputation runOCVnGAPIOptFlowLK(TestFunctional&,
442 std::vector<cv::Point2f>&,
443 const OptFlowLKTestParams&,
444 OptFlowLKTestOutput&,
445 OptFlowLKTestOutput&)
446 {
447 GAPI_Assert(0 && "This function shouldn't be called without opencv_video");
448 }
449
runOCVnGAPIOptFlowLKForPyr(TestFunctional &,OptFlowLKTestInput<std::vector<cv::Mat>> &,const OptFlowLKTestParams &,bool,OptFlowLKTestOutput &,OptFlowLKTestOutput &)450 inline cv::GComputation runOCVnGAPIOptFlowLKForPyr(TestFunctional&,
451 OptFlowLKTestInput<std::vector<cv::Mat>>&,
452 const OptFlowLKTestParams&,
453 bool,
454 OptFlowLKTestOutput&,
455 OptFlowLKTestOutput&)
456 {
457 GAPI_Assert(0 && "This function shouldn't be called without opencv_video");
458 }
459
runOCVnGAPIOptFlowPipeline(TestFunctional &,const BuildOpticalFlowPyramidTestParams &,OptFlowLKTestOutput &,OptFlowLKTestOutput &,std::vector<Point2f> &)460 inline GComputation runOCVnGAPIOptFlowPipeline(TestFunctional&,
461 const BuildOpticalFlowPyramidTestParams&,
462 OptFlowLKTestOutput&,
463 OptFlowLKTestOutput&,
464 std::vector<Point2f>&)
465 {
466 GAPI_Assert(0 && "This function shouldn't be called without opencv_video");
467 }
468
469 #endif // HAVE_OPENCV_VIDEO
470
471 } // namespace
472 } // namespace opencv_test
473
474 // Note: namespace must match the namespace of the type of the printed object
475 namespace cv { namespace gapi { namespace video
476 {
operator <<(std::ostream & os,const BackgroundSubtractorType op)477 inline std::ostream& operator<<(std::ostream& os, const BackgroundSubtractorType op)
478 {
479 #define CASE(v) case BackgroundSubtractorType::v: os << #v; break
480 switch (op)
481 {
482 CASE(TYPE_BS_MOG2);
483 CASE(TYPE_BS_KNN);
484 default: GAPI_Assert(false && "unknown BackgroundSubtractor type");
485 }
486 #undef CASE
487 return os;
488 }
489 }}} // namespace cv::gapi::video
490
491 #endif // OPENCV_GAPI_VIDEO_TESTS_COMMON_HPP
492