Visual Servoing Platform version 3.7.0
Loading...
Searching...
No Matches
vpTemplateTracker.cpp
1/*
2 * ViSP, open source Visual Servoing Platform software.
3 * Copyright (C) 2005 - 2025 by Inria. All rights reserved.
4 *
5 * This software is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 * See the file LICENSE.txt at the root directory of this source
10 * distribution for additional information about the GNU GPL.
11 *
12 * For using ViSP with software that can not be combined with the GNU
13 * GPL, please contact Inria about acquiring a ViSP Professional
14 * Edition License.
15 *
16 * See https://visp.inria.fr for more information.
17 *
18 * This software was developed at:
19 * Inria Rennes - Bretagne Atlantique
20 * Campus Universitaire de Beaulieu
21 * 35042 Rennes Cedex
22 * France
23 *
24 * If you have questions regarding the use of this file, please contact
25 * Inria at visp@inria.fr
26 *
27 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
28 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
29 *
30 * Description:
31 * Template tracker.
32 */
33
34#include <visp3/tt/vpTemplateTracker.h>
35#include <visp3/tt/vpTemplateTrackerBSpline.h>
36
39 : nbLvlPyr(1), l0Pyr(0), pyrInitialised(false), evolRMS(0), x_pos(), y_pos(), evolRMS_eps(1e-4), ptTemplate(nullptr),
40 ptTemplatePyr(nullptr), ptTemplateInit(false), templateSize(0), templateSizePyr(nullptr), ptTemplateSelect(nullptr),
41 ptTemplateSelectPyr(nullptr), ptTemplateSelectInit(false), templateSelectSize(0), ptTemplateSupp(nullptr),
42 ptTemplateSuppPyr(nullptr), ptTemplateCompo(nullptr), ptTemplateCompoPyr(nullptr), zoneTracked(nullptr), zoneTrackedPyr(nullptr),
45 useBrent(false), nbIterBrent(3), taillef(7), fgG(nullptr), fgdG(nullptr), ratioPixelIn(0), mod_i(1), mod_j(1), nbParam(0),
47 useInverse(false), Warp(_warp), p(0), dp(), X1(), X2(), dW(), BI(), dIx(), dIy(), zoneRef_()
48{
49 nbParam = Warp->getNbParam();
50 p.resize(nbParam);
51 dp.resize(nbParam);
52
53 fgG = new double[(taillef + 1) / 2];
55
56 fgdG = new double[(taillef + 1) / 2];
58}
59
60void vpTemplateTracker::setGaussianFilterSize(unsigned int new_taill)
61{
62 taillef = new_taill;
63 if (fgG)
64 delete[] fgG;
65 fgG = new double[taillef];
67
68 if (fgdG)
69 delete[] fgdG;
70 fgdG = new double[taillef];
72}
73
75{
76 zoneTracked = &zone;
77
78 int largeur_im = static_cast<int>(I.getWidth());
79 int hauteur_im = static_cast<int>(I.getHeight());
80
81 unsigned int NbPointDsZone = 0;
82 int mod_fi, mod_fj;
83 mod_fi = mod_i;
84 mod_fj = mod_i;
85
86 for (int i = 0; i < hauteur_im; i += mod_fi) {
87 for (int j = 0; j < largeur_im; j += mod_fj) {
88 if (zone.inZone(i, j)) {
89 NbPointDsZone++;
90 }
91 }
92 }
93
94 templateSize = NbPointDsZone;
96 ptTemplateInit = true;
99
100 Hdesire.resize(nbParam, nbParam);
101 HLMdesire.resize(nbParam, nbParam);
102
104 vpImage<double> GaussI;
105 vpImageFilter::filter(I, GaussI, fgG, taillef);
108
109 unsigned int cpt_point = 0;
111 for (int i = 0; i < hauteur_im; i += mod_i) {
112 for (int j = 0; j < largeur_im; j += mod_j) {
113 if (zone.inZone(i, j)) {
114 pt.x = j;
115 pt.y = i;
116
117 pt.dx = dIx[i][j];
118 pt.dy = dIy[i][j];
119
120 if (pt.dx * pt.dx + pt.dy * pt.dy > thresholdGradient) {
121 ptTemplateSelect[cpt_point] = true;
123 }
124 else {
125 ptTemplateSelect[cpt_point] = false;
126 }
127 pt.val = vpTemplateTrackerBSpline::getSubPixBspline4(GaussI, i, j);
128
129 ptTemplate[cpt_point] = pt;
130 cpt_point++;
131 }
132 }
133 }
134
135 templateSize = cpt_point;
136 GaussI.destroy();
137}
138
140{
141 delete[] fgG;
142 delete[] fgdG;
143
144 resetTracker();
145}
146
152{
153 // reset the tracker parameters
154 p = 0;
155
156 if (pyrInitialised) {
157 if (ptTemplatePyr) {
158 for (unsigned int i = 0; i < nbLvlPyr; i++) {
159 if (ptTemplatePyr[i]) {
160 for (unsigned int point = 0; point < templateSizePyr[i]; point++) {
161 delete[] ptTemplatePyr[i][point].dW;
162 delete[] ptTemplatePyr[i][point].HiG;
163 }
164 delete[] ptTemplatePyr[i];
165 }
166 }
167 delete[] ptTemplatePyr;
168 ptTemplatePyr = nullptr;
169 }
170
171 if (ptTemplateCompoPyr) {
172 for (unsigned int i = 0; i < nbLvlPyr; i++) {
173 if (ptTemplateCompoPyr[i]) {
174 for (unsigned int point = 0; point < templateSizePyr[i]; point++) {
175 delete[] ptTemplateCompoPyr[i][point].dW;
176 }
177 delete[] ptTemplateCompoPyr[i];
178 }
179 }
180 delete[] ptTemplateCompoPyr;
181 ptTemplateCompoPyr = nullptr;
182 }
183
184 if (ptTemplateSuppPyr) {
185 for (unsigned int i = 0; i < nbLvlPyr; i++) {
186 if (ptTemplateSuppPyr[i]) {
187 for (unsigned int point = 0; point < templateSizePyr[i]; point++) {
188 delete[] ptTemplateSuppPyr[i][point].Bt;
189 delete[] ptTemplateSuppPyr[i][point].BtInit;
190 delete[] ptTemplateSuppPyr[i][point].dBt;
191 delete[] ptTemplateSuppPyr[i][point].d2W;
192 delete[] ptTemplateSuppPyr[i][point].d2Wx;
193 delete[] ptTemplateSuppPyr[i][point].d2Wy;
194 }
195 delete[] ptTemplateSuppPyr[i];
196 }
197 }
198 delete[] ptTemplateSuppPyr;
199 ptTemplateSuppPyr = nullptr;
200 }
201
203 for (unsigned int i = 0; i < nbLvlPyr; i++) {
204 if (ptTemplateSelectPyr[i])
205 delete[] ptTemplateSelectPyr[i];
206 }
207 delete[] ptTemplateSelectPyr;
208 ptTemplateSelectPyr = nullptr;
209 }
210
211 if (templateSizePyr) {
212 delete[] templateSizePyr;
213 templateSizePyr = nullptr;
214 }
215
216 if (HdesirePyr) {
217 delete[] HdesirePyr;
218 HdesirePyr = nullptr;
219 }
220
221 if (HLMdesirePyr) {
222 delete[] HLMdesirePyr;
223 HLMdesirePyr = nullptr;
224 }
225
227 delete[] HLMdesireInversePyr;
228 HLMdesireInversePyr = nullptr;
229 }
230
231 if (zoneTrackedPyr) {
232 delete[] zoneTrackedPyr;
233 zoneTrackedPyr = nullptr;
234 }
235
236 if (pyr_IDes) {
237 delete[] pyr_IDes;
238 pyr_IDes = nullptr;
239 }
240 }
241 else {
242 if (ptTemplateInit) {
243 for (unsigned int point = 0; point < templateSize; point++) {
244 delete[] ptTemplate[point].dW;
245 delete[] ptTemplate[point].HiG;
246 }
247 delete[] ptTemplate;
248 ptTemplate = nullptr;
249 ptTemplateInit = false;
250 }
251 if (ptTemplateCompo) {
252 for (unsigned int point = 0; point < templateSize; point++) {
253 delete[] ptTemplateCompo[point].dW;
254 }
255 delete[] ptTemplateCompo;
256 ptTemplateCompo = nullptr;
257 }
258 if (ptTemplateSupp) {
259 for (unsigned int point = 0; point < templateSize; point++) {
260 delete[] ptTemplateSupp[point].Bt;
261 delete[] ptTemplateSupp[point].BtInit;
262 delete[] ptTemplateSupp[point].dBt;
263 delete[] ptTemplateSupp[point].d2W;
264 delete[] ptTemplateSupp[point].d2Wx;
265 delete[] ptTemplateSupp[point].d2Wy;
266 }
267 delete[] ptTemplateSupp;
268 ptTemplateSupp = nullptr;
269 }
271 if (ptTemplateSelect) {
272 delete[] ptTemplateSelect;
273 ptTemplateSelect = nullptr;
274 }
275 }
276 }
277}
278
316void vpTemplateTracker::display(const vpImage<unsigned char> &I, const vpColor &col, unsigned int thickness)
317{
318 if (I.display) { // Only if a display is associated to the image
319 vpTemplateTrackerZone zoneWarped;
320 Warp->warpZone(*zoneTracked, p, zoneWarped);
321 zoneWarped.display(I, col, thickness);
322 }
323}
324
362void vpTemplateTracker::display(const vpImage<vpRGBa> &I, const vpColor &col, unsigned int thickness)
363{
364 if (I.display) { // Only if a display is associated to the image
365 vpTemplateTrackerZone zoneWarped;
366 Warp->warpZone(*zoneTracked, p, zoneWarped);
367 zoneWarped.display(I, col, thickness);
368 }
369}
370
372 vpColVector &direction, double &alpha)
373{
374 vpColVector **ptp;
375 ptp = new vpColVector *[4];
377 p0 = tp;
378
379 // valeur necessaire si conditionnel
380 vpColVector dpt(Warp->getNbParam());
381 vpColVector adpt(Warp->getNbParam());
382
384 if (useCompositionnal) {
385 if (useInverse)
386 Warp->getParamInverse(direction, dpt);
387 else
388 dpt = direction;
389 Warp->pRondp(tp, dpt, p1);
390 }
391 else {
392 p1 = tp + direction;
393 }
394
396 if (useCompositionnal) {
397 adpt = alpha * direction;
398 if (useInverse)
399 Warp->getParamInverse(adpt, dpt);
400 else
401 dpt = adpt;
402 Warp->pRondp(tp, dpt, p2);
403 }
404 else {
405 p2 = tp + alpha * direction;
406 }
408 ptp[0] = &p0;
409 ptp[1] = &p1;
410 ptp[2] = &p2;
411 ptp[3] = &p3;
412
413 double *Cost = new double[4];
414 Cost[0] = tMI;
415 Cost[1] = getCost(I, p1);
416 Cost[2] = getCost(I, p2);
417
418 double *talpha = new double[4];
419 talpha[0] = 0;
420 talpha[1] = 1.;
421 talpha[2] = alpha;
422
423 // Utilise trois estimees de paraboles successive ...
424 // A changer pour rendre adaptable
425 for (unsigned int opt = 0; opt < nbIterBrent; opt++) {
426 vpMatrix A(3, 3);
427 for (unsigned int i = 0; i < 3; i++) {
428 A[i][0] = talpha[i] * talpha[i];
429 A[i][1] = talpha[i];
430 A[i][2] = 1.;
431 }
432 vpColVector B(3);
433 for (unsigned int i = 0; i < 3; i++)
434 B[i] = Cost[i];
435 vpColVector parabol(3);
436 parabol = (A.t() * A).inverseByLU() * A.t() * B;
437
438 // If convexe
439 if (parabol[0] > 0) {
440 talpha[3] = -0.5 * parabol[1] / parabol[0];
441 }
442 else { // If concave
443 int tindic_x_min = 0;
444 int tindic_x_max = 0;
445 for (int i = 1; i < 3; i++) {
446 if (talpha[i] < talpha[tindic_x_min])
447 tindic_x_min = i;
448 if (talpha[i] > talpha[tindic_x_max])
449 tindic_x_max = i;
450 }
451
452 if (Cost[tindic_x_max] < Cost[tindic_x_min]) {
453 talpha[3] = talpha[tindic_x_max] + 1.;
454 }
455 else {
456 talpha[3] = talpha[tindic_x_min] - 1.;
457 }
458 }
459 int indic_x_min = 0;
460 int indic_x_max = 0;
461 for (int i = 1; i < 3; i++) {
462 if (talpha[i] < talpha[indic_x_min])
463 indic_x_min = i;
464 if (talpha[i] > talpha[indic_x_max])
465 indic_x_max = i;
466 }
467 if (talpha[3] > talpha[indic_x_max])
468 if ((talpha[3] - talpha[indic_x_max]) > alpha)
469 talpha[3] = talpha[indic_x_max] + 4.;
470 if (talpha[3] < talpha[indic_x_min])
471 if ((talpha[indic_x_min] - talpha[3]) > alpha)
472 talpha[3] = talpha[indic_x_min] - 4.;
473
474 if (useCompositionnal) {
475 adpt = talpha[3] * direction;
476 if (useInverse)
477 Warp->getParamInverse(adpt, dpt);
478 else
479 dpt = adpt;
480 Warp->pRondp(tp, dpt, p3);
481 }
482 else {
483 p3 = tp + talpha[3] * direction;
484 }
485
486 Cost[3] = getCost(I, p3);
487
488 int indice_f_max = 0;
489 for (int i = 1; i < 4; i++)
490 if (Cost[i] > Cost[indice_f_max])
491 indice_f_max = i;
492 if (indice_f_max != 3) {
493 *ptp[indice_f_max] = *ptp[3];
494 Cost[indice_f_max] = Cost[3];
495 talpha[indice_f_max] = talpha[3];
496 }
497 else
498 break;
499 }
500
501 int indice_f_min = 0;
502 for (int i = 0; i < 4; i++)
503 if (Cost[i] < Cost[indice_f_min])
504 indice_f_min = i;
505
506 alpha = talpha[indice_f_min];
507
508 if (alpha < 1)
509 alpha = 1.;
510
511 delete[] ptp;
512 delete[] Cost;
513 delete[] talpha;
514}
515
521void vpTemplateTracker::initPyramidal(unsigned int nbLvl, unsigned int l0)
522{
523 nbLvlPyr = nbLvl;
524 l0Pyr = l0;
525
529 ptTemplateSelectPyr = new bool *[nbLvlPyr];
530 ptTemplateSuppPyr = new vpTemplateTrackerPointSuppMIInv *[nbLvlPyr];
532 for (unsigned int i = 0; i < nbLvlPyr; i++) {
533 ptTemplatePyr[i] = nullptr;
534 ptTemplateSuppPyr[i] = nullptr;
535 ptTemplateSelectPyr[i] = nullptr;
536 ptTemplateCompoPyr[i] = nullptr;
537 }
538 templateSizePyr = new unsigned int[nbLvlPyr];
542
543 pyrInitialised = true;
544}
545
547{
548 zoneTrackedPyr[0].copy(zone);
549
550 pyr_IDes[0] = I;
555
556 // creation pyramide de zones et images desiree
557 if (nbLvlPyr > 1) {
558 for (unsigned int i = 1; i < nbLvlPyr; i++) {
559 zoneTrackedPyr[i] = zoneTrackedPyr[i - 1].getPyramidDown();
561
566 }
567 }
569}
570
593{
594 zoneRef_.initClick(I, delaunay);
595
596 if (nbLvlPyr > 1) {
600 }
601 else {
604 }
605}
606
618void vpTemplateTracker::initFromPoints(const vpImage<unsigned char> &I, const std::vector<vpImagePoint> &v_ip,
619 bool delaunay)
620{
621 zoneRef_.initFromPoints(I, v_ip, delaunay);
622
623 if (nbLvlPyr > 1) {
627 }
628 else {
631 }
632}
633
641{
642 zoneRef_ = zone;
643
644 if (nbLvlPyr > 1) {
648 }
649 else {
652 }
653}
654
656{
660 try {
662 ptTemplateSuppPyr[0] = ptTemplateSupp;
664 HdesirePyr[0] = Hdesire;
667 }
668 catch (const vpException &e) {
669 ptTemplateSuppPyr[0] = ptTemplateSupp;
671 HdesirePyr[0] = Hdesire;
674 throw(e);
675 }
676
677 if (nbLvlPyr > 1) {
679 Itemp = I;
680 for (unsigned int i = 1; i < nbLvlPyr; i++) {
682
686 try {
687 initHessienDesired(Itemp);
688 ptTemplateSuppPyr[i] = ptTemplateSupp;
690 HdesirePyr[i] = Hdesire;
693 }
694 catch (const vpException &e) {
695 ptTemplateSuppPyr[i] = ptTemplateSupp;
697 HdesirePyr[i] = Hdesire;
700 throw(e);
701 }
702 }
703 }
704}
705
711{
712 if (nbLvlPyr > 1)
713 trackPyr(I);
714 else
715 trackNoPyr(I);
716}
717
719{
721 pyr_I = new vpImage<unsigned char>[nbLvlPyr]; // Why +1 ?
722 pyr_I[0] = I;
723
724 try {
725 vpColVector ptemp(nbParam);
726 if (nbLvlPyr > 1) {
727 for (unsigned int i = 1; i < nbLvlPyr; i++) {
728 vpImageFilter::getGaussPyramidal(pyr_I[i - 1], pyr_I[i]);
729 Warp->getParamPyramidDown(p, ptemp);
730 p = ptemp;
732 }
733
734 for (int i = static_cast<int>(nbLvlPyr) - 1; i >= 0; i--) {
735 if (i >= static_cast<int>(l0Pyr)) {
739 ptTemplateSupp = ptTemplateSuppPyr[i];
741 H = HdesirePyr[i];
742 HLM = HLMdesirePyr[i];
744 trackRobust(pyr_I[i]);
745 }
746 if (i > 0) {
747 Warp->getParamPyramidUp(p, ptemp);
748 p = ptemp;
749 zoneTracked = &zoneTrackedPyr[i - 1];
750 }
751 }
752 }
753 else {
754 trackRobust(I);
755 }
756 delete[] pyr_I;
757 }
758 catch (const vpException &e) {
759 delete[] pyr_I;
761 }
762}
763
765{
767 vpColVector p_pre_estimation;
768 p_pre_estimation = p;
770 double pre_fcost = getCost(I, p);
771
772 trackNoPyr(I);
773
774 double post_fcost = getCost(I, p);
775 if (pre_fcost < post_fcost) {
776 p = p_pre_estimation;
777 }
778 }
779 else {
780 trackNoPyr(I);
781 }
782}
783
791{
792 unsigned int nb_corners = zoneTracked->getNbTriangle() * 3;
793
794 Warp->computeCoeff(param);
795 evolRMS = 0;
797
798 for (unsigned int i = 0; i < zoneTracked->getNbTriangle(); i++) {
799 zoneTracked->getTriangle(i, triangle);
800 for (unsigned int j = 0; j < 3; j++) {
801 triangle.getCorner(j, X1[0], X1[1]);
802
803 Warp->computeDenom(X1, param);
804 Warp->warpX(X1, X2, param);
805
806 unsigned int index = i * 3 + j;
807 double x_ = x_pos[index] - X2[0];
808 double y_ = y_pos[index] - X2[1];
809 evolRMS += x_ * x_ + y_ * y_;
810 x_pos[index] = X2[0];
811 y_pos[index] = X2[1];
812 }
813 }
814 evolRMS /= nb_corners;
815}
816
824{
825 unsigned int nb_corners = zoneTracked->getNbTriangle() * 3;
826 x_pos.resize(nb_corners);
827 y_pos.resize(nb_corners);
828
829 Warp->computeCoeff(param);
831
832 for (unsigned int i = 0; i < zoneTracked->getNbTriangle(); i++) {
833 unsigned int i3 = i * 3;
834 zoneTracked->getTriangle(i, triangle);
835 for (unsigned int j = 0; j < 3; j++) {
836 triangle.getCorner(j, X1[0], X1[1]);
837
838 Warp->computeDenom(X1, param);
839 Warp->warpX(X1, X2, param);
840 x_pos[i3 + j] = X2[0];
841 y_pos[i3 + j] = X2[1];
842 }
843 }
844}
845END_VISP_NAMESPACE
Implementation of column vector and the associated operations.
Class to define RGB colors available for display functionalities.
Definition vpColor.h:157
error that can be emitted by ViSP classes.
Definition vpException.h:60
@ badValue
Used to indicate that a value is not in the allowed range.
Definition vpException.h:73
static void getGradXGauss2D(const vpImage< ImageType > &I, vpImage< FilterType > &dIx, const FilterType *gaussianKernel, const FilterType *gaussianDerivativeKernel, unsigned int size, const vpImage< bool > *p_mask=nullptr)
static void getGaussianDerivativeKernel(FilterType *filter, unsigned int size, FilterType sigma=0., bool normalize=true)
static void filter(const vpImage< ImageType > &I, vpImage< FilterType > &If, const vpArray2D< FilterType > &M, bool convolve=false, const vpImage< bool > *p_mask=nullptr)
static void getGaussianKernel(FilterType *filter, unsigned int size, FilterType sigma=0., bool normalize=true)
static void getGradYGauss2D(const vpImage< ImageType > &I, vpImage< FilterType > &dIy, const FilterType *gaussianKernel, const FilterType *gaussianDerivativeKernel, unsigned int size, const vpImage< bool > *p_mask=nullptr)
static void getGaussPyramidal(const vpImage< unsigned char > &I, vpImage< unsigned char > &GI)
Definition of the vpImage class member functions.
Definition vpImage.h:131
void destroy()
Destructor : Memory de-allocation.
Definition vpImage.h:573
Implementation of a matrix and operations on matrices.
Definition vpMatrix.h:175
vpMatrix t() const
vpColVector getCorner(unsigned int i) const
bool inZone(const int &i, const int &j) const
void display(const vpImage< unsigned char > &I, const vpColor &col=vpColor::green, unsigned int thickness=3)
void display(const vpImage< unsigned char > &I, const vpColor &col=vpColor::green, unsigned int thickness=3)
vpImage< double > dIx
void initTracking(const vpImage< unsigned char > &I, vpTemplateTrackerZone &zone)
vpImage< double > dIy
vpTemplateTracker()
Default constructor.
unsigned int templateSelectSize
virtual void initHessienDesiredPyr(const vpImage< unsigned char > &I)
vpTemplateTrackerPoint ** ptTemplatePyr
void initFromPoints(const vpImage< unsigned char > &I, const std::vector< vpImagePoint > &v_ip, bool delaunay=false)
void computeEvalRMS(const vpColVector &p)
virtual void trackNoPyr(const vpImage< unsigned char > &I)=0
unsigned int * templateSizePyr
void initFromZone(const vpImage< unsigned char > &I, const vpTemplateTrackerZone &zone)
vpTemplateTrackerZone * zoneTrackedPyr
void computeOptimalBrentGain(const vpImage< unsigned char > &I, vpColVector &tp, double tMI, vpColVector &direction, double &alpha)
vpTemplateTrackerZone zoneRef_
vpImage< unsigned char > * pyr_IDes
std::vector< double > y_pos
vpMatrix * HLMdesireInversePyr
std::vector< double > x_pos
unsigned int iterationMax
virtual double getCost(const vpImage< unsigned char > &I, const vpColVector &tp)=0
void track(const vpImage< unsigned char > &I)
vpTemplateTrackerPointCompo ** ptTemplateCompoPyr
virtual void trackPyr(const vpImage< unsigned char > &I)
void getGaussianBluredImage(const vpImage< unsigned char > &I)
void initPosEvalRMS(const vpColVector &p)
virtual void initHessienDesired(const vpImage< unsigned char > &I)=0
vpTemplateTrackerPoint * ptTemplate
virtual void initTrackingPyr(const vpImage< unsigned char > &I, vpTemplateTrackerZone &zone)
virtual void initPyramidal(unsigned int nbLvl, unsigned int l0)
void setGaussianFilterSize(unsigned int new_taill)
vpTemplateTrackerWarp * Warp
unsigned int iterationGlobale
vpImage< double > BI
void trackRobust(const vpImage< unsigned char > &I)
void initClick(const vpImage< unsigned char > &I, bool delaunay=false)
unsigned int templateSize
vpTemplateTrackerPointCompo * ptTemplateCompo
vpTemplateTrackerZone * zoneTracked
Error that can be emitted by the vpTracker class and its derivatives.