1.基础知识
(1)平均值
(2)平均偏差
平均偏差是数列中各项数值与其算术平均数的离差绝对值的算术平均数。平均偏差是用来测定数列中各项数值对其平均数离势程度的一种尺度。平均偏差可分为简单平均偏差和加权平均偏差。
加权平均偏差
在分组情况下,平均偏差的计算公式为:
为什么要取离差的绝对值?因离差和为零,离差的平均数不能将离差和除以离差的个数求得,而必须将离差取绝对数来消除正负号。
平均偏差是反映各标志值与算术平均数之间的平均差异。平均偏差越大,表明各标志值与算术平均数的差异程度越大,该算术平均数的代表性就越小;平均偏差越小,表明各标志值与算术平均数的差异程度越小,该算术平均数的代表性就越大。(下面的亮度判断利用这一特点,当平均偏差小于一定阈值时,才说明算数平均数有代表意义,可以进行进一步判断。)
2.参考博客:
https://blog.csdn.net/kklots/article/details/12720359
这篇博客很多转载,但是作者注释的太不详细了,很多人转载,不知道他们是否真的明白了其中的思路。我不想就这样直接用,还是觉得思考清楚了再用比较好。
作者思路:
首先,计算均值,注意此处的均值不是指图像灰度的均值,指的是(图像灰度值-128)的均值。
da = ∑(xi- 128) / N N = src.rows * src.cols i是指扫描图像时每个像素点索引
其次,计算平均差,利用灰度直方图获取每个灰度值对应的像素个数,以像素个数为权重,利用加权平均偏差的计算公式得平均偏差。
Ma = ∑|(xi - 128) - da| * Hist[i] / ∑Hist[i] i是指【0,256)
然后,根据平均差的值进行判断,此处需要给出一个阈值,作者给的阈值是abs(da)
如果 Ma < abs(da),图像可能存在亮度异常,进一步利用da判断偏暗还是偏亮,如果da>0,说明大多数像素值都是大于128,图像偏亮;如果da<0,说明大多数像素值都是小于128,图像偏暗。
我认为此处的阈值没法给一个准确的值,作者取得这个值可能是经过一些测试设定的,这个阈值不是一个定值,可以根据图像的情况变化,有一定的合理性。
3.基于OpenCV的实现
理解了思路,自己写出来也就很容易了。
//参考博客:https://blog.csdn.net/kklots/article/details/12720359
#include
using namespace std;
using namespace cv;
void BrightnessDetect(const cv::Mat &src, int &Refer, float &Mean, float &MeanDev);
int main()
{
cv::Mat src = cv::imread("C:\\Users\\dell\\Desktop\\2图像太亮.jpg", 1);
if (src.empty())
{
cout << "输入图像为空" << endl;
return -1;
}
int Refer = 128;
float MeanDev = 0.0;
float Mean = 0.0;
BrightnessDetect(src, Refer, Mean, MeanDev);
cout << "平均值: " << Mean << endl;
cout << "平均偏差: " << MeanDev << endl;
cout << "判断结果: " << endl;
//通过平均偏差的大小来判断是否异常
if (MeanDev < abs(Mean)) //平均偏差小于阈值
{
if (Mean > 0) //均值大于参考值(128),说明图像太亮
cout << "图像过亮!" << endl;
else if (Mean < 0) //均值大于参考值(128),说明图像太暗
cout << "图像过暗!" << endl;
else
cout << "图像亮度正常!" << endl;
}
else
cout << "图像亮度正常!" << endl;
waitKey(0);
return 0;
}
void BrightnessDetect(const cv::Mat &src, int &Refer, float &Mean, float &MeanDev)
{
CV_Assert(!src.empty());
cv::Mat gray; //转换为灰度图
if (3 == src.channels())
cv::cvtColor(src, gray, CV_BGR2GRAY);
else
gray = src.clone();
//计算整幅图像均值,此处利用函数Scalar mean(InputArray src, InputArray mask=noArray())
cv::Scalar meanGrayS = cv::mean(gray);
float meanGray = meanGrayS[0];
//认为Refer(128)为图像亮度正常值,进一步计算出图像的偏移均值(自己取的,不太好表达)
Mean = meanGray - Refer;
//计算图像的偏移均值的平均偏差 MD = ∑|x - Mean(x)| / n
int nRows = gray.rows;
int nCols = gray.cols;
int sumTemp = 0;
for (int j = 0; j < nRows; j++)
{
uchar *pGray = gray.ptr(j);
for (int i = 0; i < nCols; i++)
{
int diffTemp = pGray[i] - 128;
int absTemp = abs(diffTemp - Mean);
sumTemp += absTemp;
}
}
MeanDev = ((float)sumTemp / (nRows * nCols));
}
4.测试:
5.扩展
(1)做项目的时候,我们关注的往往只是图像中的某一部分,而不是整幅图像。有些情况下整幅图像的亮度正常,但是我们关注的那一部分其实有些亮度异常,需要进行亮度校正。因此,有必要实现一下带图像掩码的亮度检测。实现起来也不难,求均值和平均差时都在掩码图像有效的区域(非0区域)内进行,注意N不能再是整幅图像的像素总数了。
//参考博客:https://blog.csdn.net/kklots/article/details/12720359
//自己博客:https://blog.csdn.net/weixin_42142612/article/details/80901580
#include
using namespace std;
using namespace cv;
int BrightnessDetWithMask(const cv::Mat &src, cv::Mat &mask, int &Refer, float &Mean, float &MeanDev);
int main()
{
cv::Mat src = cv::imread("C:\\Users\\dell\\Desktop\\xin2.jpg", 1);
if (src.empty())
{
cout << "输入图像为空" << endl;
return -1;
}
cv::Mat mask = cv::imread("C:\\Users\\dell\\Desktop\\xin2mask.jpg", 0);
int Refer = 128;
float MeanDev = 0.0;
float Mean = 0.0;
BrightnessDetWithMask(src, mask, Refer, Mean, MeanDev);
cout << "平均值: " << Mean << endl;
cout << "平均偏差: " << MeanDev << endl;
cout << "判断结果: " << endl;
//通过平均偏差的大小来判断是否异常,阈值取abs(Mean)
if (MeanDev < abs(Mean)) //平均偏差小于阈值,说明各标志值与平均数的差异程度越小,该平均数的代表性就越大
{
if (Mean > 0) //均值大于参考值(128),说明图像太亮
cout << "图像过亮!" << endl;
else if (Mean < 0) //均值大于参考值(128),说明图像太暗
cout << "图像过暗!" << endl;
else
cout << "图像亮度正常!" << endl;
}
else
cout << "图像亮度正常!" << endl;
cv::Mat ValidImg;
src.copyTo(ValidImg, mask);
waitKey(0);
return 0;
}
/*
* 函数功能:计算图像有效区域平均值和平均差,可以完成图像有效区域的亮度异常判断
* 输入参数:src 输入图像
mask 输入图像掩码
Refer 输入图像正常参考值,一般取128
* 输出参数:Mean 输出图像平均值(注意是各像素值减Refer后的平均值)
MeanDev 输出图像平均差
* 返回值: int 1 正常
-1 输入图像有误
-2 输入掩码有误
-3 输入亮度参考值有误
-4 有效像素点个数为0,计算无意义
* 备注:根据输出参数可以完成异常判断,假设平均差阈值为T
MeanDev < T 图像可能存在异常:
Mean > 0 大多数像素值大于参考值,说明图像太亮
Mean < 0 大多数像素值小于参考值,说明图像太暗
*/
int BrightnessDetWithMask(const cv::Mat &src, cv::Mat &mask,int &Refer, float &Mean, float &MeanDev)
{
if(src.empty())
return -1; //输入图像为空
if (mask.empty()) //输入掩码为空,说明图像全部有效
mask = cv::Mat::ones(src.size(), CV_8UC1);
else
{
if (mask.channels() != 1 || mask.size() != src.size())
return -2; //输入掩码不为空,但是格式或者尺寸不对
}
if (Refer >= 256 || Refer < 0)
return -3; //输入参考值有误,范围应该在[0,256)
cv::Mat gray; //转换为灰度图
if (3 == src.channels())
cv::cvtColor(src, gray, CV_BGR2GRAY);
else
gray = src.clone();
//计算图像有效区域内均值,此处利用函数Scalar mean(InputArray src, InputArray mask=noArray())
cv::Scalar meanGrayS = cv::mean(gray, mask);
float meanGray = meanGrayS[0];
//认为Refer(128)为图像亮度正常值,进一步计算出图像的偏移均值
Mean = meanGray - Refer;
//计算图像有效区域的偏移均值的平均偏差 MD = ∑|x - Mean(x)| / n
int nRows = gray.rows;
int nCols = gray.cols;
int sumTemp = 0;
int ValidNum = 0; //有效像素点个数
for (int j = 0; j < nRows; j++)
{
uchar *pGray = gray.ptr(j); //灰度图像指针
uchar *pmask = mask.ptr(j); //掩码图像指针
for (int i = 0; i < nCols; i++)
{
if (pmask[i]) //注意只在掩码图像像素点非0(有效)时进行计算
{
int diffTemp = pGray[i] - 128;
int absTemp = abs(diffTemp - Mean);
sumTemp += absTemp;
ValidNum++; //统计有效像素点个数
}
}
}
if (0 == ValidNum)
return -4; //有效像素点个数为0,计算无意义
MeanDev = (float)(sumTemp) / (float)(ValidNum);
return 1;
}
(2)亮度检测的后续,需要对图像进行亮度校正。可以考虑gamma校正,图像偏亮和图像偏暗设置不同的校正参数。
比如,图像太暗,设置gamma = 1/2.2,使图像整体亮度值变大;图像太亮,设置gamma = 2.2,使图像整体亮度值变小。
当然gamma校正也可以加上一个图像掩码,只在掩码图像的有效区域进行。
备注:遇到一个问题,如果总想着不思考,直接找到现成的答案,这种惰性思维很可怕,入职一年了,自己明显感觉没积累什么,很大程度就是没养成一个好的解决问题的习惯。要有意识的改变这种惰性思维,加油!!