【问题标题】:OpenCV threshold with mask带掩码的 OpenCV 阈值
【发布时间】:2016-01-07 14:48:18
【问题描述】:

我正在尝试使用 OpenCV 的 cv::threshold 函数(更具体的 THRESH_OTSU),只是我想用蒙版(任何形状)来做,以便在计算过程中忽略外部(背景) .

图像是单通道(必须如此),下面的红色仅用于标记图像上的示例多边形。

我尝试使用 adaptiveThreshold,但有几个问题使其不适合我的情况。

【问题讨论】:

  • 使用阈值创建蒙版,然后在白色图像上使用带有否定蒙版的copyTo。
  • 谢谢,但那不行。 Otsu 二值化使用图像平均和其他一些参数。如果我只是屏蔽它并复制到,所有黑色像素仍将用于计算这些参数。
  • 啊好吧,我明白你的意思了

标签: c++ opencv polygon threshold


【解决方案1】:

一般情况下,您可以简单地使用cv::threshold 计算阈值,然后使用倒置的masksrc 图像复制到dst

// Apply cv::threshold on all image
thresh = cv::threshold(src, dst, thresh, maxval, type);

// Copy original image on inverted mask
src.copyTo(dst, ~mask);

但是,对于THRESH_OTSU,您还需要仅在蒙版图像上计算阈值。以下代码是thresh.cppstatic double getThreshVal_Otsu_8u(const Mat& _src)的修改版:

double otsu_8u_with_mask(const Mat1b src, const Mat1b& mask)
{
    const int N = 256;
    int M = 0;
    int i, j, h[N] = { 0 };
    for (i = 0; i < src.rows; i++)
    {
        const uchar* psrc = src.ptr(i);
        const uchar* pmask = mask.ptr(i);
        for (j = 0; j < src.cols; j++)
        {
            if (pmask[j])
            {
                h[psrc[j]]++;
                ++M;
            }
        }
    }

    double mu = 0, scale = 1. / (M);
    for (i = 0; i < N; i++)
        mu += i*(double)h[i];

    mu *= scale;
    double mu1 = 0, q1 = 0;
    double max_sigma = 0, max_val = 0;

    for (i = 0; i < N; i++)
    {
        double p_i, q2, mu2, sigma;

        p_i = h[i] * scale;
        mu1 *= q1;
        q1 += p_i;
        q2 = 1. - q1;

        if (std::min(q1, q2) < FLT_EPSILON || std::max(q1, q2) > 1. - FLT_EPSILON)
            continue;

        mu1 = (mu1 + i*p_i) / q1;
        mu2 = (mu - q1*mu1) / q2;
        sigma = q1*q2*(mu1 - mu2)*(mu1 - mu2);
        if (sigma > max_sigma)
        {
            max_sigma = sigma;
            max_val = i;
        }
    }
    return max_val;
}

然后您可以将所有内容包装在一个函数中,这里称为threshold_with_mask,它为您包装所有不同的情况。如果没有遮罩,或者遮罩是全白的,则使用cv::threshold。否则,请使用上述方法之一。请注意,此包装器仅适用于 CV_8UC1 图像(为简单起见,如果需要,您可以轻松地将其扩展为与其他类型一起使用),并接受所有 THRESH_XXX 组合作为原始 cv::threshold

double threshold_with_mask(Mat1b& src, Mat1b& dst, double thresh, double maxval, int type, const Mat1b& mask = Mat1b())
{
    if (mask.empty() || (mask.rows == src.rows && mask.cols == src.cols && countNonZero(mask) == src.rows * src.cols))
    {
        // If empty mask, or all-white mask, use cv::threshold
        thresh = cv::threshold(src, dst, thresh, maxval, type);
    }
    else
    {
        // Use mask
        bool use_otsu = (type & THRESH_OTSU) != 0;
        if (use_otsu)
        {
            // If OTSU, get thresh value on mask only
            thresh = otsu_8u_with_mask(src, mask);
            // Remove THRESH_OTSU from type
            type &= THRESH_MASK;
        }

        // Apply cv::threshold on all image
        thresh = cv::threshold(src, dst, thresh, maxval, type);

        // Copy original image on inverted mask
        src.copyTo(dst, ~mask);
    }
    return thresh;
}

以下是完整代码供参考:

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

// Modified from thresh.cpp
// static double getThreshVal_Otsu_8u(const Mat& _src)

