您的位置:首页 > 编程语言 > MATLAB

增强图像对比度算法原理及matlab代码实现

2017-11-28 17:20 1056 查看


关于图像增强必须清楚的基本概念

1.图像增强的目的:

1)改善图像的视觉效果,

2)转换为更适合于人或机器分析处理的形式

3)突出对人或机器分析有意义的信息

4)抑制无用信息,提高图像的使用价值

5)增强后的图像并不一定保真

2,图像增强的方法分类:

1)从处理对象分类:灰度图像,(伪)彩色图像

2)从处理策略分类:全局处理,局部处理(ROI ROI,Region of Interest Interest)

3)从处理方法分类:空间域(点域运算,即灰度变换;邻域方法,即空域滤波),频域方法

4)从处理目的分类:图像锐化,平滑去噪,灰度调整(对比度增强)

3,图像增强的方法之对比度增强

1)灰度变换法

线性变换(已实现)

对数变换(已实现)

指数变换(已实现)

2)直方图调整法

直方图均衡化(已实现)

直方图匹配(未实现)


一,直方图均衡化

直方图均衡化的英文名称是Histogram Equalization.
  图像对比度增强的方法可以分成两类:一类是直接对比度增强方法;另一类是间接对比度增强方法。直方图拉伸和直方图均衡化是两种最常见的间接对比度增强方法。直方图拉伸是通过对比度拉伸对直方图进行调整,从而“扩大”前景和背景灰度的差别,以达到增强对比度的目的,这种方法可以利用线性或非线性的方法来实现;直方图均衡化则通过使用累积函数对灰度值进行“调整”以实现对比度的增强。

  直方图均衡化处理的“中心思想”是把原始图像的灰度直方图从比较集中的某个灰度区间变成在全部灰度范围内的均匀分布。直方图均衡化就是对图像进行非线性拉伸,重新分配图像像素值,使一定灰度范围内的像素数量大致相同。直方图均衡化就是把给定图像的直方图分布改变成“均匀”分布直方图分布。

  缺点:

  1)变换后图像的灰度级减少,某些细节消失;

  2)某些图像,如直方图有高峰,经处理后对比度不自然的过分增强。

  直方图均衡化是图像处理领域中利用图像直方图对对比度进行调整的方法。

  这种方法通常用来增加许多图像的局部对比度,尤其是当图像的有用数据的对比度相当接近的时候。通过这种方法,亮度可以更好地在直方图上分布。这样就可以用于增强局部的对比度而不影响整体的对比度,直方图均衡化通过有效地扩展常用的亮度来实现这种功能。

  这种方法对于背景和前景都太亮或者太暗的图像非常有用,这种方法尤其是可以带来X光图像中更好的骨骼结构显示以及曝光过度或者曝光不足照片中更好的细节。这种方法的一个主要优势是它是一个相当直观的技术并且是可逆操作,如果已知均衡化函数,那么就可以恢复原始的直方图,并且计算量也不大。这种方法的一个缺点是它对处理的数据不加选择,它可能会增加背景杂讯的对比度并且降低有用信号的对比度。

  


关于编程实现,同样是不调用matlab库函数,自己编程实现。这样可以更深刻地理解直方图均衡化技术,提高编程能力。

实现代码(matlab):

[cpp] view
plain copy

clc;

close all;

clear all;

src_img = imread('flyman_gray.bmp');

figure (1)

subplot(321),imshow(src_img),title('原图像');%显示原始图像

subplot(322),imhist(src_img),title('原图像直方图');%显示原始图像直方图

matlab_eq=histeq(src_img); %利用matlab的函数直方图均衡化

subplot(323),imshow(matlab_eq),title('matlab直方图均衡化原图像');%显示原始图像

subplot(324),imhist(matlab_eq),title('matlab均衡化后的直方图');%显示原始图像直方图

dst_img=myHE(src_img); %利用自己写的函数直方图均衡化

subplot(325),imshow(dst_img),title('手写均衡化效果');%显示原始图像

subplot(326),imhist(dst_img),title('手写均衡化直方图');%显示原始图像直方图

直方图均衡化函数的实现:

[cpp] view
plain copy

function dst_img=myHE(src_img)

