一種基於平均值和平均偏差的影象亮度檢測方法[轉]
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<opencv2/opencv.hpp>
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<uchar>(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<opencv2/opencv.hpp>
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<uchar>(j); //灰度影象指標
uchar *pmask = mask.ptr<uchar>(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校正也可以加上一個影象掩碼,只在掩碼影象的有效區域進行。