【问题标题】:counting objects & better way to filling holes计数对象和填充孔的更好方法
【发布时间】:2014-08-03 23:33:01
【问题描述】:

我是 OpenCV 的新手,我正在尝试计算图像中对象的数量。我在使用 MATLAB Image Processing Toolbox 之前已经这样做了,并且在 OpenCV (Android) 中也采用了相同的方法。

第一步是将图像转换为灰度。然后对其进行阈值化,然后计算 blob 的数量。在 Matlab 中有一个命令 - “bwlabel”,它给出了 blob 的数量。我在 OpenCV 中找不到这样的东西(同样,我是 OpenCV 和 Android 的菜鸟)。

这是我的代码,

//JPG to Bitmap to MAT
Bitmap i = BitmapFactory.decodeFile(imgPath + "mms.jpg");
Bitmap bmpImg = i.copy(Bitmap.Config.ARGB_8888, false);
Mat srcMat = new Mat ( bmpImg.getHeight(), bmpImg.getWidth(), CvType.CV_8UC3);
Utils.bitmapToMat(bmpImg, srcMat);

//convert to gray scale and save image
Mat gray = new Mat(srcMat.size(), CvType.CV_8UC1);
Imgproc.cvtColor(srcMat, gray, Imgproc.COLOR_RGB2GRAY,4);
//write bitmap
Boolean bool = Highgui.imwrite(imgPath + "gray.jpg", gray);

//thresholding
Mat threshed = new Mat(bmpImg.getWidth(),bmpImg.getHeight(), CvType.CV_8UC1);
Imgproc.adaptiveThreshold(gray, threshed, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 75, 5);//15, 8 were original tests. Casey was 75,10
Core.bitwise_not(threshed, threshed);
Utils.matToBitmap(threshed, bmpImg);
//write bitmap
bool = Highgui.imwrite(imgPath + "threshed.jpg", threshed);
Toast.makeText(this, "Thresholded image saved!", Toast.LENGTH_SHORT).show();

在下一步中,我尝试使用膨胀和腐蚀来填充孔和字母,但斑点会相互连接,最终会给出错误的计数。在调整膨胀和腐蚀参数时,需要在填充孔和使斑点相互连接之间进行权衡。

这是代码,

//morphological operations
//dilation
Mat dilated = new Mat(bmpImg.getWidth(),bmpImg.getHeight(), CvType.CV_8UC1);
Imgproc.dilate(threshed, dilated, Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new org.opencv.core.Size (16, 16)));
Utils.matToBitmap(dilated, bmpImg);
//write bitmap
bool = Highgui.imwrite(imgPath + "dilated.jpg", dilated);
Toast.makeText(this, "Dilated image saved!", Toast.LENGTH_SHORT).show();

//erosion
Mat eroded = new Mat(bmpImg.getWidth(),bmpImg.getHeight(), CvType.CV_8UC1);
Imgproc.erode(dilated, eroded, Imgproc.getStructuringElement(Imgproc.MORPH_ELLIPSE, new org.opencv.core.Size(15, 15)));
Utils.matToBitmap(eroded, bmpImg);
//write bitmap
bool = Highgui.imwrite(imgPath + "eroded.jpg", eroded);
Toast.makeText(this, "Eroded image saved!", Toast.LENGTH_SHORT).show();

因为有时我的 M&M 巧克力豆可能就在旁边! ;)

我也尝试使用 Hough Circles,但结果非常不可靠(用硬币图像和真实硬币测试)

这是代码,

//hough circles
Mat circles = new Mat();

// parameters
int iCannyUpperThreshold = 100;
int iMinRadius = 20;
int iMaxRadius = 400;
int iAccumulator = 100;

Imgproc.HoughCircles(gray, circles, Imgproc.CV_HOUGH_GRADIENT, 
         1.0, gray.rows() / 8, iCannyUpperThreshold, iAccumulator, 
         iMinRadius, iMaxRadius);

// draw
if (circles.cols() > 0)
{
    Toast.makeText(this, "Coins : " +circles.cols() , Toast.LENGTH_LONG).show();
}
else
{
    Toast.makeText(this, "No coins found", Toast.LENGTH_LONG).show();
}

这种方法的问题在于该算法仅限于完美的圆(AFAIK)。因此,当我尝试扫描和计算桌上的 M&Ms 或硬币时(因为设备的角度发生了变化),它效果不佳。使用这种方法,有时我得到的结果更少。检测到的硬币数量,有时甚至更多(我不明白为什么更多??)。

在扫描此图像时,该应用有时会显示 19 个硬币,有时会显示 38 个硬币......我知道还有其他特征可能会被检测为圆圈,但我完全不明白为什么 38..?

所以我的问题...

  1. 有没有更好的方法来填充孔而不连接相邻的斑点?
  2. 如何准确计算对象的数量?我不想将我的应用限制为仅使用 HoughCircles 方法计算圈数。

仅供参考:OpenCV-2.4.9-android-sdk。请记住,我也是 OpenCV 和 Android 的新手。

非常感谢任何帮助。

感谢和干杯!

贾南

【问题讨论】:

标签: android opencv count fill blobs


【解决方案1】:

因此,我们将您生成的阈值图像作为输入并进一步修改它。目前的代码是 C++ 但我想你可以很容易地将它转换成 android 平台

现在您可以尝试泛洪填充,而不是扩张或模糊

导致

最后现在应用我们得到的轮廓检测算法算法

