J'essaie de définir un seuil de pixels rouges dans un flux vidéo à l'aide d'OpenCV. Les autres couleurs fonctionnent assez bien, mais le rouge pose un problème car il entoure l’axe des teintes (c’est-à-dire que HSV (0, 255, 255) et HSV (179, 255, 255) sont tous les deux rouges). La technique que j'utilise maintenant est loin d'être idéale. Fondamentalement:
cvInRangeS(src, cvScalar(0, 135, 135), cvScalar(20, 255, 255), dstA);
cvInRangeS(src, cvScalar(159, 135, 135), cvScalar(179, 255, 255), dstB);
cvOr(dstA, dstB, dst);
Ceci est sous-optimal car cela nécessite une branche dans le code pour le rouge (bogues potentiels), l'allocation de deux images supplémentaires et deux opérations supplémentaires par rapport au cas simple du bleu:
cvInRangeS(src, cvScalar(100, 135, 135), cvScalar(140, 255, 255), dst);
L’alternative la plus intéressante qui m’était venue à l’esprit était de "faire pivoter" les couleurs de l’image, de sorte que la teinte cible soit à 90 degrés. Par exemple.
int rotation = 90 - 179; // 179 = red
cvAddS(src, cvScalar(rotation, 0, 0), dst1);
cvInRangeS(dst1, cvScalar(70, 135, 135), cvScalar(110, 255, 255), dst);
Cela me permet de traiter toutes les couleurs de la même manière.
Toutefois, l'opération cvAddS
ne ramène pas les valeurs de teinte à 180 lorsqu'elles descendent sous 0, vous perdez donc des données. J'ai cherché à convertir l'image en CvMat
pour pouvoir la soustraire, puis utiliser le module pour replacer les valeurs négatives au maximum, mais CvMat
ne semble pas supporter le module. Bien sûr, je pourrais parcourir chaque pixel, mais je crains que cela ne soit très lent.
J'ai lu de nombreux tutoriels et exemples de code, mais ils semblent tous ne porter que sur des gammes qui ne couvrent pas le spectre de teinte, ou utilisent des solutions encore plus laides (par exemple, ré-implémenter cvInRangeS
par itération sur chaque pixel et faire des comparaisons manuelles avec une table de couleurs).
Alors, quel est le moyen habituel de résoudre ce problème? Quel est le meilleur moyen? Quels sont les compromis de chacun? Est-ce que l'itération sur les pixels est beaucoup plus lente que l'utilisation des fonctions CV intégrées?
Vous n'allez pas croire, mais j'avais exactement le même problème et je l'ai résolu en effectuant une simple itération à l'aide de l'image Hue (pas tout HSV).
Est-ce que l'itération sur les pixels est beaucoup plus lente que l'utilisation des fonctions CV intégrées?
Je viens d'essayer de comprendre cv :: inRange function mais je ne l'ai pas du tout (il semble que l'auteur ait utilisé une itération spécifique).
C'est un peu tard, mais c'est ce que j'essaierais.
Effectuez la conversion: cvCvtColor (imageBgr, imageHsv, CV_RGB2HSV);
Notez que RVB vs Bgr sont délibérément croisés.
De cette façon, la couleur rouge sera traitée dans un canal bleu et sera centrée autour de 170. Il y aurait également un renversement dans la direction, mais c'est OK tant que vous savez vous y attendre.
Vous pouvez calculer le canal de teinte dans la plage 0..255 avec CV_BGR2HSV_FULL
. Votre différence de teinte originale de 10
deviendra 14
(10/180*256
), c’est-à-dire que la teinte doit être comprise dans la plage 128-14..128+14
:
public void inColorRange(CvMat imageBgr, CvMat dst, int color, int threshold) {
cvCvtColor(imageBgr, imageHsv, CV_BGR2HSV_FULL);
int rotation = 128 - color;
cvAddS(imageHsv, cvScalar(rotation, 0, 0), imageHsv);
cvInRangeS(imageHsv, cvScalar(128-threshold, 135, 135),
cvScalar(128+threshold, 255, 255), dst);
}
cvAddS(...)
est équivalent, au niveau de l'élément, à:
out = static_cast<dest> ( in + shift );
Cette static_cast est le problème, car clips/tronque les valeurs.
Une solution serait de déplacer les données de (0-180) à (x, 255), puis d’appliquer un ajout sans écrêtage avec débordement:
out = uchar(in + (255-180) + rotation );
Vous devriez maintenant pouvoir utiliser un seul appel InRange. Il vous suffit de décaler votre intervalle rouge selon la formule ci-dessus.
Il existe un moyen très simple de le faire.
D'abord faire deux gammes de couleurs différentes
cv::Mat lower_red_hue_range;
cv::Mat upper_red_hue_range;
cv::inRange(hsv_image, cv::Scalar(0, 100, 100), cv::Scalar(10, 255, 255), lower_red_hue_range);
cv::inRange(hsv_image, cv::Scalar(160, 100, 100), cv::Scalar(179, 255, 255), upper_red_hue_range);
Puis combinez les deux masques en utilisant addWeighted
cv::Mat red_hue_mask;
cv::addWeighted(lower_red_hue_range, 1.0, upper_red_hue_range, 1.0, 0.0, red_hue_mask);
Maintenant, vous pouvez simplement appliquer le masque à l'image
cv::Mat result;
inputImageMat.copyTo(result, red_hue_mask);
J'ai eu l'idée de un article de blog j'ai trouvé