double otsu_8u_with_mask(const Mat1b src, const Mat1b& mask)
{
    const int N = 256;
    int M = 0;
    int i, j, h[N] = { 0 };
    for (i = 0; i < src.rows; i++)
    {
        const uchar* psrc = src.ptr(i);
        const uchar* pmask = mask.ptr(i);
        for (j = 0; j < src.cols; j++)
        {
            if (pmask[j])
            {
                h[psrc[j]]++;
                ++M;
            }
        }
    }

    double mu = 0, scale = 1. / (M);
    for (i = 0; i < N; i++)
        mu += i*(double)h[i];

    mu *= scale;
    double mu1 = 0, q1 = 0;
    double max_sigma = 0, max_val = 0;

    for (i = 0; i < N; i++)
    {
        double p_i, q2, mu2, sigma;

        p_i = h[i] * scale;
        mu1 *= q1;
        q1 += p_i;
        q2 = 1. - q1;

        if (std::min(q1, q2) < FLT_EPSILON || std::max(q1, q2) > 1. - FLT_EPSILON)
            continue;

        mu1 = (mu1 + i*p_i) / q1;
        mu2 = (mu - q1*mu1) / q2;
        sigma = q1*q2*(mu1 - mu2)*(mu1 - mu2);
        if (sigma > max_sigma)
        {
            max_sigma = sigma;
            max_val = i;
        }
    }

    return max_val;
}

double threshold_with_mask(Mat1b& src, Mat1b& dst, double thresh, double maxval, int type, const Mat1b& mask = Mat1b())
{
    if (mask.empty() || (mask.rows == src.rows && mask.cols == src.cols && countNonZero(mask) == src.rows * src.cols))
    {
        // If empty mask, or all-white mask, use cv::threshold
        thresh = cv::threshold(src, dst, thresh, maxval, type);
    }
    else
    {
        // Use mask
        bool use_otsu = (type & THRESH_OTSU) != 0;
        if (use_otsu)
        {
            // If OTSU, get thresh value on mask only
            thresh = otsu_8u_with_mask(src, mask);
            // Remove THRESH_OTSU from type
            type &= THRESH_MASK;
        }

        // Apply cv::threshold on all image
        thresh = cv::threshold(src, dst, thresh, maxval, type);

        // Copy original image on inverted mask
        src.copyTo(dst, ~mask);
    }
    return thresh;
}


int main()
{
    // Load an image
    Mat1b img = imread("D:\\SO\\img\\nice.jpg", IMREAD_GRAYSCALE);

    // Apply OpenCV version
    Mat1b cvth;
    double cvth_value = threshold(img, cvth, 100, 255, THRESH_OTSU);

    // Create a binary mask
    Mat1b mask(img.rows, img.cols, uchar(0));
    rectangle(mask, Rect(100, 100, 200, 200), Scalar(255), CV_FILLED);

    // Apply threshold with a mask
    Mat1b th;
    double th_value = threshold_with_mask(img, th, 100, 255, THRESH_OTSU, mask);

    // Show results
    imshow("cv::threshod", cvth);
    imshow("threshold_with_balue", th);
    waitKey();

    return 0;
}

【讨论】:

  • 感谢您非常全面的回答。我想并希望有比这更简单的方法,现在我为自己不做繁重的工作而感到难过。
  • 如果您想加快代码速度,请执行并行 for 循环,例如: src.forEach([&](uchar& pixel, const int po[]) -> void {uchar maskPix = mask.at(po[0], po[1]); if (maskPix > 0) {h[pixel]++; ++M; }});
  • 使用 OpenCv 直方图算法 (Cv2.CalcHist) 也可以加快速度,而不是自己动手。这个函数处理一个掩码作为输入。
  • @user2959547 此代码只是对原始 otsu 代码的一个小修复。如果调用 calcHist 对加速很有用,您可能会在原始代码中找到它。但是你可以尝试和评估。请告诉我;)
  • 我用 cv::CalcHist(..) 替换了第一个 for 循环,效果很好。与标准 cv::threshold(..) 算法一样快。
猜你喜欢
  • 2019-10-25
  • 2016-08-02
  • 1970-01-01
  • 2014-05-07
  • 2020-03-07
  • 1970-01-01
  • 1970-01-01
  • 2017-01-22
  • 2021-05-20
相关资源
最近更新 更多