从上一个section3.2到现在又过去一段时间了,断断续续终于把section3.3的直方图那些事弄出来了,还是那样:不求速度与健壮性,但求自己取实现一下,尽量自己去写代码,不用现成的函数。
《数字图像处理 Digital Image Processing-3E冈萨雷斯》(后面简称《DIP》)的section3.3是直方图处理有直方图均衡,直方图规则化,局部直方图处理以及用局部直直方图做图像增强,主要是这几个,所以这次就把这几个算法实现了一下,附带着写了一个简单的显示直方图的函数,能够以曲线或者条状显示直方图,1个通道或者3个通道。当然使用了一些OpenCV的函数。
算法内容都是按照书上来的,所以废话不说,先上声明:
enum HistType{Line_hist, Bin_hist};
class section_3_3
{
public:
//calculate image histogram;
bool getImgHist(Mat &src, vector< vector<int> > &hist, int chanels_use);
//equalize given image
Mat & equalizeImgHist(Mat &src);
//show given histogram--note! this change the histogram!
bool showHist(string wndname, vector< vector<int> > &hist, HistType histType, int thickness=1,bool bSave=false, string savename=string());
//normalize histogram to given range;
void normalizeHist(vector< vector<int> > &hist, int maxLeval);
//hist matching or hist normalization;
//use the point given histogram curve;
Mat& histMatching(Mat &src, vector<cv::Point> &hist_graph);
//use the given histogram;
Mat& histMatching(Mat &src, vector<int> &dest_hist);
//equalize use local hist;
Mat& localEqualize(Mat &src, int rgSize=3);
//image enhancement use local hist;
Mat& enhanceImg(Mat &src, int rgSize=3, float E=4.0f, float k0=0.4f, float k1=0.02f, float k2=0.4f);
private:
Mat m_destMat;
Mat m_showMat;
};
主要的就是前面说的那几个函数,采用了STL的vector来存放直方图,其中需要注意的是normalizeHist函数,其实就是将直方图的bin的最大值限制到maxLeval,方便直方图的显示,histMatching是直方图规则化或者叫直方图匹配,其中给出了两种实现,一个用vector点集指定直方图曲线的几个拐点,另一个是直接输入一个目标直方图,将源图像的直方图变换为与目标直方图一样(大体一样)。下面是函数实现--有点长:
bool section_3_3::getImgHist(Mat &src, vector< vector<int> > &hist, int chanels_use)
{
if(src.channels() < chanels_use || !(chanels_use==1||chanels_use==3))
return false;
Mat temp_gray;
if(chanels_use ==1 && src.channels()==3)
cvtColor(src, temp_gray, COLOR_BGR2GRAY);
//create hist vectors;
vector<int> hh(256, 0);
for(int i=0; i<chanels_use; ++i)
{
hist.push_back(hh);
}
//calculate pixels
for (int i=0; i<src.rows; ++i)
{
uchar *src_ptr = src.ptr<uchar>(i);
for (int j=0; j<src.cols; ++j)
{
if(chanels_use==1)
{
++hist[0][src_ptr[j]];
}
else
{
int j3 = j*3;
++hist[0][src_ptr[j3]];
++hist[1][src_ptr[j3+1]];
++hist[2][src_ptr[j3+2]];
}
}
}
return true;
}
//normalize hist to the given range, maxLeval gives the upper bound, lower bound is 0;
void section_3_3::normalizeHist(vector< vector<int> > &hist, int maxLeval)
{
for (size_t i=0; i<hist.size(); ++i)
{
int maxvalue = 0;
for (size_t j=0; j<hist[i].size(); ++j)
{
if(hist[i][j]>maxvalue)
maxvalue = hist[i][j];
}
//if the maxvalue is less than maxLeval, no need to change;
if(maxvalue <= maxLeval)
continue;
//normalize the hist---note!-this change the given histogram;
for (size_t j=0; j<hist[i].size(); ++j)
{
hist[i][j] = (int)(hist[i][j]*maxLeval/(float)(maxvalue));
}
}
}
//show histogram in image, can choose hist type:bin or line, save it or not;
bool section_3_3::showHist(string wndname, vector< vector<int> > &hist, HistType histType, int thickness, bool bSave, string savename)
{
int width = 512+20;
int heightB = 400; //big
int heightS = 200; //small
int heightS3 = 600; //3*small
if(thickness > 2)
thickness = 2;
//curve hist ;
if(histType == Line_hist)
{
m_showMat.create(heightB+20, width, CV_8UC3);
normalizeHist(hist, heightB-10);
if(thickness==-1)
thickness = 1;
}
else //bin hist;
{
if(hist.size() == 3)
{
m_showMat.create(heightS3+20, width, CV_8UC3);
normalizeHist(hist, heightS-10);
}
else
{
m_showMat.create(heightB+20, width, CV_8UC3);
normalizeHist(hist, heightB-10);
}
}
//black background
m_showMat = Scalar::all(0);
//if bin hist and 3 channels, up to down=channel 0 to 2;
//if line hist all in one;
int chanel = hist.size();
for(int i=1; i<256;)
{
if (histType == Line_hist)
{
if(chanel == 1)
{
line(m_showMat, cv::Point(10+2*(i-1), heightB-hist[0][i-1]), cv::Point(10+2*i, heightB-hist[0][i]), Scalar::all(255), thickness);
}
else
{
line(m_showMat, cv::Point(10+2*(i-1), heightB-hist[0][i-1]), cv::Point(10+2*i, heightB-hist[0][i]), Scalar(255,0,0), thickness);
line(m_showMat, cv::Point(10+2*(i-1), heightB-hist[1][i-1]), cv::Point(10+2*i, heightB-hist[1][i]), Scalar(0,255,0), thickness);
line(m_showMat, cv::Point(10+2*(i-1), heightB-hist[2][i-1]), cv::Point(10+2*i, heightB-hist[2][i]), Scalar(0,0,255), thickness);
}
++i;
}
else
{
if (chanel == 1)
{
rectangle(m_showMat, cv::Point(10+2*(i-1), heightB),cv::Point(10+2*i, heightB-hist[0][i-1]),Scalar::all(255), thickness);
rectangle(m_showMat, cv::Point(10+2*i, heightB),cv::Point(10+2*(i+1), heightB-hist[0][i]),Scalar::all(255), thickness);
}
else
{
rectangle(m_showMat, cv::Point(10+2*(i-1), heightS),cv::Point(10+2*i, heightS-hist[0][i-1]),Scalar(255,0,0), thickness);
rectangle(m_showMat, cv::Point(10+2*i, heightS),cv::Point(10+2*(i+1), heightS-hist[0][i]),Scalar(255,0,0), thickness);
rectangle(m_showMat, cv::Point(10+2*(i-1), heightB),cv::Point(10+2*i, heightB-hist[1][i-1]),Scalar(0,255,0), thickness);
rectangle(m_showMat, cv::Point(10+2*i, heightB),cv::Point(10+2*(i+1), heightB-hist[1][i]),Scalar(0,255,0), thickness);
rectangle(m_showMat, cv::Point(10+2*(i-1), heightS3),cv::Point(10+2*i, heightS3-hist[2][i-1]),Scalar(0,0,255), thickness);
rectangle(m_showMat, cv::Point(10+2*i, heightS3),cv::Point(10+2*(i+1), heightS3-hist[2][i]),Scalar(0,0,255), thickness);
}
i+=2;
}
}
imshow(wndname, m_showMat);
if(bSave)
{
if(savename.empty())
savename = "default.bmp";
imwrite(savename, m_showMat);
}
return true;
}
//equalize image hist and give the changed image;
Mat & section_3_3::equalizeImgHist(Mat &src)
{
src.copyTo(m_destMat);
int channel = src.channels();
float k = 255.f/src.total();
//calculate hist;
vector< vector<int> > hist;
getImgHist(src, hist, channel);
//create map table;
vector<uchar> pixtable(256, 0);
vector< vector<uchar> > ttable;
for(int i=0; i<channel; ++i)
{
size_t accum = 0;
for (size_t j=0; j<pixtable.size(); ++j)
{
accum += hist[i][j];
pixtable[j] = static_cast<uchar>(k*accum);
}
ttable.push_back(pixtable);
}
//equalize src image;
for (int i=0; i<src.rows; ++i)
{
uchar *src_ptr = src.ptr<uchar>(i);
uchar *dest_ptr = m_destMat.ptr<uchar>(i);
for (int j=0; j<src.cols; ++j)
{
if(channel==1)
{
dest_ptr[j] = ttable[0][src_ptr[j]];
}
else
{
int j3 = j*3;
dest_ptr[j3] = ttable[0][src_ptr[j3]];
dest_ptr[j3+1] = ttable[1][src_ptr[j3+1]];
dest_ptr[j3+2] = ttable[2][src_ptr[j3+2]];
}
}
}
return m_destMat;
}
//hist matching---see <<digital image processing-3E-chs>>-section3.3.2,page:77-83;
//hist_graph--histogram curve given by multiplie points;
Mat& section_3_3::histMatching(Mat &src, vector<cv::Point> &hist_graph)
{
if(src.channels()!=1 || hist_graph.size()<2 || hist_graph[0].x!=0||hist_graph[hist_graph.size()-1].x!=255)
return m_destMat;
//check each point--every point's x coord should not less than the last one;
for (size_t i=1; i<hist_graph.size(); ++i)
{
if(hist_graph[i].x<hist_graph[i-1].x)
return m_destMat;
}
//create dest mat;
src.copyTo(m_destMat);
//equalize src image;
float k = 255.f/src.total();
//calculate hist;
vector< vector<int> > src_hist;
getImgHist(src, src_hist, 1);
//create src equalize table--index is r, value is s;
uchar r_s_table[256] = {0};
size_t accum = 0;
for (size_t i=0; i<256; ++i)
{
accum += src_hist[0][i];
r_s_table[i] = static_cast<uchar>(k*accum);
}
//create the required hist;
int hist[256]={0};
int uplimit = min(256, hist_graph[1].x);
size_t j=0;
size_t total_pix = 0;
for(int i=0;;)
{
float k = (hist_graph[j+1].y-hist_graph[j].y)/(float)(hist_graph[j+1].x-hist_graph[j].x);
for(; i<=uplimit; ++i)
{
int pix = 0;
//not vertical
if(hist_graph[j].x < hist_graph[j+1].x)
{
pix = saturate_cast<int>(hist_graph[j].y+k*(i-hist_graph[j].x));
}
else
{//vertical
pix = hist_graph[j].y;
}
hist[i] = pix;
total_pix += pix;
}
//shift the section and check
if (++j < hist_graph.size()-1)
{
uplimit = hist_graph[j+1].x;
}
else
{
for (;i<256; ++i)
{
hist[i] = 0;
}
break;
}
}
//create dest equalize table--index is z, value is s;
k = 255.f/total_pix;
uchar z_s_table[256] = {0};
accum = 0;
for (size_t i=0; i<256; ++i)
{
accum += hist[i];
z_s_table[i] = static_cast<uchar>(k*accum);
}
// cout<<"total="<<total_pix<<",accum="<<accum<<endl;
//create s_z table,init to -1 --index is s, value is z;
int s_z_table[256];
for(int t=0; t<256; ++t)
s_z_table[t] = -1;
//from big to small--if multiple z mapped to same s, the big overlayed by small z;
for (int i=255; i>-1; --i)
{
s_z_table[z_s_table[i]] = i;
// cout<<i<<"-"<<(int)z_s_table[i]<<endl;
}
//create the final transform table--index is r, value is z;
uchar r_z_table[256]={0};
uchar last_z = 0;
for (int i=0; i<256; ++i)
{
if (s_z_table[i] != -1)
{
last_z = r_z_table[i] = r_s_table[s_z_table[i]];
}
else
{
r_z_table[i] = last_z;
}
// cout<<i<<"-"<<(int)r_z_table[i]<<endl;
}
//tranform the src image pixels value;
for (int i=0; i<src.rows; ++i)
{
uchar *src_ptr = src.ptr<uchar>(i);
uchar *dest_ptr = m_destMat.ptr<uchar>(i);
for (int j=0; j<src.cols; ++j)
{
dest_ptr[j] = r_z_table[src_ptr[j]];
}
}
return m_destMat;
}
//match the image hist to the given hist, most codes are same with the one above;
Mat& section_3_3::histMatching(Mat &src, vector<int> &dest_hist)
{
if(src.channels()!=1 || dest_hist.size()!=256)
return m_destMat;
//create dest mat;
src.copyTo(m_destMat);
//equalize src image;
float k = 255.f/src.total();
//calculate hist;
vector< vector<int> > src_hist;
getImgHist(src, src_hist, 1);
//create src equalize table--index is r, value is s;
uchar r_s_table[256] = {0};
size_t accum = 0;
for (size_t i=0; i<256; ++i)
{
accum += src_hist[0][i];
r_s_table[i] = static_cast<uchar>(k*accum);
}
//create dest equalize table--index is z, value is s;
size_t total = 0;
for(int i=0; i<256; ++i)
total += dest_hist[i];
k = 255.f/total;
uchar z_s_table[256] = {0};
accum = 0;
for (size_t i=0; i<256; ++i)
{
accum += dest_hist[i];
z_s_table[i] = static_cast<uchar>(k*accum);
}
//create s_z table,init to -1 --index is s, value is z;
int s_z_table[256];
for(int t=0; t<256; ++t)
s_z_table[t] = -1;
//from big to small--if multiple z mapped to same s, the big overlayed by small z;
for (int i=255; i>-1; --i)
{
s_z_table[z_s_table[i]] = i;
// cout<<i<<"-"<<(int)z_s_table[i]<<endl;
}
//create the final transform table--index is r, value is z;
uchar r_z_table[256]={0};
uchar last_z = 0;
for (int i=0; i<256; ++i)
{
if (s_z_table[i] != -1)
{
last_z = r_z_table[i] = r_s_table[s_z_table[i]];
}
else
{
r_z_table[i] = last_z;
}
// cout<<i<<"-"<<(int)r_z_table[i]<<endl;
}
//tranform the src image pixels value;
for (int i=0; i<src.rows; ++i)
{
uchar *src_ptr = src.ptr<uchar>(i);
uchar *dest_ptr = m_destMat.ptr<uchar>(i);
for (int j=0; j<src.cols; ++j)
{
dest_ptr[j] = r_z_table[src_ptr[j]];
}
}
return m_destMat;
}
//use local hist to equalize image hist;--see<<digital image processing>>(call it DIP from now on),section 3.3.3;
//note! rgSize should less than 17, and this function is slow if rgSize is big!!
Mat& section_3_3::localEqualize(Mat &src, int rgSize)
{
//only deals odds that less than 17;
//if rgSize>16 then transtable will bigger than 256, this method will have no attraction;
if (src.channels()!=1 || (rgSize%2 != 1) || rgSize>15)
return m_destMat;
/*
the equalize formula: Sk=((L-1)/M*N)*sigma_i=0~k(Ri);
to this M*N=rgSize*rgSize, L=256, and i at most can be numbers,others are 0, so this formula equals(take 3*3 as example):
Sk = a*i; a=255/9, i=1~9; so we can create a table to accelerate the speed;
*/
int tabSize = rgSize*rgSize;
uchar *trans_table = new uchar[tabSize];
memset(trans_table, 0, tabSize);
float k = 255.f/tabSize;
for (int i=0; i<tabSize; ++i)
{
trans_table[i] = static_cast<uchar>(k*(i+1));
// cout<<i<<"="<<int(trans_table[i])<<endl;
}
//create the dest mat;
src.copyTo(m_destMat);
//
int *box = new int[rgSize];
for (int i=0; i<rgSize; ++i)
{
box[i] = -rgSize/2+i;
}
int step = src.step[0];
int offset_i=0,offset_j=0;
int count=0;
for (int i=0; i<src.rows; ++i)
{
for (int j=0; j<src.cols; ++j)
{
uchar pix = *(src.data+i*step+j);
count = 0;
for (int a=0; a<rgSize; ++a)
{
for (int b=0;b<rgSize; ++b)
{
offset_i = std::min(std::max(0,i+box[a]), src.rows-1);
offset_j = std::min(std::max(0,j+box[b]), src.cols-1);
if(*(src.data+step*offset_i+offset_j) <= pix)
++count;
}
}
*(m_destMat.data+i*step+j) = trans_table[count];
}
}
return m_destMat;
}
//image enhancement use local hist, <DIP>section 3.3.4;
Mat& section_3_3::enhanceImg(Mat &src, int rgSize, float E, float k0, float k1, float k2)
{
if (src.channels()!=1 || (rgSize !=3 && rgSize != 5))
return m_destMat;
//get histogram of total image;
vector< vector<int> > hist;
getImgHist(src, hist, 1);
//calculate average
vector<float> pers(256,0.f);
float m_g = 0.f;
float totals = src.total();
for (int i=0; i<256; ++i)
{
pers[i] = hist[0][i]/totals;
m_g += i*pers[i];
}
//calculate sigma2
float sigma2_g = 0.f;
for (int i=0; i<256; ++i)
{
sigma2_g += (i-m_g)*(i-m_g)*pers[i];
}
cout<<"mg="<<m_g<<",sigma2_g="<<sigma2_g<<endl;
//create the dest mat;
src.copyTo(m_destMat);
//roi offsets
int *box = new int[rgSize];
for (int i=0; i<rgSize; ++i)
{
box[i] = -rgSize/2+i;
}
//prepare vars
int roiSize = rgSize*rgSize;
int step = src.step[0];
int offset_i=0,offset_j=0;
float count = 0;
float k0_mg = k0*m_g;
float k1_sigma2_g = k1*sigma2_g;
float k2_sigma2_g = k2*sigma2_g;
//caluc and assign
for (int i=0; i<src.rows; ++i)
{
for (int j=0; j<src.cols; ++j)
{
uchar pix = *(src.data+i*step+j);
count = 0.f;
//calculate local avg;
for (int a=0; a<rgSize; ++a)
{
for (int b=0;b<rgSize; ++b)
{
offset_i = std::min(std::max(0,i+box[a]), src.rows-1);
offset_j = std::min(std::max(0,j+box[b]), src.cols-1);
count += *(src.data+step*offset_i+offset_j);
}
}
float m = count/roiSize;
float sigma2 = 0.f;
//calculate local sigma2;
for (int a=0; a<rgSize; ++a)
{
for (int b=0;b<rgSize; ++b)
{
offset_i = std::min(std::max(0,i+box[a]), src.rows-1);
offset_j = std::min(std::max(0,j+box[b]), src.cols-1);
uchar vl = *(src.data+step*offset_i+offset_j);
sigma2 += (vl-m)*(vl-m);
}
}
sigma2 /=roiSize;
//check conditions;
if(m <= k0_mg && sigma2<=k2_sigma2_g && sigma2>=k1_sigma2_g)
*(m_destMat.data+i*step+j) = MIN(saturate_cast<uchar>(E*pix), 255);
else
*(m_destMat.data+i*step+j) = pix;
if(0 && i>230&&j>190&&i<270&&j<230)
cout<<j<<","<<i<<",m="<<m<<",sigma="<<sigma2<<endl;
}
}
return m_destMat;
}
对应声明,按顺序来的。贴几个处理的图像:
原始的dog图像,以lena直方图为目标直方图变换后的图像。
对应的变换前,变换后,以及lena的直方图--从直方图可以看出,变换后的直方图确实与lena的很相似:
然后是局部直方图均衡化,对lena图像使用7*7邻域;
最后是直方图用于图像增强,对钨丝图像,采用的参数为邻域为3*3,E=4,k0=0.35,k1=0.0001,k2=0.05,下面是变换前后的图
虽然黑白交界处出现了轮廓,但是对右边很暗的那部分,确实出现了与书中一样的效果。
继续努力,下一个section!