web-dev-qa-db-fra.com

Détection OpenCV Edge / Border basée sur la couleur

Je suis assez nouveau sur OpenCV et très heureux d'en savoir plus. J'ai joué avec l'idée de décrire les bords, les formes.

Je suis tombé sur ce code (fonctionnant sur un appareil iOS), qui utilise Canny. J'aimerais pouvoir le rendre en couleur et encercler chaque forme. Quelqu'un peut me diriger dans la bonne direction?

Merci!

IplImage *grayImage = cvCreateImage(cvGetSize(iplImage), IPL_DEPTH_8U, 1);
cvCvtColor(iplImage, grayImage, CV_BGRA2GRAY);
cvReleaseImage(&iplImage);

IplImage* img_blur = cvCreateImage( cvGetSize( grayImage ), grayImage->depth, 1);
cvSmooth(grayImage, img_blur, CV_BLUR, 3, 0, 0, 0);
cvReleaseImage(&grayImage);

IplImage* img_canny = cvCreateImage( cvGetSize( img_blur ), img_blur->depth, 1);
cvCanny( img_blur, img_canny, 10, 100, 3 );
cvReleaseImage(&img_blur);

cvNot(img_canny, img_canny);

Et l'exemple pourrait être ces galettes de hamburger. OpenCV détecterait la galette et la définirait.enter image description here

Image originale:

enter image description here

20
Jeff

Les informations sur les couleurs sont souvent traitées par conversion en espace colorimétrique HSV qui gère directement la "couleur" au lieu de diviser la couleur en composants R/G/B, ce qui facilite la gestion des mêmes couleurs avec une luminosité différente, etc.

si vous convertissez votre image en HSV, vous obtiendrez ceci:

cv::Mat hsv;
cv::cvtColor(input,hsv,CV_BGR2HSV);

std::vector<cv::Mat> channels;
cv::split(hsv, channels);

cv::Mat H = channels[0];
cv::Mat S = channels[1];
cv::Mat V = channels[2];

Canal de teinte:

enter image description here

Canal de saturation:

enter image description here

Canal de valeur:

enter image description here

généralement, le canal de teinte est le premier à regarder si vous êtes intéressé à segmenter la "couleur" (par exemple tous les objets rouges). Un problème est que la teinte est une valeur circulaire/angulaire, ce qui signifie que les valeurs les plus élevées sont très similaires aux valeurs les plus faibles, ce qui entraîne des artefacts lumineux à la frontière des galettes. Pour surmonter cela pour une valeur particulière, vous pouvez déplacer tout l'espace de teinte. Si décalé de 50 °, vous obtiendrez quelque chose comme ceci à la place:

cv::Mat shiftedH = H.clone();
int shift = 25; // in openCV hue values go from 0 to 180 (so have to be doubled to get to 0 .. 360) because of byte range from 0 to 255
for(int j=0; j<shiftedH.rows; ++j)
    for(int i=0; i<shiftedH.cols; ++i)
    {
        shiftedH.at<unsigned char>(j,i) = (shiftedH.at<unsigned char>(j,i) + shift)%180;
    }

enter image description here

vous pouvez maintenant utiliser une simple détection des bords pour trouver des bords dans le canal de teinte:

cv::Mat cannyH;
cv::Canny(shiftedH, cannyH, 100, 50);

enter image description here

Vous pouvez voir que les régions sont un peu plus grandes que les vraies galettes, c'est peut-être à cause des minuscules reflets sur le sol autour des galettes, mais je n'en suis pas sûr. C'est peut-être juste à cause des artefacts de compression jpeg;)

Si vous utilisez plutôt le canal de saturation pour extraire les bords, vous vous retrouverez avec quelque chose comme ceci:

cv::Mat cannyS;
cv::Canny(S, cannyS, 200, 100);

enter image description here

où les contours ne sont pas complètement fermés. Vous pouvez peut-être combiner la teinte et la saturation dans le prétraitement pour extraire les bords du canal de teinte, mais uniquement là où la saturation est suffisamment élevée.

À ce stade, vous avez des bords. Considérez que les bords ne sont pas encore des contours. Si vous extrayez directement les contours des bords, ils peuvent ne pas être fermés/séparés, etc.:

// extract contours of the canny image:
std::vector<std::vector<cv::Point> > contoursH;
std::vector<cv::Vec4i> hierarchyH;
cv::findContours(cannyH,contoursH, hierarchyH, CV_RETR_TREE , CV_CHAIN_APPROX_SIMPLE);

// draw the contours to a copy of the input image:
cv::Mat outputH = input.clone();
for( int i = 0; i< contoursH.size(); i++ )
 {
   cv::drawContours( outputH, contoursH, i, cv::Scalar(0,0,255), 2, 8, hierarchyH, 0);
 }

enter image description here

vous pouvez supprimer ces petits contours en vérifiant cv::contourArea(contoursH[i]) > someThreshold avant de dessiner. Mais vous voyez les deux galettes à gauche pour être connectées? Voici la partie la plus difficile ... utilisez des heuristiques pour "améliorer" votre résultat.

cv::dilate(cannyH, cannyH, cv::Mat());
cv::dilate(cannyH, cannyH, cv::Mat());
cv::dilate(cannyH, cannyH, cv::Mat());

Dilation before contour extraction will "close" the gaps between different objects but increase the object size too.

enter image description here

si vous en extrayez les contours, cela ressemblera à ceci:

enter image description here

Si vous choisissez plutôt uniquement les contours "intérieurs", c'est exactement ce que vous aimez:

cv::Mat outputH = input.clone();
for( int i = 0; i< contoursH.size(); i++ )
 {
    if(cv::contourArea(contoursH[i]) < 20) continue; // ignore contours that are too small to be a patty
    if(hierarchyH[i][3] < 0) continue;  // ignore "outer" contours

    cv::drawContours( outputH, contoursH, i, cv::Scalar(0,0,255), 2, 8, hierarchyH, 0);
 }

enter image description here

gardez à l'esprit que la dilatation et le contour intérieur sont un peu flous, donc cela pourrait ne pas fonctionner pour différentes images et si les bords initiaux sont mieux placés autour de la bordure de l'objet, il pourrait 1. ne pas être nécessaire de faire la dilatation et le contour intérieur et 2 S'il est encore nécessaire, le dilat rendra l'objet plus petit dans ce scénario (ce qui, heureusement, est idéal pour l'image d'échantillon donnée.).

EDIT: Quelques informations importantes sur HSV: Le canal de teinte donnera à chaque pixel une couleur du spectre, même si la saturation est très faible (= gris/blanc) ou si la couleur est très faible (valeur) si souvent on souhaite seuil la saturation et les canaux de valeur pour trouver une couleur spécifique! Cela pourrait être beaucoup plus facile et beaucoup plus simple à gérer que la dilatation que j'ai utilisée dans mon code.

67
Micka