上面的代码是

    Mat dst = imread($path to the threshold image); // image should be single channel black and white image
    imshow("dst",dst);

    cv::Mat mask = cv::Mat::zeros(dst.rows + 2, dst.cols + 2, CV_8U);

            // A image with size greater than the present object is created

    cv::floodFill(dst, mask, cv::Point(0,0), 255, 0, cv::Scalar(), cv::Scalar(),  4 + (255 << 8) + cv::FLOODFILL_MASK_ONLY);
    erode(mask,mask,Mat());
    // Now to remove the outer boundary
    rectangle(mask,Rect(0,0,mask.cols,mask.rows), Scalar(255,255,255),2,8,0);
    imshow("Mask",mask);


    Mat copy;
    mask.copyTo(copy);

    vector<vector<Point> > contours;
    vector<Vec4i> hierarchy;
    findContours( copy, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );

    vector<vector<Point> > contours_poly( contours.size() );
    vector<Rect> boundRect( contours.size() );
    vector<Point2f>center( contours.size() );
    vector<float>Distance( contours.size() );
    vector<float>radius( contours.size() );

    Mat drawing = cv::Mat::zeros(mask.rows, mask.cols, CV_8U);
    int num_object = 0;
    for( int i = 0; i < contours.size(); i++ ){
        approxPolyDP( Mat(contours[i]), contours_poly[i], 3, true );

            // To get rid of the smaller object and the outer rectangle created
            //because of the additional mask image we enforce a lower limit on area 
            //to remove noise and an upper limit to remove the outer border.    

        if (contourArea(contours_poly[i])>(mask.rows*mask.cols/10000) && contourArea(contours_poly[i])<mask.rows*mask.cols*0.9){
            boundRect[i] = boundingRect( Mat(contours_poly[i]) );
            minEnclosingCircle( (Mat)contours_poly[i], center[i], radius[i] );
            circle(drawing,center[i], (int)radius[i], Scalar(255,255,255), 2, 8, 0);
            rectangle(drawing,boundRect[i], Scalar(255,255,255),2,8,0);
            num_object++;
        }
    }

    cout <<"No. of object detected =" <<num_object<<endl;


    imshow("drawing",drawing);

    waitKey(2);
    char key = (char) waitKey(20);
    if(key == 32){
    // You can save your images here using a space

            }

我希望这可以帮助您解决问题

【讨论】:

  • 我被 Android 中的 floodFill 语法困住了。我得到的所有例子都是用 C++ 编写的。这是我的代码Mat mask = new Mat(threshed.rows() + 2, threshed.cols() + 2, CvType.CV_8U, new Scalar(0)); Point point = new Point(); point.x = 0; point.y = 0; Imgproc.floodFill(threshed, mask, point, new Scalar(255, 255, 255)); 在将鼠标悬停在 floodFill 上时,我阅读了这个快速修复建议“点对点更改”。知道如何解决这个问题吗?谢谢!
  • 代替Point point = new Point();点.x = 0;点.y = 0;你尝试过新的 Point(0,0) 吗?
  • 还可以在这里查看问题中引用的文字grokbase.com/t/gg/android-opencv/123en5esw9/…
  • 我试过new Point(0, 0),我得到了同样的快速修复建议。我还浏览了您上面提到的链接中的代码...找不到解决方案
  • stackoverflow.com/questions/23107123/… 希望这会有所帮助,关于您在上一个答案中询问的计数错误,请检查小尺寸的矩形框。代码已包含在我的答案中。有时非常小的对象(噪声)被检测到,或者在某些情况下,边界被检测为轮廓。
【解决方案2】:

看看就好,

您可以将轮廓大小作为对象计数。

代码:

  Mat tmp,thr;
  Mat src=imread("img.jpg",1);
  blur(src,src,Size(3,3));
  cvtColor(src,tmp,CV_BGR2GRAY);
  threshold(tmp,thr,220,255,THRESH_BINARY_INV);
  imshow("thr",thr);

  vector< vector <Point> > contours; // Vector for storing contour
  vector< Vec4i > hierarchy;

  findContours( thr, contours, hierarchy,CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE ); // Find the contours in the image
  for( int i = 0; i< contours.size(); i=hierarchy[i][0] ) // iterate through each contour.
     {
       Rect r= boundingRect(contours[i]);
       rectangle(src,r, Scalar(0,0,255),2,8,0);
     }
 cout<<"Numeber of contour = "<<contours.size()<<endl;
 imshow("src",src);
 waitKey();

【讨论】:

  • 感谢您的快速回复。对于上述 M&M 图像,我得到了错误的结果。 List<matofpoint> 轮廓 = new ArrayList<matofpoint>(); Imgproc.findContours(平滑,轮廓,新垫(),Imgproc.RETR_EXTERNAL,Imgproc.CHAIN_APPROX_SIMPLE); Imgproc.drawContours(平滑,轮廓,-1,新标量(255,255,0)); Toast.makeText(this, "对象数:"+contours.size(), Toast.LENGTH_LONG).show(); </matofpoint></matofpoint> 我的计数是 14..知道为什么吗?
  • 对上述评论中的糟糕表现感到抱歉。我试图将其编码成评论,但不能......无论如何。如果你能帮助我,那就太好了。谢谢!
猜你喜欢
  • 1970-01-01
  • 2012-05-06
  • 1970-01-01
  • 1970-01-01
  • 2015-05-28
  • 2014-06-04
  • 2014-04-14
  • 2015-10-23
  • 1970-01-01
相关资源
最近更新 更多