带掩码的 OpenCV 阈值
OpenCV threshold with mask
我正在尝试使用 OpenCV 的 cv::threshold
函数(更具体 THRESH_OTSU
),只是我想用遮罩(任何形状)来做,所以外面 (背景)在计算过程中被忽略。
图像是单通道的(这是必须的),下面的红色仅用于标记图像上的示例多边形。
我尝试使用 adaptiveThreshold
,但有几个问题使它不适合我的情况。
一般来说,您可以使用 cv::threshold
简单地计算阈值,然后使用反转的 mask
.[=26= 将 src
图像复制到 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.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;
}
然后您可以将所有内容包装在一个函数中,这里称为 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;
}
我正在尝试使用 OpenCV 的 cv::threshold
函数(更具体 THRESH_OTSU
),只是我想用遮罩(任何形状)来做,所以外面 (背景)在计算过程中被忽略。
图像是单通道的(这是必须的),下面的红色仅用于标记图像上的示例多边形。
我尝试使用 adaptiveThreshold
,但有几个问题使它不适合我的情况。
一般来说,您可以使用 cv::threshold
简单地计算阈值,然后使用反转的 mask
.[=26= 将 src
图像复制到 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.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;
}
然后您可以将所有内容包装在一个函数中,这里称为 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;
}