web-dev-qa-db-fra.com

seuil efficacement rouge en utilisant HSV dans OpenCV

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?

21
Ian

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).

2
ArtemStorozhuk

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.

6
GaryK

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);
}
2
Oliv

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.

0
Sam

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é

0
Ethan