为啥 OpenCV4Android 的 pointPolygonTest() 方法为每个像素返回-1?

Posted

技术标签:

【中文标题】为啥 OpenCV4Android 的 pointPolygonTest() 方法为每个像素返回-1?【英文标题】:Why is pointPolygonTest() method of OpenCV4Android returning -1 for every pixel?为什么 OpenCV4Android 的 pointPolygonTest() 方法为每个像素返回-1? 【发布时间】:2016-01-13 19:44:10 【问题描述】:

在以下代码中,我执行了以下步骤:

    从 sdcard 加载了一张图片。

    将其转换为 HSV 格式。

    使用inRange 函数来掩盖红色。

    使用findContours 查找轮廓。

    从这些轮廓中找到最大的轮廓。

    使用boundingRectsubmat 函数围绕最大轮廓创建了一个ROI。

    将此 ROI Mat 转换为 HSV 格式。

    遍历 ROI Mat,并检查每个像素是否位于最大轮廓内。 我使用pointPolygonTest 方法找到了这一点,但它为每个像素返回-1,从Log.i 输出I have pasted here 可以看出。问题是为什么?我该如何纠正这个问题。

    private Scalar detectColoredBlob() 
        rgbaFrame = Highgui.imread("/mnt/sdcard/DCIM/rgbaMat4Mask.bmp");
    
        Mat hsvImage = new Mat();
        Imgproc.cvtColor(rgbaFrame, hsvImage, Imgproc.COLOR_BGR2HSV);
        Highgui.imwrite("/mnt/sdcard/DCIM/hsvImage.bmp", hsvImage);// check
    
        Mat maskedImage = new Mat();
        Core.inRange(hsvImage, new Scalar(0, 100, 100), new Scalar(10, 255, 255), maskedImage);
        Highgui.imwrite("/mnt/sdcard/DCIM/maskedImage.bmp", maskedImage);// check
    
        List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        Imgproc.findContours(maskedImage, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
    
        // \/ We will use only the largest contour. Other contours (any other possible blobs of this color range) will be ignored.
        MatOfPoint largestContour = contours.get(0);
        double largestContourArea = Imgproc.contourArea(largestContour);
        for (int i = 1; i < contours.size(); ++i) // NB Notice the prefix increment.
            MatOfPoint currentContour = contours.get(i);
            double currentContourArea = Imgproc.contourArea(currentContour);
            if (currentContourArea > largestContourArea) 
                largestContourArea = currentContourArea;
                largestContour = currentContour;
            
        
        MatOfPoint2f largestContour2f = new MatOfPoint2f(largestContour.toArray());// Required on Line 289. See http://***.com/questions/11273588/how-to-convert-matofpoint-to-matofpoint2f-in-opencv-java-api
    
        Rect detectedBlobRoi = Imgproc.boundingRect(largestContour);
        Mat detectedBlobRgba = rgbaFrame.submat(detectedBlobRoi);
        Highgui.imwrite("/mnt/sdcard/DCIM/detectedBlobRgba.bmp", detectedBlobRgba);// check
    
        Mat detectedBlobHsv = new Mat();
        Imgproc.cvtColor(detectedBlobRgba, detectedBlobHsv, Imgproc.COLOR_BGR2HSV);
        Highgui.imwrite("/mnt/sdcard/DCIM/roiHsv.bmp", detectedBlobHsv);// check
    
        for (int firstCoordinate = 0; firstCoordinate < detectedBlobHsv.rows(); firstCoordinate++) 
            for (int secondCoordinate = 0; secondCoordinate < detectedBlobHsv.cols(); secondCoordinate++) 
                Log.i(TAG, "HAPPY " + Arrays.toString(detectedBlobHsv.get(firstCoordinate, secondCoordinate)));
                if (Imgproc.pointPolygonTest(largestContour2f, new Point(firstCoordinate, secondCoordinate), false) == -1) 
                    Log.i(TAG, "HAPPY ....................... OUTSIDE");
                
            
        
        Highgui.imwrite("/mnt/sdcard/DCIM/processedcontoured.bmp", detectedBlobHsv);// check
    

编辑:

我这样做是因为我需要计算位于轮廓内的像素的平均 HSV 颜色(即最大红色斑点的平均 HSV 颜色)。如果我通过正常公式计算 ROI detectedBlobHsv 的平均颜色,我会做类似

Scalar averageHsvColor= new Scalar(256);
Scalar sumHsvOfPixels = new Scalar(256);
sumHsvOfPixels = Core.sumElems(detectedBlobHsv); 
int numOfPixels = detectedBlobHsv.width() * detectedBlobHsv.height(); 
for (int channel=0; channel<sumHsvOfPixels.val.length; channel++)  
  averageHsvColor = sumHsvOfPixels.val[channel]/numOfPixels; 

所以这里有人(可能是你?)曾建议我一种方法来排除我轮廓之外的像素。我会这样实现:

//Giving pixels outside contour of interest an HSV value of `double[]0,0,0`, so that they don't affect the computation of `sumHsvOfPixels` while computing average, 
//and while keeping track of the number of pixels removed from computation this way, so we can subtract that number from the `$numOfPixels` during computation of average.
int pixelsRemoved = 0;
for (int row=0; row<detectedBlobHsv.rows(); row++) 
  for (int col=0; col<detectedBlobHsv.cols(); col++) 
    if (Imgproc.pointPolygonTest(largestContour2f, new Point(row, col), false) == -1) 
      detectedBlobHsv.put(row, col, new double[]0,0,0);
      pixelsRemoved++;
    
  

然后计算平均值

Scalar averageHsvColor= new Scalar(256);
Scalar sumHsvOfPixels = new Scalar(256);
sumHsvOfPixels = Core.sumElems(detectedBlobHsv); //This will now exclude pixels outside the contour
int numOfPixels = (  detectedBlobHsv.width()*detectedBlobHsv.height()  )-pixelsRemoved; 
for (int channel=0; channel<sumHsvOfPixels.val.length; channel++)  
  averageHsvColor = sumHsvOfPixels.val[channel]/numOfPixels; 


编辑 1:

在以下方法的最后,我创建了一个带有 MatOfPoints 列表的掩码,其中仅包含 最大 轮廓。当我把它写到 SDCard 时,我得到了

我不知道我在哪里搞砸了!

private Scalar detectColoredBlob() 
        //Highgui.imwrite("/mnt/sdcard/DCIM/rgbaFrame.jpg", rgbaFrame);// check
        rgbaFrame = Highgui.imread("/mnt/sdcard/DCIM/rgbaMat4Mask.bmp");


        //GIVING A UNIFORM VALUE OF 255 TO THE V CHANNEL OF EACH PIXEL (255 IS THE MAXIMUM VALUE OF V ALLOWED - Simulating a maximum light condition) 
        for (int firstCoordinate = 0; firstCoordinate < rgbaFrame.rows(); firstCoordinate++) 
            for (int secondCoordinate = 0; secondCoordinate < rgbaFrame.cols(); secondCoordinate++) 
                double[] pixelChannels = rgbaFrame.get(firstCoordinate, secondCoordinate); 
                pixelChannels[2] = 255;
                rgbaFrame.put(firstCoordinate, secondCoordinate, pixelChannels);
            
        


        Mat hsvImage = new Mat();
        Imgproc.cvtColor(rgbaFrame, hsvImage, Imgproc.COLOR_BGR2HSV);
        Highgui.imwrite("/mnt/sdcard/DCIM/hsvImage.bmp", hsvImage);// check


        Mat maskedImage = new Mat();
        Core.inRange(hsvImage, new Scalar(0, 100, 100), new Scalar(10, 255, 255), maskedImage);
        Highgui.imwrite("/mnt/sdcard/DCIM/maskedImage.bmp", maskedImage);// check


        // Mat dilatedMat = new Mat();
        // Imgproc.dilate(maskedImage, dilatedMat, new Mat());
        // Highgui.imwrite("/mnt/sdcard/DCIM/dilatedMat.jpg", dilatedMat);// check


        List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        Imgproc.findContours(maskedImage, contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE);
        //FINDING THE BIGGEST CONTOUR
        // \/ We will use only the largest contour. Other contours (any other possible blobs of this color range) will be ignored.
        MatOfPoint largestContour = contours.get(0);
        double largestContourArea = Imgproc.contourArea(largestContour);
        for (int i = 1; i < contours.size(); ++i) // NB Notice the prefix increment.
            MatOfPoint currentContour = contours.get(i);
            double currentContourArea = Imgproc.contourArea(currentContour);
            if (currentContourArea > largestContourArea) 
                largestContourArea = currentContourArea;
                largestContour = currentContour;
            
        


        Rect detectedBlobRoi = Imgproc.boundingRect(largestContour);
        Mat detectedBlobRgba = rgbaFrame.submat(detectedBlobRoi);
        Highgui.imwrite("/mnt/sdcard/DCIM/detectedBlobRgba.bmp", detectedBlobRgba);// check


        Mat detectedBlobHsv = new Mat();
        Imgproc.cvtColor(detectedBlobRgba, detectedBlobHsv, Imgproc.COLOR_BGR2HSV);
        Highgui.imwrite("/mnt/sdcard/DCIM/roiHsv.bmp", detectedBlobHsv);// check

        List<MatOfPoint> largestContourList = new ArrayList<>();
        largestContourList.add(largestContour);

        Mat roiWithMask = new Mat(detectedBlobHsv.rows(), detectedBlobHsv.cols(), CvType.CV_8UC3);
        roiWithMask.setTo(new Scalar(0,0,0));
        Imgproc.drawContours(roiWithMask, largestContourList, 0, new Scalar(0, 255, 255), -1);//TODO Using -1 instead of CV_FILLED.
        Highgui.imwrite("/mnt/sdcard/DCIM/roiWithMask.bmp", roiWithMask);// check


        // CALCULATING THE AVERAGE COLOR OF THE DETECTED BLOB
        // STEP 1:
        double [] averageHsvColor = new double[]0,0,0;
        int numOfPixels = 0;
        for (int firstCoordinate = 0; firstCoordinate < detectedBlobHsv.rows(); ++firstCoordinate) 
            for (int secondCoordinate = 0; secondCoordinate < detectedBlobHsv.cols(); ++secondCoordinate) 

                double hue = roiWithMask.get(firstCoordinate, secondCoordinate)[0];
                double saturation = roiWithMask.get(firstCoordinate, secondCoordinate)[1];
                double value = roiWithMask.get(firstCoordinate, secondCoordinate)[2];

                averageHsvColor[0] += hue;
                averageHsvColor[1] += saturation;
                averageHsvColor[2] += value;

                numOfPixels++;
            
        
        averageHsvColor[0] /= numOfPixels;
        averageHsvColor[1] /= numOfPixels;
        averageHsvColor[1] /= numOfPixels;



        return new Scalar(averageHsvColor);
    

编辑 2:

我修正了我的 3 通道蒙版并制作了单通道蒙版

Mat roiMask = new Mat(rgbaFrame.rows(), rgbaFrame.cols(), CvType.CV_8UC1);
        roiMask.setTo(new Scalar(0));
        Imgproc.drawContours(roiMask, largestContourList, 0, new Scalar(255), -1);

这导致了正确的roiMask

然后,在评论// CALCULATING THE AVERAGE COLOR OF THE DETECTED BLOB之前,我补充说:

Mat newImageWithRoi = new Mat(rgbaFrame.rows(), rgbaFrame.cols(), CvType.CV_8UC3);
newImageWithRoi.setTo(new Scalar(0, 0, 0));
rgbaFrame.copyTo(newImageWithRoi, roiMask);
Highgui.imwrite("/mnt/sdcard/DCIM/newImageWithRoi.bmp", newImageWithRoi);//check

这导致:

现在我又不知道该怎么办了。:s

【问题讨论】:

为什么需要这样做?一旦你有了轮廓,或者面具,你现在已经知道了哪些点在里面。 @Miki 请查看问题中的编辑,我在那里说明了。 几乎不推荐使用pointPolygonTest :D。查看答案... 【参考方案1】:

你不需要使用pointPolygonTest,因为你已经有了面具。

您可以简单地总结掩码上的值。类似于(无法测试):

// Initialize at 0!!!
Scalar averageHsvColor= new Scalar(0,0,0);

int numOfPixels = 0;

for(int r=0; r<detectedBlobHsv.height(); ++r)

    for(int c=0; c<detectedBlobHsv.width(); ++c)
    
        if( /* value of mask(r,c) > 0 */) 
        
            int H = // get H value of pixel at (r, c)
            int S = // get S value of pixel at (r, c)
            int V = // get V value of pixel at (r, c)

            // Sum values
            averageHsvColor[0] += H;
            averageHsvColor[1] += S;
            averageHsvColor[2] += V; 

            // Increment number of pixels inside mask
            numOfPixels ++;
        
    


// Compute average
averageHsvColor[0] /= numOfPixels ;
averageHsvColor[1] /= numOfPixels ;
averageHsvColor[2] /= numOfPixels ; 

【讨论】:

“因为你已经有了面具”if( /* value of mask(r,c) &gt; 0 */)... - 我们说的是哪个面具?你的意思是我应该通过Imgproc.drawContours(anEmptyMatForMask, aListHavingLargestContour, 0, new Scalar(255), -1); 来从detectedBlobHsv 创建一个掩码? 1) 是的! 2) 为什么你不能将 double 与 &gt; 0 进行比较? anEmptyMatForMask.get(r,c)[0] 应该得到正确的值 掩码只有一个通道 使用蒙版(单通道),您只能选择像素位置(r,c) 是否应该用于计算平均值。然后你在位置(r,c) 的 3 通道 hsv 图像上计算它

以上是关于为啥 OpenCV4Android 的 pointPolygonTest() 方法为每个像素返回-1?的主要内容,如果未能解决你的问题,请参考以下文章

Opencv4Android:如何与 C++ 一起使用

在 OpenCv4Android 中存储测试数据 [重复]

opencv4android 示例编译但不运行

openCV4Android平滑校正透视

OpenCV4Android背景建模(MOGMOG2)

Windows编译OpenCV4Android解决undefined reference to std错误