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:
Voici les étapes que nous suivons:
Cette approche fonctionne parfaitement pour l'image 1
et voici le résultat:
Jusqu'à présent, tout allait bien, mais un autre échantillon d'image a compliqué notre travail présenté ci-dessous.
Avoir une petite serviette vert clair sous les objets donne cette image:
Après avoir filtré les régions comme nous l'avons fait précédemment, nous avons obtenu ceci:
De toute évidence, ce n’est pas ce dont nous avons besoin ... nous faisons exception de ce qui suit:
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.
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:
Fond (après filtrage morphologique):
Avant-plan (après un filtrage morphologique):
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:
Segmentation GrabCut utilisant le premier plan comme masque:
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;
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.
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.
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.
Sac de fonctionnalités utilisant OpenCV
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.