检测图像opencv中的对象区域
Detecting object regions in image opencv
我们目前正在尝试使用 OpenCV C++ 版本中可用的方法来检测医疗器械图像中的对象区域。示例图像如下所示:
以下是我们要执行的步骤:
- 正在将图像转换为灰度
- 应用中值滤波器
- 使用 sobel 过滤器查找边
- 使用阈值 25 将结果转换为二值图像
- 骨架化图像以确保我们有整齐的边缘
- 寻找 X 个最大的连通分量
这种方法非常适合图像 1
,结果如下:
- 黄色边框是检测到的连通分量。
- 矩形只是为了突出连接组件的存在。
- 为了得到可理解的结果,我们只是删除了完全在任何另一个内部的连接组件,所以最终结果是这样的:
到目前为止,一切都很好,但另一个图像样本使我们的工作变得复杂,如下所示。
在物体下面放一条浅绿色的小毛巾会产生这张图片:
像我们之前那样过滤区域后,我们得到了这个:
显然,这不是我们需要的..我们排除了这样的东西:
我正在考虑将找到的最接近的连接组件聚类(以某种方式!!)这样我们就可以最大限度地减少毛巾存在的影响,但还不知道它是否可行或有人尝试过类似的东西这个以前?另外,有没有人有更好的办法来克服这类问题?
提前致谢。
我会考虑几个选项。我的假设是相机不会移动。我没有使用过图像或编写任何代码,所以这主要是根据经验。
与其仅仅寻找边缘,不如尝试使用分割算法分离背景。高斯混合可以帮助解决这个问题。给定同一区域(即视频)上的一组图像,您可以取消持久性区域。然后,会弹出新的项目,例如仪器。然后可以在 blob 上使用连接的组件。
- 我会研究分割算法,看看您是否可以优化条件以使这项工作适合您。一个主要项目是确保您的相机稳定或您自己预处理图像稳定。
我会考虑使用兴趣点来识别图像中有很多新区域的区域 material。鉴于背景比较平淡,针等小物件会产生一堆兴趣点。毛巾应该更稀疏。也许将检测到的兴趣点覆盖在连接的组件足迹上会给您一个 "density" 指标,然后您可以对其进行阈值处理。如果连通分量对于项目的区域具有较大的兴趣点比例,则它是一个有趣的对象。
- 关于这一点,您甚至可以通过使用 Convex Hull 修剪检测到的对象来清理连接的组件足迹。这可能有助于诸如医疗仪器在拉伸组件区域的毛巾上投射阴影的情况。这是一个猜测,但兴趣点绝对可以为您提供比边缘更多的信息。
最后,如果你有一个稳定的背景并且视野中有清晰的物体,我会看一下 Bag-of-Features 看看你是否可以检测图像中的每个单独的物体。这可能很有用,因为这些图像中的对象似乎具有一致的模式。你可以建立一个大的图像数据库,如针、纱布、剪刀等。然后 OpenCV 中的 BoF 会为你找到这些候选对象。您还可以将其与您正在执行的其他操作混合使用以比较结果。
-
这是我试过的。
在图像中,背景大部分是绿色的,背景面积比前景大很多。因此,如果您获取图像的颜色直方图,则绿色分块将具有更高的值。阈值此直方图,以便将具有较小值的箱设置为零。这样我们很可能会保留绿色(较高值)的容器并丢弃其他颜色。然后反向投影这个直方图。反投影将突出显示图像中的这些绿色区域。
反投影:
- 然后阈值这个反投影。这给了我们背景。
背景(经过一些形态过滤后):
- 反转背景以获得前景。
前景(经过一些形态学过滤后):
- 然后找到前景的轮廓。
我认为这给出了一个合理的分割,并且使用它作为掩码你可以使用像 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;
我也会向您的初始版本提出一个想法。您也可以跳过等高线,其区域的宽度和高度大于图像宽度和高度的一半。
//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.
我们目前正在尝试使用 OpenCV C++ 版本中可用的方法来检测医疗器械图像中的对象区域。示例图像如下所示:
以下是我们要执行的步骤:
- 正在将图像转换为灰度
- 应用中值滤波器
- 使用 sobel 过滤器查找边
- 使用阈值 25 将结果转换为二值图像
- 骨架化图像以确保我们有整齐的边缘
- 寻找 X 个最大的连通分量
这种方法非常适合图像 1
,结果如下:
- 黄色边框是检测到的连通分量。
- 矩形只是为了突出连接组件的存在。
- 为了得到可理解的结果,我们只是删除了完全在任何另一个内部的连接组件,所以最终结果是这样的:
到目前为止,一切都很好,但另一个图像样本使我们的工作变得复杂,如下所示。
在物体下面放一条浅绿色的小毛巾会产生这张图片:
像我们之前那样过滤区域后,我们得到了这个:
显然,这不是我们需要的..我们排除了这样的东西:
我正在考虑将找到的最接近的连接组件聚类(以某种方式!!)这样我们就可以最大限度地减少毛巾存在的影响,但还不知道它是否可行或有人尝试过类似的东西这个以前?另外,有没有人有更好的办法来克服这类问题?
提前致谢。
我会考虑几个选项。我的假设是相机不会移动。我没有使用过图像或编写任何代码,所以这主要是根据经验。
与其仅仅寻找边缘,不如尝试使用分割算法分离背景。高斯混合可以帮助解决这个问题。给定同一区域(即视频)上的一组图像,您可以取消持久性区域。然后,会弹出新的项目,例如仪器。然后可以在 blob 上使用连接的组件。
- 我会研究分割算法,看看您是否可以优化条件以使这项工作适合您。一个主要项目是确保您的相机稳定或您自己预处理图像稳定。
我会考虑使用兴趣点来识别图像中有很多新区域的区域 material。鉴于背景比较平淡,针等小物件会产生一堆兴趣点。毛巾应该更稀疏。也许将检测到的兴趣点覆盖在连接的组件足迹上会给您一个 "density" 指标,然后您可以对其进行阈值处理。如果连通分量对于项目的区域具有较大的兴趣点比例,则它是一个有趣的对象。
- 关于这一点,您甚至可以通过使用 Convex Hull 修剪检测到的对象来清理连接的组件足迹。这可能有助于诸如医疗仪器在拉伸组件区域的毛巾上投射阴影的情况。这是一个猜测,但兴趣点绝对可以为您提供比边缘更多的信息。
最后,如果你有一个稳定的背景并且视野中有清晰的物体,我会看一下 Bag-of-Features 看看你是否可以检测图像中的每个单独的物体。这可能很有用,因为这些图像中的对象似乎具有一致的模式。你可以建立一个大的图像数据库,如针、纱布、剪刀等。然后 OpenCV 中的 BoF 会为你找到这些候选对象。您还可以将其与您正在执行的其他操作混合使用以比较结果。
-
这是我试过的。
在图像中,背景大部分是绿色的,背景面积比前景大很多。因此,如果您获取图像的颜色直方图,则绿色分块将具有更高的值。阈值此直方图,以便将具有较小值的箱设置为零。这样我们很可能会保留绿色(较高值)的容器并丢弃其他颜色。然后反向投影这个直方图。反投影将突出显示图像中的这些绿色区域。
反投影:
- 然后阈值这个反投影。这给了我们背景。
背景(经过一些形态过滤后):
- 反转背景以获得前景。
前景(经过一些形态学过滤后):
- 然后找到前景的轮廓。
我认为这给出了一个合理的分割,并且使用它作为掩码你可以使用像 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;
我也会向您的初始版本提出一个想法。您也可以跳过等高线,其区域的宽度和高度大于图像宽度和高度的一半。
//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.