检测图像中的对象区域opencv

时间:2015-05-20 14:40:54

标签: opencv cluster-analysis object-detection connected-components

我们目前正在尝试使用OpenCV,C ++版本中提供的方法检测医疗器械图像中的对象区域。示例图像如下所示: 1

以下是我们要遵循的步骤:

  • 将图像转换为灰度
  • 应用中值过滤器
  • 使用sobel过滤器查找边缘
  • 使用阈值25
  • 将结果转换为二进制图像
  • 歪曲图像以确保我们有清晰的边缘
  • 查找X最大连接组件

此方法适用于图像1,结果如下:

1-result

  • 黄色边框是检测到的连接组件。
  • 矩形只是为了突出显示连接组件的存在。
  • 为了获得可理解的结果,我们只删除了完全在另一个内部的连接组件,因此最终结果是这样的:

1-endResult

到目前为止,一切都很好,但另一个图像样本使我们的工作变得复杂,如下所示。 2

在物体下面放一条浅绿色毛巾会产生这样的图像:

2-result

按照我们之前的做法过滤了区域后,我们得到了这个:

2-endresult

显然,这不是我们需要的......我们除了这样的东西:

2-ExpectedResult

我正在考虑聚集最近发现的连接组件(不知何故!!),这样我们就可以最大限度地减少毛巾存在的影响,但是不知道它是否可行,或者有人尝过这样的东西之前?此外,有没有人有更好的想法来克服这类问题?

提前致谢。

3 个答案:

答案 0 :(得分:21)

这是我尝试过的。

在图像中,背景主要是浅绿色,背景区域远大于前景区域。因此,如果您采用图像的颜色直方图,则绿色区域将具有更高的值。阈值此直方图使得具有较小值的箱设置为零。这样我们很可能会保留绿色(更高价值)的垃圾箱并丢弃其他颜色。然后反投影这个直方图。反投影将突出显示图像中的这些绿色区域。

<强>反投影: backprojection

  • 然后对此反投影进行阈值处理。这为我们提供了背景资料。

背景(经过一些形态学过滤后): background

  • 反转背景以获得前景。

前景(经过一些形态学过滤后): foreground

  • 然后找到前景的轮廓。

我认为这给出了一个合理的分割,并使用它作为掩码,你可以使用像GrabCut这样的分割来细化边界(我还没有尝试过)。

修改 我尝试了GrabCut方法,它确实改进了界限。我添加了GrabCut分段的代码。

<强>轮廓: contours

使用前景作为掩码进行GrabCut分割: gc

我正在使用OpenCV C API进行直方图处理部分。

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

更新:我们可以使用C ++界面执行上述操作,如下所示。

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;

答案 1 :(得分:5)

我会考虑一些选择。我的假设是相机不动。我没有使用图像或编写任何代码,所以这主要来自经验。

  • 尝试使用分割算法分离背景,而不仅仅是寻找边缘。高斯混合可以帮助解决这个问题。给定同一区域(即视频)上的一组图像,您可以取消持久的区域。然后,将弹出诸如乐器之类的新项目。然后可以在blob上使用连接的组件。

    • 我会查看分段算法,看看您是否可以优化条件以使其适合您。一个主要项目是确保您的相机稳定,或者您自己预处理稳定图像。
  • 我会考虑使用兴趣点来识别图像中包含大量新材料的区域。鉴于背景相对简单,诸如针之类的小物体将产生一堆兴趣点。毛巾应该更稀疏。也许将检测到的兴趣点覆盖在连接的组件覆盖区上将为您提供“密度”度量标准,然后您可以将其设置为阈值。如果连接的组件对于项目的区域具有大的兴趣点比率,那么它是一个有趣的对象。

    • 在此注释中,您甚至可以使用Convex Hull来修剪已检测到的对象,从而清理连接的组件占用空间。这可以帮助诸如医疗器械在毛巾上投下阴影的情况,该阴影拉伸组件区域。这是一个猜测,但兴趣点肯定可以提供更多的信息,而不仅仅是边缘。
  • 最后,鉴于你有一个稳定的背景,视野中有清晰的物体,我会看一下Bag of of Features,看看你是否可以只检测到图像中的每个物体。这可能很有用,因为这些图像中的对象似乎存在一致的模式。你可以建立一个大的图像数据库,如针,纱布,剪刀等。然后在OpenCV的BoF将为你找到那些候选人。您还可以将其与您正在进行的其他操作混合以比较结果。

  •   -

答案 2 :(得分:0)

我还建议你的初始版本。您还可以跳过轮廓,其区域的宽度和高度大于图像宽度和高度的一半。

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