【问题标题】:Opencv increasing accuracy of thresholdOpencv提高阈值的准确性
【发布时间】:2023-04-05 03:58:02
【问题描述】:

我正在开发一个预计会使用 opencv 删除图像背景的应用程序,起初我尝试使用 grabcut 但它太慢并且结果并不总是准确,然后我尝试使用阈值,虽然结果还没有关闭抓取,它非常快并且看起来更好,所以我的代码首先查看图像色调并分析它的哪个部分出现更多,该部分被作为背景,问题有时是它的前景因为下面的背景是我的代码:

private Bitmap backGrndErase()
{

    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.skirt);
    Log.d(TAG, "bitmap: " + bitmap.getWidth() + "x" + bitmap.getHeight());


    bitmap = ResizeImage.getResizedBitmap(bitmap, calculatePercentage(40, bitmap.getWidth()), calculatePercentage(40, bitmap.getHeight()));

    Mat frame = new Mat();
    Utils.bitmapToMat(bitmap, frame);

    Mat hsvImg = new Mat();
    List<Mat> hsvPlanes = new ArrayList<>();
    Mat thresholdImg = new Mat();

    // int thresh_type = Imgproc.THRESH_BINARY_INV;
    //if (this.inverse.isSelected())
    int thresh_type = Imgproc.THRESH_BINARY;

    // threshold the image with the average hue value
    hsvImg.create(frame.size(), CvType.CV_8U);
    Imgproc.cvtColor(frame, hsvImg, Imgproc.COLOR_BGR2HSV);
    Core.split(hsvImg, hsvPlanes);

    // get the average hue value of the image
    double threshValue = this.getHistAverage(hsvImg, hsvPlanes.get(0));

    Imgproc.threshold(hsvPlanes.get(0), thresholdImg, threshValue, mThresholdValue, thresh_type);
   // Imgproc.adaptiveThreshold(hsvPlanes.get(0), thresholdImg, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 11, 2);

    Imgproc.blur(thresholdImg, thresholdImg, new Size(5, 5));

    // dilate to fill gaps, erode to smooth edges
    Imgproc.dilate(thresholdImg, thresholdImg, new Mat(), new Point(-1, -1), 1);
    Imgproc.erode(thresholdImg, thresholdImg, new Mat(), new Point(-1, -1), 3);

    Imgproc.threshold(thresholdImg, thresholdImg, threshValue, mThresholdValue, Imgproc.THRESH_BINARY);
    //Imgproc.adaptiveThreshold(thresholdImg, thresholdImg, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 11, 2);

    // create the new image
    Mat foreground = new Mat(frame.size(), CvType.CV_8UC3, new Scalar(255, 255, 255));
    frame.copyTo(foreground, thresholdImg);


    Utils.matToBitmap(foreground,bitmap);
    //return foreground;

    alreadyRun = true;
    return  bitmap;

}

负责色调的方法:

    private double getHistAverage(Mat hsvImg, Mat hueValues)
{
    // init
    double average = 0.0;
    Mat hist_hue = new Mat();
    // 0-180: range of Hue values
    MatOfInt histSize = new MatOfInt(180);
    List<Mat> hue = new ArrayList<>();
    hue.add(hueValues);

    // compute the histogram
    Imgproc.calcHist(hue, new MatOfInt(0), new Mat(), hist_hue, histSize, new MatOfFloat(0, 179));

    // get the average Hue value of the image
    // (sum(bin(h)*h))/(image-height*image-width)
    // -----------------
    // equivalent to get the hue of each pixel in the image, add them, and
    // divide for the image size (height and width)
    for (int h = 0; h < 180; h++)
    {
        // for each bin, get its value and multiply it for the corresponding
        // hue
        average += (hist_hue.get(h, 0)[0] * h);
    }

    // return the average hue of the image
    average = average / hsvImg.size().height / hsvImg.size().width;
    return average;
}

