web-dev-qa-db-fra.com

Détection de régions d'objet dans l'opencv image

Nous essayons actuellement de détecter les régions d'objets dans les images d'instruments médicaux en utilisant les méthodes disponibles dans OpenCV, version C++. Un exemple d'image est présenté ci-dessous: 1

Voici les étapes que nous suivons:

  • Conversion de l'image en niveaux de gris
  • Appliquer le filtre médian 
  • Trouver des arêtes à l'aide du filtre sobel
  • Convertir le résultat en image binaire en utilisant un seuil de 25
  • Squelettiser l'image pour s'assurer que nous avons des bords nets
  • Recherche de X plus grands composants connectés

Cette approche fonctionne parfaitement pour l'image 1 et voici le résultat:

1-result

  • Les bordures jaunes sont les composants connectés détectés.
  • Les rectangles servent uniquement à mettre en évidence la présence d’un composant connecté.
  • Pour obtenir des résultats compréhensibles, nous venons de supprimer les composants connectés qui sont complètement insérés dans un autre, le résultat final ressemble à ceci:

1-endResult

Jusqu'à présent, tout allait bien, mais un autre échantillon d'image a compliqué notre travail présenté ci-dessous. 2

Avoir une petite serviette vert clair sous les objets donne cette image:

2-result

Après avoir filtré les régions comme nous l'avons fait précédemment, nous avons obtenu ceci:

2-endresult

De toute évidence, ce n’est pas ce dont nous avons besoin ... nous faisons exception de ce qui suit:

2-ExpectedResult

Je pense à regrouper les composants connectés les plus proches trouvés (en quelque sorte !!) afin de minimiser l'impact de la présence de la serviette, mais je ne sais pas encore si c'est faisable ou si quelqu'un a déjà essayé quelque chose comme ça auparavant? Aussi, est-ce que quelqu'un a une meilleure idée pour surmonter ce genre de problèmes?

Merci d'avance.

19
Maystro

Voici ce que j'ai essayé. 

Sur les images, l’arrière-plan est principalement verdâtre et la zone de l’arrière-plan est beaucoup plus grande que celle du premier plan. Donc, si vous prenez un histogramme de couleur de l'image, les bacs verdâtres auront des valeurs plus élevées. Seuil cet histogramme de sorte que les bacs ayant des valeurs plus petites soient mis à zéro. De cette façon, nous conserverons probablement les bacs verdâtres (valeur supérieure) et éliminerons les autres couleurs. Retournez ensuite cet histogramme. La projection arrière mettra en évidence ces régions verdâtres dans l'image.

Rétroprojection: backprojection

  • Puis seuil cette projection arrière. Cela nous donne le fond.

Fond (après filtrage morphologique): background

  • Inversez l'arrière-plan pour passer au premier plan.

Avant-plan (après un filtrage morphologique): foreground

  • Trouvez ensuite les contours du premier plan.

