检测图像opencv中的对象区域

Posted

技术标签:

【中文标题】检测图像opencv中的对象区域【英文标题】:Detecting object regions in image opencv 【发布时间】:2015-08-01 21:25:56 【问题描述】:

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

以下是我们正在执行的步骤:

将图像转换为灰度 应用中值过滤器 使用 sobel 滤波器查找边缘 使用阈值 25 将结果转换为二值图像 骨架化图像以确保边缘整齐 查找 X 个最大的连通分量

这种方法非常适用于图像1,结果如下:

黄色边框是检测到的连通分量。 矩形只是为了突出连接组件的存在。 为了获得易于理解的结果,我们只是移除了完全位于任何其他组件中的连接组件,因此最终结果是这样的:

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

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

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

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

我正在考虑对找到的最接近的连接组件进行聚类(不知何故!!),这样我们就可以最大限度地减少毛巾存在的影响,但还不知道这是否可行或有人以前尝试过这样的事情?另外,有没有人有更好的办法来克服这种问题?

提前致谢。

【问题讨论】:

【参考方案1】:

我也会向您的初始版本提出一个想法。您也可以跳过轮廓,其区域的宽度和高度大于图像宽度和高度的一半。

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

【讨论】:

【参考方案2】:

这是我尝试过的。

在图像中,背景大部分是绿色的,背景的面积比前景的面积大得多。因此,如果您获取图像的颜色直方图,则绿色箱将具有更高的值。将此直方图设置为阈值,以便将具有较小值的 bin 设置为零。这样我们很可能会保留绿色(较高值)的箱子并丢弃其他颜色。然后反向投影这个直方图。反投影将突出显示图像中的这些绿色区域。

反投影:

然后对该反投影进行阈值化。这为我们提供了背景。

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

反转背景以获得前景。

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

然后找到前景的轮廓。

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

编辑: 我尝试了 GrabCut 方法,它确实细化了边界。我已经添加了 GrabCut 分割的代码。

轮廓:

使用前景作为掩码的 GrabCut 分割:

我将 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;

【讨论】:

我要感谢你的贡献。这正是我想要的,你应该得到赏金 :) 再次感谢。 @Maystro 查看更新。一段时间以来,我一直在考虑添加此更新,但错过了。现在是最好的时机:) 谢谢,是否也可以添加缺少的部分?【参考方案3】:

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

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

我会查看分割算法,看看您是否可以优化条件以使其适合您。一项主要工作是确保您的相机稳定,或者您自己进行预处理来稳定图像。

我会考虑使用兴趣点来识别图像中包含大量新材料的区域。鉴于背景比较平淡,针等小物体会产生一堆兴趣点。毛巾应该更稀疏。也许将检测到的兴趣点覆盖在连接的组件足迹上会给您一个“密度”指标,然后您可以对其进行阈值处理。如果连通分量在该项目的区域中具有较大比例的兴趣点,则它是一个有趣的对象。

在此说明中,您甚至可以通过使用 Convex Hull 修剪已检测到的对象来清理连接的组件占用空间。这可能有助于一些情况,例如医疗器械在毛巾上投射阴影,从而拉伸组件区域。这是一个猜测,但兴趣点绝对可以为您提供更多信息,而不仅仅是边缘。

最后,鉴于您有一个稳定的背景,并且可以看到清晰的对象,我会看看 Bag-of-Features 看看您是否可以检测到图像中的每个单独的对象。这可能很有用,因为这些图像中的对象似乎具有一致的模式。您可以建立一个大的图像数据库,例如针、纱布、剪刀等。然后,OpenCV 中的 BoF 将为您找到这些候选对象。您还可以将其与您正在执行的其他操作混合以比较结果。

使用 OpenCV 的功能包

http://www.codeproject.com/Articles/619039/Bag-of-Features-Descriptor-on-SIFT-Features-with-O -

【讨论】:

感谢您的回复。实际上,我正在手术期间拍摄器械台。开始时,所有器械都放在桌子上,外科医生将逐渐使用它们。所以我需要在视频的每一刻都知道桌子上展示的乐器是什么。然后,与只检查表中的新人无关。我会考虑你的建议并尝试一些东西,然后点击。 鉴于项目在收集开始时呈现,Bag-of-Features 可能比背景分割更好。一种不优雅但经过验证的技术是将物品放在图案上。在航空领域,工具箱需要有工具箱中每个工具的轮廓。这允许快速问责制检查。考虑到准备工作,如果您有特定操作的固定模式,它可能会大大简化您的问题。它并不优雅,而是另一种消除错误的技术。您可以在角落放置一个 ID 标签,以便您预先加载预期的项目。 感谢您的贡献,但我可以投票赞成您的答案,因为接受的答案包含代码和我需要的一切。

以上是关于检测图像opencv中的对象区域的主要内容,如果未能解决你的问题,请参考以下文章

Python+OpenCV图像处理—— 模板匹配

Python+OpenCV图像处理—— 模板匹配

OpenCV实战(15)——轮廓检测详解

如何利用opencv进行样本训练

Opencv 笔记5 边缘处理-cannysobelLaplacianPrewitt

选择区域 OpenCV