输入输出示例:[

输入图像 2 和输出:

输入图像 3 和输出:

【问题讨论】:

  • 衬衫示例中的边框可能是因为您使用的是 JPEG 图像。对于其他示例,这种在非平凡背景上的任务一点也不容易:D。
  • 我已经做了3周了,明天是第4周了还是没有什么进展
  • 我可以建议您采用这种方法:从原始图像的减色版本开始创建您的“背景蒙版”(例如,考虑 256 色图像)。这样,您将拥有更少的颜色桶,因此(可能)在颜色边缘附近有更高的容差
  • 你能帮我提供一个示例代码吗?
  • 看这里:stackoverflow.com/a/10179800/2760919这是减少图像颜色数量的Python实现(实际上,页面中有很多类似的方法),您需要调整其中一个到Java(但执行起来可能真的很慢)

标签: android opencv


【解决方案1】:

我会再试一次 Grabcut,它是目前最好的分割方法之一。这是我得到的result

cv::Mat bgModel,fgModel; // the models (internally used)
cv::grabCut(image,// input image
            object_mask,// segmentation result
            rectang,// rectangle containing foreground
            bgModel,fgModel, // models
            5,// number of iterations
            cv::GC_INIT_WITH_RECT); // use rectangle
// Get the pixels marked as likely foreground
cv::compare(object_mask,cv::GC_PR_FGD,object_mask,cv::CMP_EQ);
cv::threshold(object_mask, object_mask, 0,255, CV_THRESH_BINARY);  //ensure the mask is binary

Grabcut 的唯一问题是您必须提供一个包含您要提取的对象的矩形作为输入。除此之外,它工作得很好。

【讨论】:

  • 老实说这很令人印象深刻,我尝试了grabcut,它有点慢,但为了获得如此好的效果,我可以再次尝试使用它,但即使我在使用它时,它也会标记正确图像的低角就像你的结果一样,我怎样才能防止这种情况发生
  • 很难自动做到这一点,因为算法会反复选择前景和背景。尽管您可以为算法提供一个基本事实,例如 GC_BGD 或 GC_PR_FGD 值。更多信息在这里docs.opencv.org/2.4/modules/imgproc/doc/…和这里docs.opencv.org/master/d8/d83/…
  • 加速Grabcut的一个技巧是先缩小图像,应用GC并获得分割矩阵,然后将矩阵放大到原始大小并乘以原始图像。虽然你可能会失去质量
【解决方案2】:

确实,正如其他人所说,您不太可能仅通过色调阈值获得好的结果。您可以使用类似于GrabCut 的内容,但速度更快。

在后台,GrabCut 计算前景和背景直方图,然后根据这些直方图计算每个像素为 FG/BG 的概率,然后使用 graph cut 优化生成的概率图以获得分割。

最后一步是最昂贵的,根据应用程序可能会被忽略。相反,您可以将阈值应用于概率图以获得分割。它可能(并且将会)比 GrabCut 更糟,但会比您当前的方法更好。

这种方法需要考虑一些要点。直方图模型的选择在这里非常重要。您可以在某些空间(如 YUV 或 HSV)中考虑 2 个通道,考虑 3 个 RGB 通道,或者考虑 2 个归一化 RGB 通道。您还必须为这些直方图选择合适的 bin 大小。太小的 bin 会导致“过度训练”,而太大会降低精度。简而言之,它们之间的权衡是一个单独讨论的主题 - 我建议使用 RGB,每个通道有 64 个 bin,然后看看哪些更改对您的数据更好。

此外,如果您使用插值来获取 bin 之间的值,您可以获得更好的粗分级结果。过去我使用过三线性插值,与根本没有插值相比,它有点好。

但请记住,在没有事先了解对象形状的情况下,无论是使用 GrabCut、阈值还是这种方法,都无法保证您的分割是正确的。

【讨论】:

    【解决方案3】:

    您找到平均色调的方法是错误的!您很可能知道,色调以角度表示,取值在 [0,360] 范围内。因此,具有色相 360 的像素与具有色相 0 的像素具有相同的颜色(两者都是纯红色)。同理,色相 350 的像素实际上比色相 300 的像素更接近色相 10 的像素。

    对于 opencv,cvtColor 函数实际上将计算出的色调值除以 2 以适应 8 位整数。因此,在opencv中,色调值在180之后换行。现在,考虑我们有两个红色(ish)像素,色调分别为10和170。如果我们取它们的平均值,我们将得到90——纯青色的色调,与红色完全相反——这不是我们想要的值。

    因此,要正确求出平均色调,首先需要在 RGB 颜色空间中找到平均像素值,然后根据该 RGB 值计算色调。您可以创建具有平均 RGB 像素的 1x1 矩阵并将其转换为 HSV/HSL。

    按照相同的推理,将threshold 应用于色调图像并不能完美地工作。它不考虑色调值的包装。

    如果我理解正确,您希望找到以 similar 色调为背景的像素。假设我们知道背景的颜色,我会在 RGB 空间中进行分割。我会介绍一些tolerance 变量。我将使用背景像素值作为中心,将此容差作为半径,从而在 RGB 颜色空间中定义一个球体。现在,rest 正在检查每个像素值,如果它落在这个球体内,则归类为背景;否则,将其视为前景像素。

    【讨论】:

      猜你喜欢
      • 2016-04-02
      • 1970-01-01
      • 2014-12-23
      • 2019-10-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-13
      相关资源
      最近更新 更多