求取图像像素最大值、最小值、平均值、均方差等众多用于统计的函数。
void minMaxLoc(InputArray src, // 必须是单通道矩阵
CV_OUT double* minVal, // 输出最大值
CV_OUT double* maxVal = 0, // 输出最小值
CV_OUT Point* minLoc = 0, // 最大值坐标
CV_OUT Point* maxLoc = 0, // 最小值坐标
InputArray mask = noArray());
坐标类型:
// 类模板
template<typename _Tp> class Point_
{
public:
// ...
_Tp x; //!< x coordinate of the point
_Tp y; //!< y coordinate of the point
};
// 模板实例化
typedef Point_<int> Point2i;
typedef Point_<int64> Point2l;
typedef Point_<float> Point2f;
typedef Point_<double> Point2d;
// 整型坐标
typedef Point2i Point;
由于图像的像素坐标轴以左上角为坐标原点,水平方向为x轴,垂直方向为y轴,因此 Point(x, y)
对应于图像的行和列表示为 Point(列数,行数)
。
minMaxLoc函数的功能时寻找图像中特定区域内的最值,函数第一个参数是单通道矩阵,如果是一个多通道矩阵需要用到reshape函数将多通道变为单通道:
Mat cv::Mat::reshape(int cn, // 转换后矩阵的通道数
int rows=0) const; // 转换后矩阵的行数,如果为0表示转换后行数与转换前行数相同
minMaxLoc函数如果不找最大值,可以将maxVal, maxLoc参数设置为NULL。
minMaxLoc函数的最后一个参数是寻找最值的掩码矩阵,用于标记寻找上述4个值的范围,参数默认值为noArray(),表示寻找范围是矩阵中的所有数据。
#include
#include
using namespace cv;
using namespace std;
int main()
{
cout << "OpenCV Version: " << CV_VERSION << endl;
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);
float a[12] = { 1, 2, 3, 4, 5, 10, 6, 7, 8, 9, 10, 0 };
Mat img = Mat(3, 4, CV_32FC1, a); //单通道矩阵
Mat imgs = Mat(2, 3, CV_32FC2, a); //多通道矩阵
double minVal, maxVal; //用于存放矩阵中的最大值和最小值
Point minIdx, maxIdx; //用于存放矩阵中的最大值和最小值在矩阵中的位置
/*寻找单通道矩阵中的最值*/
minMaxLoc(img, &minVal, &maxVal, &minIdx, &maxIdx);
cout << "img中最大值是:" << maxVal << " " << "在矩阵中的位置:" << maxIdx << endl;
cout << "img中最小值是:" << minVal << " " << "在矩阵中的位置:" << minIdx << endl;
/*寻找多通道矩阵中的最值*/
Mat imgs_re = imgs.reshape(1, 4); //将多通道矩阵变成单通道矩阵
minMaxLoc(imgs_re, &minVal, &maxVal, &minIdx, &maxIdx);
cout << "imgs中最大值是:" << maxVal << " " << "在矩阵中的位置:" << maxIdx << endl;
cout << "imgs中最小值是:" << minVal << " " << "在矩阵中的位置:" << minIdx << endl;
int k = waitKey(0); // Wait for a keystroke in the window
return 0;
}
/*
img中最大值是:10 在矩阵中的位置:[1, 1]
img中最小值是:0 在矩阵中的位置:[3, 2]
imgs中最大值是:10 在矩阵中的位置:[2, 1]
imgs中最小值是:0 在矩阵中的位置:[2, 3]
*/
注意上面的图像矩阵可以在VS2022种的Image Watch插件查看矩阵的值。此外,注意输出结果,列数在前,行数在后。
图像的均值表示图像整体的亮暗程度,图像的平均值越大,整体越亮。标准差表示图像中明暗变化的对比程度,标准差越大,表示图像中明暗变化越明显。OpenCV提供了mean()函数用于计算图像的平均值,提供了meanStdDev()函数用于同时计算图像的平均值和标准差。
Scalar cv::mean(InputArray src, // 待求平均值的图像矩阵
InputArray mask = noArray()); // 掩码用于标记求取哪些区域的平均值
/*
该函数用于求取图像矩阵的每个通道的平均值,src可以是1-4通道,返回值是一个cv::Scalar类型的变量,返回值有4位,分别表示输入图像的4通道的平均值,如果输入图像只有一个通道,那么返回值的后3位都为0。通过cv::Scalar[n]查看第n个通道的平均值。
*/
void cv::meanStdDev(InputArray src, // 待求平均值的图像矩阵
OutputArray mean, // 每个通道的均值,Mat类型
OutputArray stddev,// 每个通道的标准差,Mat类型
InputArray mask=noArray()); // 掩码,用于标记求取哪些区域的平均值和标准差
查看mean返回值:Scalar类
typedef Scalar_<double> Scalar;
// 类模板,继承Vec_
template<typename _Tp> class Scalar_ : public Vec<_Tp, 4>
{
public:
// ...
};
// 类模板,继承Matx_,默认为1列
template<typename _Tp, int cn> class Vec : public Matx<_Tp, cn, 1>
{
public:
// ...
}
// 类模板,m行n列的数组
template<typename _Tp, int m, int n> class Matx
{
public:
_Tp val[m*n]; //< matrix elements
};
#include
#include
using namespace cv;
using namespace std;
int main()
{
cout << "OpenCV Version: " << CV_VERSION << endl;
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);
float a[12] = { 1, 2, 3, 4, 5, 10, 6, 7, 8, 9, 10, 0 };
Mat img = Mat(3, 4, CV_32FC1, a); //单通道矩阵
Mat imgs = Mat(2, 3, CV_32FC2, a); //多通道矩阵
cout << "/* 用mean求取图像的均值 */" << endl;
Scalar myMean;
myMean = mean(imgs);
cout << "imgs均值=" << myMean << endl;
cout << "imgs第一个通道的均值=" << myMean[0] << " "
<< "imgs第二个通道的均值=" << myMean[1] << endl << endl;
cout << "/* 用meanStdDev同时求取图像的均值和标准差 */" << endl;
Mat myMeanMat, myStddevMat;
meanStdDev(img, myMeanMat, myStddevMat);
cout << "img均值=" << myMeanMat << endl;
cout << "img标准差=" << myStddevMat << endl << endl;
meanStdDev(imgs, myMeanMat, myStddevMat);
cout << "imgs均值=" << myMeanMat << endl;
cout << "imgs标准差=" << myStddevMat << endl;
int k = waitKey(0); // Wait for a keystroke in the window
return 0;
}
/*
/* 用mean求取图像的均值 */
imgs均值=[5.5, 5.33333, 0, 0]
imgs第一个通道的均值=5.5 imgs第二个通道的均值=5.33333
/* 用meanStdDev同时求取图像的均值和标准差 */
img均值=[5.416666666666666]
img标准差=[3.32812092461931]
imgs均值=[5.5;
5.333333333333333]
imgs标准差=[2.986078811194819;
3.636237371545238]
*/
前面介绍的计算最值、平均值等操作都是对一幅图像进行处理,接下来将介绍两幅图间像素的相关操作。
OpenCV提供了求取两幅图像每一个像素较大或者较小灰度值的max()、min()函数,这两个函数分别比较两幅图像种每一个元素灰度值的大小,保留较大(较小)的灰度值。
void max(const Mat& src1, // 第一个图像矩阵,任意通道数
const Mat& src2, // 第二个图像矩阵,尺寸、数据类型、通道数与src1一直
Mat& dst); // 保留对应位置较大灰度之后的图像矩阵
void min(const Mat& src1,
const Mat& src2,
Mat& dst); // 保留对应位置较小灰度之后的图像矩阵
#include
#include
using namespace cv;
using namespace std;
int main()
{
cout << "OpenCV Version: " << CV_VERSION << endl;
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);
float a[12] = { 1, 2, 3.3, 4, 5, 9, 5, 7, 8.2, 9, 10, 2 };
float b[12] = { 1, 2.2, 3, 1, 3, 10, 6, 7, 8, 9.3, 10, 1 };
Mat imga = Mat(3, 4, CV_32FC1, a);
Mat imgb = Mat(3, 4, CV_32FC1, b);
Mat imgas = Mat(2, 3, CV_32FC2, a);
Mat imgbs = Mat(2, 3, CV_32FC2, b);
//对两个单通道矩阵进行比较运算
Mat myMax, myMin;
max(imga, imgb, myMax);
min(imga, imgb, myMin);
//对两个多通道矩阵进行比较运算
Mat myMaxs, myMins;
max(imgas, imgbs, myMaxs);
min(imgas, imgbs, myMins);
//对两张彩色图像进行比较运算
Mat img0 = imread("len.png");
Mat img1 = imread("noobcv.jpg");
if (img0.empty() || img1.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat comMin, comMax;
max(img0, img1, comMax);
min(img0, img1, comMin);
imshow("comMin", comMin);
imshow("comMax", comMax);
//与掩模进行比较运算
Mat src1 = Mat::zeros(Size(512, 512), CV_8UC3);
Rect rect(100, 100, 300, 300);
src1(rect) = Scalar(255, 255, 255); //生成一个低通300*300的掩模
Mat comsrc1, comsrc2;
min(img0, src1, comsrc1);
imshow("comsrc1", comsrc1);
Mat src2 = Mat(512, 512, CV_8UC3, Scalar(0, 0, 255)); //生成一个显示红色通道的低通掩模
min(img0, src2, comsrc2);
imshow("comsrc2", comsrc2);
//对两张灰度图像进行比较运算
Mat img0G, img1G, comMinG, comMaxG;
cvtColor(img0, img0G, COLOR_BGR2GRAY);
cvtColor(img1, img1G, COLOR_BGR2GRAY);
max(img0G, img1G, comMaxG);
min(img0G, img1G, comMinG);
imshow("comMinG", comMinG);
imshow("comMaxG", comMaxG);
int k = waitKey(0); // Wait for a keystroke in the window
return 0;
}
值得说的是上面代码中掩模的使用:
Mat src1 = Mat::zeros(Size(512, 512), CV_8UC3);
Rect rect(100, 100, 300, 300);
src1(rect) = Scalar(255, 255, 255); //生成一个低通300*300的掩模
Mat comsrc1, comsrc2;
min(img0, src1, comsrc1);
imshow("comsrc1", comsrc1);
生成一个512x512大小的3通道图像,在左上角(100,100)到右下角(300,300)正方形区域内,设置值为Scalar(255, 255, 255)白色。然后min取lena图像和掩膜图像的最小值,可以把正方形区域内的lena图像截取出来。
OpenCV提供了针对两个图像像素间的与、或、异或以及单个图像像素的非运算。如果像素取值只有0和1,那么位运算正好对应。但是CV_8U类型的图像像素值从0取到255,此时的逻辑运算需要将像素值转换成二进制数后再进行。
void bitwise_not(InputArray src,
OutputArray dst,
InputArray mask = noArray());
void bitwise_and(InputArray src1,
InputArray src2,
OutputArray dst,
InputArray mask = noArray());
void bitwise_or(InputArray src1,
InputArray src2,
OutputArray dst,
InputArray mask = noArray());
void bitwise_xor(InputArray src1,
InputArray src2,
OutputArray dst,
InputArray mask = noArray());
/*
src1:第一个图像矩阵,可以是多通道数据
src2:第二个图像矩阵,尺寸、通道数、数据类型与src1一致
dst:逻辑运算输出结果
mask:用于设置图像或矩阵中逻辑运算范围
*/
#include
#include
using namespace cv;
using namespace std;
int main()
{
cout << "OpenCV Version: " << CV_VERSION << endl;
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);
Mat img = imread("lena.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
//创建两个黑白图像
Mat img0 = Mat::zeros(200, 200, CV_8UC1);
Mat img1 = Mat::zeros(200, 200, CV_8UC1);
Rect rect0(50, 50, 100, 100);
img0(rect0) = Scalar(255);
Rect rect1(100, 100, 100, 100);
img1(rect1) = Scalar(255);
imshow("img0", img0);
imshow("img1", img1);
//进行逻辑运算
Mat myAnd, myOr, myXor, myNot, imgNot;
bitwise_not(img0, myNot);
bitwise_and(img0, img1, myAnd);
bitwise_or(img0, img1, myOr);
bitwise_xor(img0, img1, myXor);
bitwise_not(img, imgNot);
imshow("myAnd", myAnd);
imshow("myOr", myOr);
imshow("myXor", myXor);
imshow("myNot", myNot);
imshow("img", img);
imshow("imgNot", imgNot);
int k = waitKey(0); // Wait for a keystroke in the window
return 0;
}
黑白图像的像素灰度值无论在什么数据类型中都只有最大值和最小值两种取值,因此称为二值图像。二值图像的色彩种类少可以进行高度的压缩节省存储空间,将非二值图像经过计算变成二值图像的过程称为图像的二值化。在OpenCV种提供了threshold()和adaptiveThreshold()函数用于实现图像的二值化。
double threshold(InputArray src, // 待二值化的图像,只能是CV_8U和CV_32F两种数据类型,通道要求与type参数有关
OutputArray dst, // 二值化后的图像
double thresh, // 二值化阈值
double maxval, // 二值化过程的最大值,只在THRESH_BINARY和THRESH_BINARY_INV使用
int type );
type:
标记参数 | 简记 | 作用 |
---|---|---|
THRESH_BINARY | 0 | 灰度值大于阈值的为最大值maxval,其他值为0 |
THRESH_BINARY_INV | 1 | 灰度值大于阈值的为0,其他值为最大值maxval |
THRESH_TRUNC | 2 | 灰度值大于阈值的为阈值thresh,其他值不变 |
THRESH_TOZERO | 3 | 灰度值大于阈值的不变,其他值为0 |
THRESH_TOZERO_INV | 4 | 灰度值大于阈值的为0,其他值不变 |
THRESH_OTSU | 8 | 大津法自动求全局阈值 |
THRESH_TRIANGLE | 16 | 三角形法自动求全局阈值 |
THRESH_OTSU和THRESH_TRIANGLE标志是获取阈值的方法,并不是阈值比较方法的标志,这两个标志可以与前5个标志一起使用。前五种方法都需要认为地设置阈值,大津法(OTSU)和三角形法(TRIANGLE)结合图像灰度值分布特性获取二值化的阈值,并将阈值以函数返回值的形式给出。这两个标志只支持CV_8UC1类型的图像。
threshold函数全局只使用一个阈值,在实际情况中,由于光照不均匀以及阴影的存在,全局只有一个阈值会使得在阴影处的白色区域也会被函数二值化为黑色,因此adaptiveThreshold函数提供了两种局部自适应阈值的二值化方法:
void adaptiveThreshold(InputArray src, // 待二值化图像,只能是CV_8UC1类型
OutputArray dst, // 二值化后图像
double maxValue, // 二值化的最大值
int adaptiveMethod,// 自适应确定阈值方法,均值法ADAPTIVE_THRESH_MEAN_C,高斯法ADAPTIVE_THRESH_GAUSSIAN_C
int thresholdType, // 图像二值化方法,只能是THRESH_BINARY和THRESH_BINARY_INV
int blockSize, // 自适应确定阈值的像素领域大小,一般为3,5,7的奇数
double C ); // 从平均值或者加权平均值种减去的常数
该函数将灰度图转换为二值图,通过均值法和高斯法自适应计算blocksize * blocksize邻域内的阈值,之后进行二值化。
#include
#include
using namespace cv;
using namespace std;
int main()
{
cout << "OpenCV Version: " << CV_VERSION << endl;
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);
Mat img = imread("lena.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
Mat img_B, img_B_V, gray_B, gray_B_V, gray_T, gray_T_V, gray_TRUNC;
//彩色图像二值化
threshold(img, img_B, 125, 255, THRESH_BINARY);
threshold(img, img_B_V, 125, 255, THRESH_BINARY_INV);
imshow("img_B", img_B);
imshow("img_B_V", img_B_V);
//灰度图BINARY二值化
threshold(gray, gray_B, 125, 255, THRESH_BINARY);
threshold(gray, gray_B_V, 125, 255, THRESH_BINARY_INV);
imshow("gray_B", gray_B);
imshow("gray_B_V", gray_B_V);
//灰度图像TOZERO变换
threshold(gray, gray_T, 125, 255, THRESH_TOZERO);
threshold(gray, gray_T_V, 125, 255, THRESH_TOZERO_INV);
imshow("gray_T", gray_T);
imshow("gray_T_V", gray_T_V);
//灰度图像TRUNC变换
threshold(gray, gray_TRUNC, 125, 255, THRESH_TRUNC);
imshow("gray_TRUNC", gray_TRUNC);
//灰度图像大津法和三角形法二值化
Mat img_Thr = imread("threshold.png", IMREAD_GRAYSCALE);
Mat img_Thr_O, img_Thr_T;
threshold(img_Thr, img_Thr_O, 100, 255, THRESH_BINARY | THRESH_OTSU);
threshold(img_Thr, img_Thr_T, 125, 255, THRESH_BINARY | THRESH_TRIANGLE);
imshow("img_Thr", img_Thr);
imshow("img_Thr_O", img_Thr_O);
imshow("img_Thr_T", img_Thr_T);
//灰度图像自适应二值化
Mat adaptive_mean, adaptive_gauss;
adaptiveThreshold(img_Thr, adaptive_mean, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 55, 0);
adaptiveThreshold(img_Thr, adaptive_gauss, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 55, 0);
imshow("adaptive_mean", adaptive_mean);
imshow("adaptive_gauss", adaptive_gauss);
int k = waitKey(0); // Wait for a keystroke in the window
return 0;
}
前面介绍的阈值比较方法中都只有一个阈值,如果需要与多个阈值进行比较,就需要用到显示查找表(Look-Up-Table,LUT)。LUT是一个像素灰度值的映射表。
void LUT(InputArray src, // 输入图像矩阵,数据类型只能是CV_8U
InputArray lut, // 256个像素灰度值的查找表,单通道或者与src通道数相同
OutputArray dst); // 输出图像矩阵
如果lut是单通道,src中的每个通道都按照lut进行映射。如果lut是多通道,则src中的第i个通道按照lut的第i个通道进行映射。
#include
#include
using namespace cv;
using namespace std;
int main()
{
cout << "OpenCV Version: " << CV_VERSION << endl;
utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);
//LUT查找表第一层
uchar lutFirst[256];
for (int i = 0; i < 256; i++)
{
if (i <= 100)
lutFirst[i] = 0;
if (i > 100 && i <= 200)
lutFirst[i] = 100;
if (i > 200)
lutFirst[i] = 255;
}
Mat lutOne(1, 256, CV_8UC1, lutFirst);
//LUT查找表第二层
uchar lutSecond[256];
for (int i = 0; i < 256; i++)
{
if (i <= 100)
lutSecond[i] = 0;
if (i > 100 && i <= 150)
lutSecond[i] = 100;
if (i > 150 && i <= 200)
lutSecond[i] = 150;
if (i > 200)
lutSecond[i] = 255;
}
Mat lutTwo(1, 256, CV_8UC1, lutSecond);
//LUT查找表第三层
uchar lutThird[256];
for (int i = 0; i < 256; i++)
{
if (i <= 100)
lutThird[i] = 100;
if (i > 100 && i <= 200)
lutThird[i] = 200;
if (i > 200)
lutThird[i] = 255;
}
Mat lutThree(1, 256, CV_8UC1, lutThird);
//拥有三通道的LUT查找表矩阵
vector<Mat> mergeMats;
mergeMats.push_back(lutOne);
mergeMats.push_back(lutTwo);
mergeMats.push_back(lutThree);
Mat LutTree;
merge(mergeMats, LutTree);
//计算图像的查找表
Mat img = imread("lena.png");
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat gray, out0, out1, out2;
cvtColor(img, gray, COLOR_BGR2GRAY);
LUT(gray, lutOne, out0);
LUT(img, lutOne, out1);
LUT(img, LutTree, out2);
imshow("out0", out0);
imshow("out1", out1);
imshow("out2", out2);
int k = waitKey(0); // Wait for a keystroke in the window
return 0;
}