[height,width] = size(src_img);

dst_img=uint8(zeros(height,width));

%进行像素灰度统计;

NumPixel = zeros(1,256);%统计各灰度数目,共256个灰度级

for i = 1:height

for j = 1: width

NumPixel(src_img(i,j) + 1) = NumPixel(src_img(i,j) + 1) + 1;%对应灰度值像素点数量增加一

end

end

%计算灰度分布密度

ProbPixel = zeros(1,256);

for i = 1:256

ProbPixel(i) = NumPixel(i) / (height * width * 1.0);

end

%计算累计直方图分布

CumuPixel = zeros(1,256);

for i = 1:256

if i == 1

CumuPixel(i) = ProbPixel(i);

else

CumuPixel(i) = CumuPixel(i - 1) + ProbPixel(i);

end

end

% 指定范围进行均衡化

% pixel_max=max(max(I));

% pixel_min=min(min(I));

pixel_max=255;

pixel_min=0;

%对灰度值进行映射(均衡化)

for i = 1:height

for j = 1: width

dst_img(i,j) = CumuPixel(src_img(i,j)+1)*(pixel_max-pixel_min)+pixel_min;

end

end

return;



为什们和matlab的直方图不一样呢???


二,指数变换

指数变换(Power-Law )的公式:S=c*R^r,通过合理的选择c和r可以压缩灰度范围,算法以c=1.0/255.0, r=2实现。



要做该图像增强变换需要先做归一化,再指数变换,最后反归一化

增强效果展示:可以看见,改增强算法并不能很好的将像素尽可能的碾平。



指数增强参考程序为:

[cpp] view
plain copy

clc;

close all;

clear all;

% -------------Gamma Transformations-----------------

%f = imread('Fig0316(4)(bottom_left).tif');

f = imread('seed.tif');

Gamma = 0.4;

g2 = myExpEnhance(f,Gamma);

figure();

subplot(221); imshow(f); xlabel('a).Original Image');

subplot(222),imhist(f),title('原图像直方图');%显示原始图像直方图

subplot(223); imshow(g2); xlabel('b).Gamma Transformations \gamma = 0.4');

subplot(224),imhist(g2),title('增强图像直方图');%显示原始图像直方图

指数增强核心函数为:

[cpp] view
plain copy

function dst_img=myExpEnhance(src_img,Gamma)

src_img = mat2gray(src_img,[0 255]);%将图像矩阵A中介于amin和amax的数据归一化处理, 其余小于amin的元素都变为0, 大于amax的元素都变为1。

C = 1;

g2 = C*(src_img.^Gamma);

%反归一化

max=255;

min=0;

dst_img=uint8(g2*(max-min)+min);


三,对数变换

对数变换主要用于将图像的低灰度值部分扩展,将其高灰度值部分压缩,以达到强调图像低灰度部分的目的。变换方法由下式给出。



这里的对数变换,底数为(v+1),实际计算的时候,需要用换底公式。其输入范围为归一化的【0-1】,其输出也为【0-1】。对于不同的底数,其对应的变换曲线如下图所示。



底数越大,对低灰度部分的强调就越强,对高灰度部分的压缩也就越强。相反的,如果想强调高灰度部分,则用反对数函数就可以了。看下面的实验就可以很直观的理解,下图是某图像的二维傅里叶变换图像,其为了使其灰度部分较为明显,一般都会使用灰度变换处理一下。

效果图:



参考代码:

[cpp] view
plain copy

clc;

close all;

clear all;

%-------------Log Transformations-----------------

f = imread('seed.tif');

g_1 = myLogEnhance(f,10);

g_2 = myLogEnhance(f,100);

g_3 = myLogEnhance(f,200);

figure();

subplot(2,2,1);

imshow(f);xlabel('a).Original Image');

subplot(2,2,2);

imshow(g_1);xlabel('b).Log Transformations v=10');

subplot(2,2,3);

imshow(g_2);xlabel('c).Log Transformations v=100');

subplot(2,2,4);

imshow(g_3);

xlabel('d).Log Transformations v=200');

对数变换核心函数

[cpp] view
plain copy

function dst_img=myLogEnhance(src_img,v)

c=1.0;