Je pense que cela donne une segmentation raisonnable, et en utilisant ceci comme masque, vous pourrez peut-être utiliser une segmentation telle que GrabCut pour affiner les limites (je n'ai pas encore essayé cela).

EDIT: J’ai essayé l’approche GrabCut et elle a permis d’affiner les limites. J'ai ajouté le code pour la segmentation GrabCut.

Contours: contours

Segmentation GrabCut utilisant le premier plan comme masque: gc

J'utilise l'API C OpenCV pour la partie traitement de l'histogramme.

// load the color image
IplImage* im = cvLoadImage("bFly6.jpg");

// get the color histogram
IplImage* im32f = cvCreateImage(cvGetSize(im), IPL_DEPTH_32F, 3);
cvConvertScale(im, im32f);

int channels[] = {0, 1, 2};
int histSize[] = {32, 32, 32};
float rgbRange[] = {0, 256};
float* ranges[] = {rgbRange, rgbRange, rgbRange};

CvHistogram* hist = cvCreateHist(3, histSize, CV_HIST_ARRAY, ranges);
IplImage* b = cvCreateImage(cvGetSize(im32f), IPL_DEPTH_32F, 1);
IplImage* g = cvCreateImage(cvGetSize(im32f), IPL_DEPTH_32F, 1);
IplImage* r = cvCreateImage(cvGetSize(im32f), IPL_DEPTH_32F, 1);
IplImage* backproject32f = cvCreateImage(cvGetSize(im), IPL_DEPTH_32F, 1);
IplImage* backproject8u = cvCreateImage(cvGetSize(im), IPL_DEPTH_8U, 1);
IplImage* bw = cvCreateImage(cvGetSize(im), IPL_DEPTH_8U, 1);
IplConvKernel* kernel = cvCreateStructuringElementEx(3, 3, 1, 1, MORPH_ELLIPSE);

cvSplit(im32f, b, g, r, NULL);
IplImage* planes[] = {b, g, r};
cvCalcHist(planes, hist);

// find min and max values of histogram bins
float minval, maxval;
cvGetMinMaxHistValue(hist, &minval, &maxval);

// threshold the histogram. this sets the bin values that are below the threshold to zero
cvThreshHist(hist, maxval/32);

// backproject the thresholded histogram. backprojection should contain higher values for the
// background and lower values for the foreground
cvCalcBackProject(planes, backproject32f, hist);

// convert to 8u type
double min, max;
cvMinMaxLoc(backproject32f, &min, &max);
cvConvertScale(backproject32f, backproject8u, 255.0 / max);

// threshold backprojected image. this gives us the background
cvThreshold(backproject8u, bw, 10, 255, CV_THRESH_BINARY);

// some morphology on background
cvDilate(bw, bw, kernel, 1);
cvMorphologyEx(bw, bw, NULL, kernel, MORPH_CLOSE, 2);

// get the foreground
cvSubRS(bw, cvScalar(255, 255, 255), bw);
cvMorphologyEx(bw, bw, NULL, kernel, MORPH_OPEN, 2);
cvErode(bw, bw, kernel, 1);

// find contours of the foreground
//CvMemStorage* storage = cvCreateMemStorage(0);
//CvSeq* contours = 0;
//cvFindContours(bw, storage, &contours);
//cvDrawContours(im, contours, CV_RGB(255, 0, 0), CV_RGB(0, 0, 255), 1, 2);

// grabcut
Mat color(im);
Mat fg(bw);
Mat mask(bw->height, bw->width, CV_8U);

mask.setTo(GC_PR_BGD);
mask.setTo(GC_PR_FGD, fg);

Mat bgdModel, fgdModel;
grabCut(color, mask, Rect(), bgdModel, fgdModel, GC_INIT_WITH_MASK);

Mat gcfg = mask == GC_PR_FGD;

vector<vector<cv::Point>> contours;
vector<Vec4i> hierarchy;
findContours(gcfg, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
for(int idx = 0; idx < contours.size(); idx++)
{
    drawContours(color, contours, idx, Scalar(0, 0, 255), 2);
}

// cleanup ...

UPDATE: Nous pouvons faire ce qui précède à l'aide de l'interface C++, comme indiqué ci-dessous.

const int channels[] = {0, 1, 2};
const int histSize[] = {32, 32, 32};
const float rgbRange[] = {0, 256};
const float* ranges[] = {rgbRange, rgbRange, rgbRange};

Mat hist;
Mat im32fc3, backpr32f, backpr8u, backprBw, kernel;

Mat im = imread("bFly6.jpg");

im.convertTo(im32fc3, CV_32FC3);
calcHist(&im32fc3, 1, channels, Mat(), hist, 3, histSize, ranges, true, false);
calcBackProject(&im32fc3, 1, channels, hist, backpr32f, ranges);

double minval, maxval;
minMaxIdx(backpr32f, &minval, &maxval);
threshold(backpr32f, backpr32f, maxval/32, 255, THRESH_TOZERO);
backpr32f.convertTo(backpr8u, CV_8U, 255.0/maxval);
threshold(backpr8u, backprBw, 10, 255, THRESH_BINARY);

kernel = getStructuringElement(MORPH_ELLIPSE, Size(3, 3));

dilate(backprBw, backprBw, kernel);
morphologyEx(backprBw, backprBw, MORPH_CLOSE, kernel, Point(-1, -1), 2);

backprBw = 255 - backprBw;

morphologyEx(backprBw, backprBw, MORPH_OPEN, kernel, Point(-1, -1), 2);
erode(backprBw, backprBw, kernel);

Mat mask(backpr8u.rows, backpr8u.cols, CV_8U);

mask.setTo(GC_PR_BGD);
mask.setTo(GC_PR_FGD, backprBw);

Mat bgdModel, fgdModel;
grabCut(im, mask, Rect(), bgdModel, fgdModel, GC_INIT_WITH_MASK);

Mat fg = mask == GC_PR_FGD;
21
dhanushka

Je considérerais quelques options. Mon hypothèse est que la caméra ne bouge pas. Je n'ai pas utilisé les images ni écrit de code, c'est donc surtout une expérience.

  • Plutôt que de rechercher simplement des bords, essayez de séparer l’arrière-plan en utilisant un algorithme de segmentation. Un mélange de gaussien peut vous aider. Avec un ensemble d’images sur la même région (vidéo), vous pouvez annuler les régions persistantes. Ensuite, de nouveaux éléments tels que des instruments vont apparaître. Les composants connectés peuvent ensuite être utilisés sur les blobs. 

    • Je regarderais des algorithmes de segmentation pour voir si vous pouvez optimiser les conditions pour que cela fonctionne pour vous. Un élément important est de vous assurer que votre appareil photo est stable ou de stabiliser vous-même les images en pré-traitement.
  • J'envisagerais d'utiliser des points d'intérêt pour identifier des régions dans l'image avec beaucoup de nouveaux matériaux. Étant donné que l'arrière-plan est relativement simple, de petits objets tels que des aiguilles créeront une multitude de points d'intérêt. La serviette devrait être beaucoup plus clairsemée. Si vous superposez les points d’intérêt détectés à l’empreinte du composant connecté, vous obtiendrez une métrique de «densité» que vous pourrez ensuite définir comme seuil. Si le composant connecté a un grand ratio de points d'intérêt pour la zone de l'élément, il s'agit d'un objet intéressant. 

    • Sur cette note, vous pouvez même nettoyer l’empreinte des composants connectés en utilisant une coque convexe pour élaguer les objets que vous avez détectés. Cela peut aider des situations telles qu'un instrument médical projetant une ombre sur la serviette qui étend la région du composant. C'est une hypothèse, mais les points d'intérêt peuvent certainement vous donner plus d'informations que de simples contours. 
  • Enfin, étant donné que vous avez un arrière-plan stable avec des objets clairs en vue, je jetterai un coup d'œil à Bag-of-Features pour voir si vous pouvez simplement détecter chaque objet de l'image. Cela peut être utile car il semble y avoir un motif cohérent pour les objets dans ces images. Vous pouvez construire une grande base de données d'images telles que des aiguilles, de la gaze, des ciseaux, etc. Ensuite, BoF, qui se trouve dans OpenCV, trouvera ces candidats pour vous. Vous pouvez également le combiner avec d'autres opérations que vous effectuez pour comparer les résultats. 

  • -
5
msmith81886

Je suggérerais également une idée à votre version initiale. Vous pouvez également ignorer les contours dont les régions ont une largeur et une hauteur supérieures à la moitié de la largeur et de la hauteur de l'image.

//take the rect of the contours

Rect rect = Imgproc.boundingRect(contours.get(i));

if (rect.width < inputImageWidth / 2 && rect.height < inputImageHeight / 2)

//then continue to draw or use for next purposes.
0
Tejash L