一、对比度与亮度增强
使用场景:深色(黑色)背景的亮点(白点)检测
详情见: 对比度亮度图像增强及convertTo详解
实现:
void adjust(const cv::Mat &src, cv::Mat &dst, const float brightness, const float contrast)
{
double B = brightness / 255.;
double c = contrast / 255.;
double k = tan((45 + 44 * c) / 180 * M_PI);
double alpha = k;
double beta = 127.5 * (1 + B) - k * 127.5 * (1 - B);
src.convertTo(dst, -1, alpha, beta);
}
二、直方图均衡化
简单介绍一下,暂时用不到。
直方图均衡化是对一整幅图像进行映射,并不会对某些区域局部映射,对于那些部分区域偏暗或者偏亮的图像而言并不适用。同时直方图均衡化后的图像灰度级会减少,造成图像的一些细节消失。
对于一幅整体偏暗或者偏亮的图像而言比较适用。可以使得整幅图像的灰度值份均匀分布在整个动态范围[0,255]之内,从而增加图像的对比度。
1.自定义的累计频率均衡法:
步骤: 1.统计图像中每个灰度值像素的个数 2.计算每个灰度值像素的频率,并计算累计频率 3.将图像进行映射,图像的灰度值=图像原来灰度值*累计频率
单通道:
bool MyEqualizeHist(Mat gray, Mat & result)
{
map<int, int>mp;
for (int i = 0; i < gray.rows; i++)
{
uchar* ptr = gray.data + i * gray.cols;
for (int j = 0; j < gray.cols; j++)
{
int value = ptr[j];
mp[value]++;
}
}
map<int, double> valuePro;
int sum = gray.cols*gray.rows;
double sumPro = 0;
for (int i = 0; i < 256; i++)
{
sumPro += 1.0*mp[i] / sum;
valuePro[i] = sumPro;
}
for (int i = 0; i < gray.rows; i++)
{
uchar* ptr1 = gray.data + i*gray.cols;
for (int j = 0; j < gray.cols; j++) {
int value = ptr1[j];
double p = valuePro[value];
result.at<uchar>(i, j) = p*value;
}
}
return true;
}
多通道(RGB):
void MyEqualizeHistRgb(Mat& image)
{
Mat imageRGB[3];
split(image, imageRGB);
for (int i = 0; i < 3; i++)
{
MyEqualizeHist(imageRGB[i], imageRGB[i]);
}
merge(imageRGB, 3, image);
imshow("result", image);
}
2.opencv自带的equalizeHist()
equalizeHist(Mat src, Mat dst);
步骤:
1.计算输入图像(8位)的直方图H; 2. 对直方图H进行归一化,使直方图bins的总和为255; 3.计算直方图的积分(integral of thehistogram); H′i=∑ 0≤j<i H(j) 4.使用H′作为查找表( look-uptable)对图像进行变换,具体像素值的变换公式为:dst(x,y)=H"(src(x,y))
该算法归一化亮度并增加图像的对比度。
结果与自定义的会不同。
3.自适应的局部直方图均衡化
局部直方图处理过程:
(1)设定某一大小的模板(矩形邻域),在图像中沿逐个像素移动; (2)对每个像素位置,计算模板区域的直方图,对该局部区域进行直方图均衡或直方图匹配变换,变换结果只用于模板区域中心像素点的灰度值修正; (3)模板(邻域)在图像中逐行逐列移动,遍历所有像素点,完成对整幅图像的局部直方图处理。
C++代码:
cvtColor(img,gray,COLOR_BGR2GRAY);
Ptr<CLAHE> clahe = createCLAHE();
clahe->apply(gray, dst);
python代码:
//python
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(4,4))
imgLocalEqu = clahe.apply(img)
clipLimit:颜色对比度的阈值,可选项,默认值 8 titleGridSize:局部直方图均衡化的模板(邻域)大小,可选项,默认值 (8,8)
三、指数变换增强
指数变换(Power-Law )的公式:S=c*R^r, 通过合理的选择c和r可以压缩灰度范围,算法以c=1.0/255.0, r=2实现。
以偏暗的图片测试,实际效果更暗。符合该公式推理,实际的像素值替换成了查找表的较小值。
自定义指数增强算法:
void Enhance::ExpEnhance(IplImage* img, IplImage* dst)
{
uchar lut[256] ={0};
double temp = 1.0/255.0;
for ( int i =0; i<255; i++)
{
lut[i] = (uchar)(temp*i*i+0.5);
}
for( int row =0; row <img->height; row++)
{
uchar *data = (uchar*)img->imageData+ row* img->widthStep;
uchar *dstData = (uchar*)dst->imageData+ row* dst->widthStep;
for ( int col = 0; col<img->width; col++)
{
for( int k=0; k<img->nChannels; k++)
{
uchar t1 = data[col*img->nChannels+k];
dstData[col*img->nChannels+k] = lut[t1];
}
}
}
}
void Enhance::ExpEnhance(Mat & img,double c, int r)
{
if(img.channels() != 1)
{
cvtColor(img,img,COLOR_BGR2GRAY);
}
uchar lut[256] ={0};
for ( int i =0; i<255; i++)
{
lut[i] = (uchar)( c * pow(i,r) +0.5);
cout << c*i*i+0.5<<endl;
}
for(int i = 0;i < img.rows; i++)
{
for(int j = 0;j < img.cols; j++)
{
int pv = img.at<uchar>(i,j);
img.at<uchar>(i,j) = lut[pv];
}
}
}
注:部分博客使用expf(n)计算e的n次方,个人认为是错的。expf(100)已经inf无穷大了。
srcmat.at<uchar>(i, j) = (unsigned char)(255.0 *(expf(srcmat.at<uchar>(i, j) - low_bound) / expf(up_bound - low_bound)));
四、gamma增强
特殊的指数增强,基于幂次变换的Gamma校正是图像处理中一种非常重要的非线性变换,它与对数变换相反,它是对输入图像的灰度值进行指数变换,进而校正亮度上的偏差。
通常Gamma校正长应用于拓展暗调的细节。通常来讲,当Gamma校正的值大于1时,图像的高光部分被压缩而暗调部分被扩展;当Gamma校正的值小于1时,相反的,图像的高光部分被扩展而暗调备份被压缩。
output = L^γ 当γ小于1时,低灰度区间拉伸,高灰度区间压缩; 当γ大于1时,低灰度区间压缩,高灰度区间拉伸。 当γ等于1时,简化成恒等变换。
1.固定三次方增强:
void Enhance::gammaEhance(Mat& image)
{
Mat imageGamma(image.size(), CV_32FC3);
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
imageGamma.at<Vec3f>(i, j)[0] = (image.at<Vec3b>(i, j)[0])*(image.at<Vec3b>(i, j)[0])*(image.at<Vec3b>(i, j)[0]);
imageGamma.at<Vec3f>(i, j)[1] = (image.at<Vec3b>(i, j)[1])*(image.at<Vec3b>(i, j)[1])*(image.at<Vec3b>(i, j)[1]);
imageGamma.at<Vec3f>(i, j)[2] = (image.at<Vec3b>(i, j)[2])*(image.at<Vec3b>(i, j)[2])*(image.at<Vec3b>(i, j)[2]);
}
}
normalize(imageGamma, imageGamma, 0, 255, CV_MINMAX);
convertScaleAbs(imageGamma, imageGamma);
imshow("gamma三次方增强", imageGamma);
}
2.自定义系数增强:
Mat Enhance::gammaWithParameter(Mat &img, float parameter)
{
unsigned char LUT[256];
for (int i = 0; i < 256; i++)
{
LUT[i] = saturate_cast<uchar>(pow((float)(i / 255.0), parameter)*255.0f);
}
Mat dstImage = img.clone();
if (img.channels() == 1)
{
MatIterator_<uchar>iterator = dstImage.begin<uchar>();
MatIterator_<uchar>iteratorEnd = dstImage.end<uchar>();
for (; iterator != iteratorEnd; iterator++)
*iterator = LUT[(*iterator)];
}
else
{
MatIterator_<Vec3b>iterator = dstImage.begin<Vec3b>();
MatIterator_<Vec3b>iteratorEnd = dstImage.end<Vec3b>();
for (; iterator!=iteratorEnd; iterator++)
{
(*iterator)[0] = LUT[((*iterator)[0])];
(*iterator)[1] = LUT[((*iterator)[1])];
(*iterator)[2] = LUT[((*iterator)[2])];
}
}
return dstImage;
}
五、log转换增强
有效。测试透明物品模糊的边界增强后获得不错效果。
对数变换可以拓展低灰度值而压缩高灰度级值,让图像的灰度分布更加符合人眼的视觉特征。
经过适当的处理后,原始图像中低灰度区域的对比度将会增加,暗部细节将被增强。
对数变换公式为:
y = log(1+x)/b
其中,b是一个常数,用来控制曲线的弯曲程度,其中,b越小越靠近y轴,b越大越靠近x轴。表达式中的x是原始图像中的像素值,y是变换后的像素值。
实现:
void logEhance(Mat& image)
{
Mat imageLog(image.size(), CV_32FC3);
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
imageLog.at<Vec3f>(i, j)[0] = log(1 + image.at<Vec3b>(i, j)[0]);
imageLog.at<Vec3f>(i, j)[1] = log(1 + image.at<Vec3b>(i, j)[1]);
imageLog.at<Vec3f>(i, j)[2] = log(1 + image.at<Vec3b>(i, j)[2]);
}
}
normalize(imageLog, imageLog, 0, 255, CV_MINMAX);
convertScaleAbs(imageLog, image);
imshow("Log", image);
}
六、laplaceEhance增强
拥有锐化作用,对噪声比较敏感,需要先做平滑处理。
用来改善因扩散效应的模糊特别有效,因为它符合降制模型。扩散效应是成像过程中经常发生的现象。
Laplacian算子一般不以其原始形式用于边缘检测,因为其作为一个二阶导数,Laplacian算子对噪声具有无法接受的敏感性;同时其幅值产生算边缘,这是复杂的分割不希望有的结果;最后Laplacian算子不能检测边缘的方向.
实现:
void Enhance::laplaceEhance(Mat& image)
{
Mat imageEnhance;
Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0, 0, 5, 0, 0, -1, 0);
filter2D(image, imageEnhance, CV_8UC3, kernel);
imshow("laplaceEhance", imageEnhance);
}
七、线性变换:
没什么好说的,就是直接做线性变换。
y = kx+b;
实现:
Mat Enhance::linearTransformation(Mat img, const float k, const float b)
{
Mat dst = img.clone();
for(int i=0; i<img.rows; i++)
{
for(int j=0; j<img.cols; j++)
{
for(int c=0; c<3; c++)
{
float x =img.at<Vec3b>(i, j)[c];
dst.at<Vec3b>(i, j)[c] = saturate_cast<uchar>(k* x + b); }
}
}
return dst;
}
八、分段线性拉伸算法:
图像灰度变换中常用的算法,在商业图像编辑软件Photoshop中也有相应的功能。分段线性拉伸主要是用于提高图像对比度,突显图像细节。
自定义增强,减弱的像素区间:
void Enhance::PiecewiseLinearTrans(cv::Mat& matInput, float x1, float x2, float y1, float y2)
{
float K1 = y1 / x1;
float K2 = (y2 - y1) / (x2 - x1);
float C2 = y1 - K2 * x1;
float K3 = (255.0f - y2) / (255.0f - x2);
float C3 = 255.0f - K3 * 255.0f;
uchar LUT[256] ={0};
for (int m = 0; m < 256; m++)
{
if (m < x1)
{
LUT[m] = m * K1;
}
else if (m > x2)
{
LUT[m] = m * K3 + C3;
}
else
{
LUT[m] = m * K2 + C2;
}
}
for (int j = 0; j < matInput.rows; j++)
{
for (int i = 0; i < matInput.cols; i++)
{
int x = matInput.at<uchar>(j,i);
matInput.at<uchar>(j,i) = LUT[x];
}
}
}
九、灰度级分层
最简单的例子就是常用的opencv二值化算法:thresh、inRange都可以。
此处可以自行举一反三。
十、曝光过度对图像取反
字面意思:对值做255-x的操作即可。
实现:
void ExporeOver(IplImage* img, IplImage* dst)
{
for( int row =0; row height; row++)
{
uchar *data = (uchar*)img->imageData+ row* img->widthStep;
uchar *dstData = (uchar*)dst->imageData+ row* dst->widthStep;
for ( int col = 0; colwidth; col++)
{
for( int k=0; knChannels; k++)
{
uchar t1 = data[col*img->nChannels+k];
uchar t2 = 255 - t1;
dstData[col*img->nChannels+k] = min(t1,t2);
}
}
}
}
十一、高反差保留
高反差保留主要是将图像中颜色、明暗反差较大两部分的交界处保留下来,比如图像中有一个人和一块石头,那么石头的轮廓线和人的轮廓线以及面部、服装等有明显线条的地方会变被保留,儿其他大面积无明显明暗变化的地方则生成中灰色。
其表达形式为:
dst = r*(img – Blur(img))。
实现:
Mat HighPass(Mat img)
{
Mat temp;
GaussianBlur(img, temp,Size(7,7),1.6,1.6);
int r=3;
Mat diff = img + r*(img-temp);
return diff;
}
十二、Masaic算法(马赛克)
在日常中有时候保密或其他需要将图像马赛克,下面的算法实现图像马赛克功能(原理:用中心像素来表示邻域像素)。
uchar getPixel( IplImage* img, int row, int col, int k)
{
return ((uchar*)img->imageData + row* img->widthStep)[col*img->nChannels +k];
}
void setPixel( IplImage* img, int row, int col, int k, uchar val)
{
((uchar*)img->imageData + row* img->widthStep)[col*img->nChannels +k] = val;
}
void Masic(IplImage* img, IplImage* dst, int nSize)
{
int offset = (nSize-1)/2;
for ( int row = offset; row <img->height - offset; row= row+offset)
{
for( int col= offset; col<img->width - offset; col = col+offset)
{
int val0 = getPixel(img, row, col, 0);
int val1 = getPixel(img, row, col, 1);
int val2 = getPixel(img, row, col, 2);
for ( int m= -offset; m<offset; m++)
{
for ( int n=-offset; n<offset; n++)
{
setPixel(dst, row+m, col+n, 0, val0);
setPixel(dst, row+m, col+n, 1, val1);
setPixel(dst, row+m, col+n, 2, val2);
}
}
}
}
}
十三、总结
以上众多的方法在实战中有时不是单独使用的,滤波与各个方法相互的结合往往取得更好的效果。
|