src_img = mat2gray(src_img,[0 255]);

g =c*log2(1 + v*src_img)/log2(v+1);

%反归一化

max=255;

min=0;

dst_img=uint8(g*(max-min)+min);

四,灰度拉伸

灰度拉伸也用于强调图像的某个部分,与伽马变换与对数变换不同的是,灰度拉升可以改善图像的动态范围。可以将原来低对比度的图像拉伸为高对比度图像。实现灰度拉升的方法很多,其中最简单的一种就是线性拉伸。而这里介绍的方法稍微复杂一些。灰度拉伸所用数学式如下所示。



同样的,其输入r为【0-1】,其输出s也为【0-1】。这个式子再熟悉不过了,跟巴特沃斯高通滤波器像极了,其输入输出关系也大致能猜到是个什么形状的。但是,这里就出现一个问题了,输入为0时候,式子无意义了。所以,在用Matlab计算的时候,将其变为如下形式。



这里的eps,就是Matlab里面,一个很小数。如此做的话,式子变得有意义了。但是,其输入范围为【0-1】的时候,其输出范围变为了

。输出范围大致为【0-1】,为了精确起见,使用mat2gray函数将其归一化到精确的[0-1]。调用格式如下。


五,线性拉伸

为了突出感兴趣的目标或者灰度区间,相对抑制那些不感兴趣的灰度区域,可采用分段线性法,常用的是三段线性变换





参考程序:

[cpp] view
plain copy

clc;

close all;

clear all;

I=imread('seed.tif');

[m,n,k]=size(I);

figure (1)

imshow('seed.tif');title(' 原图像');

mid=mean(mean(I));

%横轴

fa=20; fb=80;

%纵轴

ga=50; gb=230;

J=myLinearEnhance(I,fa,fb,ga,gb);

figure (2)

imshow(J);title(' 线性拉伸图像');

pixel_f=1:256;

pixel_g=zeros(1,256);

%三段斜率,小于1表示该段将会被收缩

k1=double(ga/fa);

k2=(gb- ga)/(fb- fa);

k3=(256- gb)/(256- fb);

for i=1:256

if i <= fa

pixel_g(i)= k1*i;

elseif fa < i && i <= fb

pixel_g(i)= k2*( i- fa)+ ga;

else

pixel_g(i)= k3*( i - fb)+ gb;

end

end

figure (3)

plot(pixel_f,pixel_g);

核心函数:

[cpp] view
plain copy

function dst_img=myLinearEnhance(src_img,fa,fb,ga,gb)

[height,width] = size(src_img);

dst_img=uint8(zeros(height,width));

src_img=double(src_img);

%三段斜率

k1=ga/fa;

k2=(gb- ga)/(fb- fa);

k3=(255- gb)/(255- fb);

for i=1:height

for j=1:width

if src_img(i,j) <= fa

dst_img(i,j)= k1*src_img(i,j);

elseif fa < src_img(i,j) && src_img(i,j) <= fb

dst_img(i,j)= k2*( src_img(i,j)- fa)+ ga;

else

dst_img(i,j)= k3*( src_img(i,j)- fb)+ gb;

end

end

end

dst_img=uint8(dst_img);


附录:

附录网上的另一份讲解:

直方图均衡化算法分为三个步骤,第一步是统计直方图每个灰度级出现的次数,第二步是累计归一化的直方图,第三步是计算新的像素值。

第一步:

for(i=0;i<height;i++)

for(j=0;j<width;j++)

n[s[i][j]]++;

for(i=0;i<L;i++)

p[i]=n[i]/(width*height);

这里,n[i]表示的是灰度级为i的像素的个数,L表示的是最大灰度级,width和height分别表示的是原始图像的宽度和高度,所以,p[i]表示的就是灰度级为i的像素在整幅图像中出现的概率(其实就是p[]这个数组存储的就是这幅图像的归一化之后的直方图)。

第二步:

for(i=0;i<=L;i++)

for(j=0;j<=i;j++)

c[i]+=p[j];

c[]这个数组存储的就是累计的归一化直方图。

第三步:

max=min=s[0][0];

for(i=0;i<height;i++)

for(j=0;j<width;j++)

