C++ 带掩码的OpenCV阈值

C++ 带掩码的OpenCV阈值,c++,opencv,polygon,threshold,C++,Opencv,Polygon,Threshold,我试图使用OpenCV的cv::threshold函数(更具体的THRESH_OTSU),只是我想用一个遮罩(任何形状)来做,这样在计算过程中外部(背景)就被忽略了 图像是单通道(必须如此),下面的红色波纹管仅用于标记图像上的示例多边形 我尝试使用自适应阈值,但在我的案例中有几个问题使它不合适 通常,您只需使用cv::threshold计算阈值,然后使用反转的掩码在dst上复制src图像即可 // Apply cv::threshold on all image thresh = cv::thr

我试图使用OpenCV的
cv::threshold
函数(更具体的
THRESH_OTSU
),只是我想用一个遮罩(任何形状)来做,这样在计算过程中外部(背景)就被忽略了

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

我尝试使用
自适应阈值
,但在我的案例中有几个问题使它不合适


通常,您只需使用
cv::threshold
计算阈值,然后使用反转的
掩码在
dst
上复制
src
图像即可

// 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.cpp
static double getThreshVal\u Otsu\u 8u(const Mat&\u 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;
}
以下是完整的代码供参考:

#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;
}
#包括
#包括
使用名称空间std;
使用名称空间cv;
//从thresh.cpp修改
//静态双getThreshVal\u Otsu\u 8u(常数矩阵和src)
双otsu_8u_,带遮罩(常数Mat1b src、常数Mat1b和遮罩)
{
常数int N=256;
int M=0;
int i,j,h[N]={0};
对于(i=0;i1.-FLT_ε)
继续;
mu1=(mu1+i*p_i)/q1;
mu2=(mu-q1*mu1)/q2;
西格玛=q1*q2*(mu1-mu2)*(mu1-mu2);
如果(西格玛>最大西格玛)
{
最大西格玛=西格玛;
max_val=i;
}
}
返回最大值;
}
带屏蔽的双阈值屏蔽(Mat1b&src、Mat1b&dst、双阈值、双最大值、int类型、常量Mat1b&mask=Mat1b())
{
if(mask.empty()| |(mask.rows==src.rows&&mask.cols==src.cols&&countNonZero(mask)==src.rows*src.cols))
{
//如果为空遮罩或全白遮罩,请使用cv::threshold
thresh=cv::threshold(src、dst、thresh、maxval、type);
}
其他的
{
//使用面具
bool use_otsu=(类型和阈值_otsu)!=0;
如果(使用大津)
{
//如果是大津,则仅获取遮罩上的阈值
thresh=otsu_8u_,带_掩模(src,掩模);
//从类型中删除THRESH_OTSU
类型&=阈值遮罩;
}
//对所有图像应用cv::阈值
thresh=cv::threshold(src、dst、thresh、maxval、type);
//在反转遮罩上复制原始图像
src.copyTo(dst,~mask);
}
回程脱粒;
}
int main()
{
//加载图像
Mat1b img=imread(“D:\\SO\\img\\nice.jpg”,imread\u灰度);
//应用OpenCV版本
Mat1b-cvth;
双cvth_值=阈值(img,cvth,100255,THRESH_OTSU);
//创建一个二进制掩码
Mat1b掩模(img.rows,img.cols,uchar(0));
矩形(遮罩、矩形(100100200200)、标量(255)、CV_填充);
//使用遮罩应用阈值
Mat1b-th;
双th_值=带遮罩的阈值(img、th、100、255、阈值大津、遮罩);
//显示结果
imshow(“cv::Threshold”,cvth);
imshow(“带平衡的阈值”,th);
waitKey();
返回0;
}

使用阈值创建遮罩,然后在白色图像上使用copyTo和否定遮罩。谢谢,但这不行。大津二值化使用图像平均和其他一些方法。如果我只是屏蔽它并复制到,所有黑色像素仍将用于计算这些参数。啊,好的,我现在明白你的意思了。谢谢你非常全面的回答。我以为有一个比这更简单的方法,现在我为自己没有完成繁重的工作而感到难过。如果你想把代码的速度提高一点,可以做一个类似这样的并行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(..)算法一样快。
#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;
}