if(max<s[i][j]){

max=s[i][j];

}else if(min>s[i][j]){

min=s[i][j];

}

找出像素的最大值和最小值。

for(i=0;i<height;i++)

for(j=0;j<width;j++)

t[i][j]=c[s[i][j]]*(max-min)+min;

t[][]就是最终直方图均衡化之后的结果。

收录优秀代码:

这份代码写得不错,学习了,原博客地址见参考资源【3】!

[cpp] view
plain copy

#include <stdio.h>

#include <iostream>

#include "fftw3.h"

#include "string"

#include "vector"

#include <windows.h>

#include <opencv2/legacy/legacy.hpp>

#include <opencv2/nonfree/nonfree.hpp>//opencv_nonfree模块:包含一些拥有专利的算法,如SIFT、SURF函数源码。

#include "opencv2/core/core.hpp"

#include "opencv2/features2d/features2d.hpp"

#include "opencv2/highgui/highgui.hpp"

#include <opencv2/nonfree/features2d.hpp>

using namespace cv;

using namespace std;

class hisEqt

{

public:

hisEqt::hisEqt();

hisEqt::~hisEqt();

public:

int w;

int h;

int nlen;

int *pHis;

float *pdf;

//=====求像素分布概率密度====

void getPdf();

//======统计像素个数=======

void getHis(unsigned char*imgdata);

//==========画统计分布直方图===============

void drawHistogram(const float*pdf,Mat &hist1);

//===========直方图均衡化==========

void hisBal();

//====直方图均衡化后的图像===

void imgBal(unsigned char* img);

};

hisEqt::hisEqt() :nlen(0){

pHis = new int[256 * sizeof(int)];

memset(pHis, 0, 256 * sizeof(int));

pdf = new float[255 * sizeof(float)];

memset(pdf, 0, 255 * sizeof(float));

}

hisEqt::~hisEqt(){

delete[]pHis;

delete[]pdf;

}

//======统计像素个数=======

void hisEqt::getHis(unsigned char*imgdata){

for (int i = 0; i<nlen; i++)

{

pHis[imgdata[i]]++;

}

}

//=====求像素分布概率密度====

void hisEqt::getPdf(){

for (int k = 0; k<256; k++)

{

pdf[k] = pHis[k] / float(nlen);

}

}

//===========直方图均衡化==========

void hisEqt::hisBal(){

for (int k = 1; k<256; k++)

{

pdf[k] += pdf[k - 1];

}

for (int k = 0; k<256; k++)

{

pHis[k] = 255 * pdf[k];

}

}

//====直方图均衡化

void hisEqt::imgBal(unsigned char* img){

for (int i = 0; i<nlen; i++)

{

img[i] = pHis[img[i]];

}

}

void hisEqt::drawHistogram(const float *pdf, Mat& hist1){

for (int k = 0; k<256; k++)

{

if (k % 2 == 0)

{

Point a(k, 255), b(k, 255 - pdf[k] * 2550);

line(hist1,

a,

b,

Scalar(0, 0, 255),

1);

}

else

{

Point a(k, 255), b(k, 255 - pdf[k] * 2550);

line(hist1,

a,

b,

Scalar(0, 255, 0),

1);

}

}

}

int main()

{

Mat image = imread("Fig0651(a)(flower_no_compression).tif");

if (!image.data)

return -1;

Mat hist2(256, 256, CV_8UC3, Scalar(0, 0, 0));

Mat hist1(256, 256, CV_8UC3, Scalar(0, 0, 0));

Mat imgOut = Mat(image.rows, image.cols, CV_8UC3, Scalar(0, 0, 0));

vector<Mat> planes;

int chn = image.channels();

if (chn == 3)

{

split(image, planes);

}

while (chn)

{

chn--;

unsigned char* imageData = new unsigned char[sizeof(unsigned char)*(image.cols*image.rows)];

memcpy(imageData, planes[chn].data, planes[chn].cols*planes[chn].rows);

hisEqt his;//自定义的类

his.nlen = image.rows*image.cols;

his.getHis(imageData);

his.getPdf();

// //======画原图直方图并保存============

his.drawHistogram(his.pdf, hist1);

string pic_name = "hisline";

pic_name = pic_name + to_string(chn);

pic_name=pic_name+ ".jpg";

imwrite(pic_name, hist1);

his.hisBal();

his.getPdf();

// //======画均衡化后直方图并保存============

his.drawHistogram(his.pdf, hist2);

string pic_name0 = "his_balanceline";

pic_name0 = pic_name0 + to_string(chn);

pic_name0 = pic_name0 + ".jpg";

imwrite(pic_name0, hist2);

// //=====图像均衡化===

his.imgBal(imageData);

memcpy(planes[chn].data, imageData, planes[chn].cols*planes[chn].rows);

delete[] imageData;

imageData = NULL;

}

merge(planes, imgOut);//单通道合并

imwrite("result.jpg", imgOut);

return 0;

}

1、直方图均衡化

void cvEqualizeHist( const CvArr* src, CvArr* dst );

用来使灰度图象直方图均衡化,可以将比较淡的图像变换为比较深的图像(即增强图像的亮度及对比度)。

2、灰度拉伸

由于光线原因会造成图像局部过亮或过暗,需要对图像进行拉伸使之覆盖较大的取值区间。使亮的区域更亮,暗的区域更暗,提高图像的对比度,从而使图像边缘明显。灰度拉伸是将灰度图像进行分段性变化,即若原图像f(x,y)的灰度变化区间为[a,b],变换后图像g(x,y)的灰度范围扩展到区间[c,d],可采用下列线性变换来实现:



代码如下:
void stretch(IplImage* src,IplImage* dst,int nMin,int nMax)
{
int low_value=nMin;    //拉伸后像素的最小值
int high_value=nMax;   //拉伸后像素的最大值

float rate=0;          //图像的拉伸率

float stretch_p[256],stretch_p1[256],stretch_num[256];
//清空三个数组,初始化填充数组元素为0
memset(stretch_p,0,sizeof(stretch_p));
memset(stretch_p1,0,sizeof(stretch_p1));
memset(stretch_num,0,sizeof(stretch_num));
//统计图像各个灰度级出现的次数
uchar* srcData=(uchar*)src->imageData;
uchar* dstData=(uchar*)dst->imageData;
int nHeight=src->height;
int nWidth=src->width;
int i,j;
uchar nVal=0;
for (i=0;i<nHeight;i++)
{
for (j=0;j<nWidth;j++)
{
nVal=srcData[i*nWidth+j];  //i*nWidth为列数也就是宽度的乘积,不为字节,如果加上sizeof(uchar)= =1也可以,但是若为double型就会出现访问越界的情况
stretch_num[nVal]++;
}
}
//统计各个灰度级出现的概率
for (i=0;i<256;i++)
{
stretch_p[i]=stretch_num[i]/(nHeight*nWidth);
}
//统计各个灰度级的概率和
for (i=0;i<256;i++)
{
for (j=0;j<=i;j++)
{
stretch_p1[i]+=stretch_p[j];
}
}
//计算两个阈值点的值
for (i=0;i<256;i++)
{
if (stretch_p1[i]<0.1)     //low_value取值接近于10%的总像素的灰度值
{
low_value=i;
}
if (stretch_p1[i]>0.9)     //high_value取值接近于90%的总像素的灰度值
{
high_value=i;
break;
}
}
rate=(float)255/(high_value-low_value+1);
//进行灰度拉伸
for (i=0;i<nHeight;i++)
{
for (j=0;j<nWidth;j++)
{
nVal=srcData[i*nWidth+j];
if (nVal<low_value)
{
dstData[i*nWidth+j]=0;
}
else if (nVal>high_value)
{
dstData[i*nWidth+j]=255;
}
else
{
dstData[i*nWidth+j]=(uchar)((nVal-low_value)*rate+0.5);
if (dstData[i*nWidth+j]>255)
{
dstData[i*nWidth+j]=255;
}
}
}
}
}



参考资源

【1】http://blog.csdn.net/xiajun07061225/article/details/6910129

【2】数字图像处理,冈萨雷斯著

【3】http://blog.csdn.net/bettyshasha/article/details/46940805

【4】http://blog.csdn.net/terryzero/article/details/6043821

【5】http://www.myexception.cn/image/1450848.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: