A. OpenCV C++(四)----對比度增強
對比度增強或者稱為對比度拉伸就是圖像增強技術的一種,它主要解決由於圖像的灰度級范圍較小造成的對比度較低的問題,目的就是將輸出圖像的灰度級放大到指定的程度,使得圖像中的細節看起來更加清晰。對比 度增強有幾種常用的方法,如線性變換、分段線性變換、伽馬變換、直方圖正規化、直方圖均衡化、局部自適應直方圖均衡化等。
灰度直方圖是圖像灰度級的函數, 用來描述每個灰度級在圖像矩陣中的像素個數或者佔有率(概率)。
OpenCV提供了函數 calcHist 來實現直方圖的構建,但是在計算8點陣圖的灰度直方圖 時,它使用起來略顯復雜。下面是OpenCV源碼
可以定義函數 calcGrayHist 來計算灰度直方圖,其中輸入參數為8點陣圖,將返回的灰度直方圖存儲為一個1行256列的 Mat 類型。
圖像對比度是通過灰度級范圍來度量的,而灰度級范圍可通過觀察灰度直方圖得到,灰度級范圍越大代表對比度越高;反之,對比度越低,低對比度的圖像在視覺上給人的感覺是看起來不夠清晰,所以通過演算法調整圖像的灰度值,從而調整圖像的對比度是有必要的。最簡單的一種對比度增強方法是通過灰度值的線性變換來實現的。
當a=1,b=0時,O為I的一個副本;如果a>1,則輸出圖像O的對 比度比I 有所增大;如果0<a< 1,則O的對比度比I有所減小。而b值的改變,影響的是輸出圖像的亮度,當b> 0時,亮度增加;當b<0時,亮度減小。
在OpenCV中實現一個常數與矩陣相乘有多種方式。
1、convertTo
註:當輸出矩陣的數據類型是 CV_8U 時, 大於255的值會自動截斷為255
2、矩陣乘法運算
使用乘法運算符「*」, 無論常數是什麼數據類型, 輸出矩陣的數據類型總是和輸入矩陣的數據類型相同,當數據類型是 CV_8U 時,在返回值中將大於255的值自動截斷為255。
3、convertScaleAbs
直方圖正規化是一種自動選取a和b的值的線性變換方法。
利用 minMaxLoc 函數不僅可以計算出矩陣中的最大值和最小值, 而且可以求出最大 值的位置和最小值的位置。 當然,
在使用過程中如果只想得到最大值和最小值, 則將其 他的變數值設為 NULL 即可。
OpenCV提供的函數: normalize()
使用函數 normalize 對圖像進行對比度增強時, 經常令參數 norm_type=NORM_MINMAX , 和直方圖正規化原理詳解中提到的計算方法是相同的, 參數 alpha 相當於 Omax , 參數 beta 相當於 Omin 。 注意, 使用 normalize 可以處理多通道矩陣, 分別對每一個通道進行正規化操作。
非線性變換 。
假設輸入圖像為I,寬為W、 高為H,首先將其灰度值歸一化到[0,1]范圍,對於8位 圖來說,除以255即可。 I (r, c) 代表歸一化後的第r行第c列的灰度值, 輸出圖像記為 O, 伽馬變換就是令 O(r, c) =I(r, c) γ , 0≤r<H, 0≤c< W,
當γ=1時, 圖像不變。 如果圖像整體或者感興趣區域較暗, 則令0< γ< 1可以 增加圖像對比度; 相反, 如果圖像整體或者感興趣區域較亮, 則令γ>1可以降低圖像對比度。
伽馬變換在提升對比度上有比較好的效果, 但是需要手動調節γ值。
全局直方圖均衡化操作是對圖像I進行改變, 使得輸出圖像O的灰度直方圖 hist O 是「平」的, 即每一個灰度級的像素點個數是「相等」的。 注意,其實這里的「相等」不是嚴格意義上的等於, 而是約等於,
上述分別為I和O的累加直方圖
總結,對於直方圖均衡化的實現主要分四個步驟:
OpenCV實現的直方圖均衡化函數 equalize-Hist , 其使用方法很簡單, 只支持對 8點陣圖 的處理。
雖然全局直方圖均衡化方法對提高對比度很有效,但是均衡化處理以後暗區域的雜訊可能會被放大,變得清晰可 見,而亮區域可能會損失信息。為了解決該問題, 提出了自適應直方圖均衡化(Aptive Histogram Equalization) 方法。
自適應直方圖均衡化首先將圖像劃分為不重疊的區域塊(tiles) ,然後對每一個塊分別進行直方圖均衡化。 顯然, 在沒有雜訊影響的情況下, 每一個小區域的灰度直方圖會被限制在一個小的灰度級范圍內; 但是如果有雜訊, 每一個分割的區域塊執行直方圖均衡化後, 雜訊會被放大。為了避免出現雜訊這種情況, 提出了「限制對比度」(Contrast Limiting) [3],如果直方圖的bin超過了提前預設好的「限制對比度」, 那麼會被裁減, 然 後將裁剪的部分均勻分布到其他的bin, 這樣就重構了直方圖。
OpenCV提供的函數 createCLAHE 構建指向 CLAHE 對象的指針, 其中默認設置「限制 對比度」為40,塊的大小為8×8。
B. photoshop 怎麼實現直方圖均衡化
圖形處理中有一種對比度變換,像顯示器就有對比度調節,PhotoShop也有圖片的對比度修改,對比度的提高可以使圖像細節清晰,相反,對比度的減小可以隱藏圖像的細節,在一定程度上使圖像柔和。
對比度變換其中一種比較簡單的方法是直方圖均衡化。
所謂直方圖就是在某一灰度級的象素個數占整幅圖像的象素比 h=nj/N,其中nj是灰度級在j的象素數,N是總象素數,掃描整幅圖像得出的h的離散序列就是圖像的直方圖,h求和必然=1,所以直方圖可以看成是象素對於灰度的概率分布函數。
直方圖是高低不齊的,因為象素灰度是隨機變化的,直方圖均衡化就是用一定的演算法使直方圖大致平和。
演算法如下:
對於一個直方圖
設 Pr(r)是原始圖像直方圖,Ps(s)是均衡化的直方圖,
由於其是一個概率分布函數
所以有 Ps(s)ds=Pr(r)dr (編輯關系,ds,dr是積分變數)
因為要進行均衡化,令 Ps(s)=1,
得 ds=Pr(r)dr/1
兩邊積分得 s=F Pr(r)dr (因為編輯關系,左邊F表示積分符號....-__-++)
數字圖像是離散的,因此離散化上式得
sk=E{j=0,k}(nj/N) 左式k,j是離散量下標,因為編輯關系,E{0,k}表示下標0到k的連加符號,N是象素總數
由此得出每一象素的sk為均衡化後的正規化灰度(即灰度正規化到[0,1]),統計sk即可得出均衡化後的直方圖。
在均衡化過程中可以對每一象素映射到新的實際灰度值sk*255,就實現了圖像的變換
(嚴格理論中應該是灰度正規化到[0,1]區間,然後均衡化後的sk還要量化到原始的正規灰度以實現灰度合並,下面的BCB程序並沒有量化,而且255是固定灰度級,因為256色BMP的彩色表就是256個表項)
現在開始實踐
用BCB對一BMP灰度圖像進行直方圖均衡化處理,代碼如下
//----------------------------BCB6代碼
#include <vcl.h>
#pragma hdrstop
#include<stdio.h>
#include "Unit1.h"
#include"File1.h"
#pragma pack(1)
//BMP文件頭
struct BITMAPFILEHEADER_
{
short type;
int bfSize;
short re1,re2;
int Offbits;
};
//BMP信息頭
struct BITMAPINFO_
{
long size;
long width,height;
short planes,bitCount;
long comp,sizeImg;
long xpels,ypels;
long used,important;
};
//BMP彩色表項
struct COLOR_
{
char blue,green,red,re;
};
//------將BMP彩色表的數據校正到BCB TColor的數據。
void SwitchColor(long &c)
{
long blue=c& 0x000000ff;
long green=c& 0x0000ff00;
long red=c& 0x00ff0000;
c=(blue<<16) | green | (red>>16);
}
void xxx()
{
FILE *f=fopen("f:\\bbs_prev2.bmp","rb");
if(f==NULL) /*判斷文件是否打開成功*/
{
ShowMessage("File open error");
return;
}
fseek(f,0,0);//移動到開頭
//----------讀BMP文件頭
BITMAPFILEHEADER_ *bmph=new BITMAPFILEHEADER_();
if(fread((char*)bmph,sizeof(BITMAPFILEHEADER_),1,f)==NULL)
{
ShowMessage("File read error");
return;
}
//-----------讀BMP信息頭
BITMAPINFO_ *bmpi=new BITMAPINFO_();
if(fread((char*)bmpi,sizeof(BITMAPINFO_),1,f)==NULL)
{
ShowMessage("File read error2");
return;
}
//--------------讀彩色表
long *c=new long[bmph->Offbits-sizeof(BITMAPFILEHEADER_)-sizeof(BITMAPINFO_)];
fread((char*)c,bmph->Offbits-sizeof(BITMAPFILEHEADER_)-sizeof(BITMAPINFO_),1,f);
//----------顯示一些信息
Form1->Edit1->Text=IntToStr(bmph->bfSize);
Form1->Edit2->Text=IntToStr(bmpi->width);
Form1->Edit3->Text=IntToStr(bmpi->height);
Form1->Edit4->Text=IntToStr(bmpi->comp);
Form1->Edit5->Text=IntToStr(bmpi->used);
int i,j,k,wc;
long N=bmph->bfSize- bmph->Offbits;//象素總數
unsigned char *image=new char[N]; //點陣圖矩陣
unsigned char *newimage=new char[N];//變換後的點陣圖矩陣
fread(image,N,1,f);//讀入點陣圖矩陣
//---------直方圖數列初始化
//---------直方圖數列用來存儲正規化後的灰度
double *h=new double[255];//255個灰度級,保存原始圖像正規化灰度直方圖數據
for(i=0;i<255;i++)
h[i]=0.0;
double *nh=new double[255];//255個灰度級,保存變換後的圖像正規化灰度直方圖
for(i=0;i<255;i++)
nh[i]=0.0;
long *count=new long[255]; //每一灰度級的象素數量統計
for(i=0;i<255;i++)
count[i]=0;
for(i=0;i<N;i++)
{
count[image[i]]++;
}
//-----正規化灰度概率統計
for(i=0;i<255;i++)
{
h[i]=count[i]/(double)N;
}
//------正規化新灰度圖
double hc;
for(i=0;i<N;i++)
{
hc=0;
for(j=0;j<image[i];j++)
hc+=h[j];
nh[image[i]]+=hc; //保存新正規化灰度圖
newimage[i]=hc*255; //保存新圖像灰度索引
}
//----------顯示直方圖
for(i=0;i<255;i++)
{
//原始直方圖
Form1->Canvas->MoveTo(10+i,200);
Form1->Canvas->LineTo(10+i,200+h[i]*N);
//新直方圖
Form1->Canvas->MoveTo(300+i,200);
Form1->Canvas->LineTo(300+i,200+nh[i]*255);
}
//------顯示圖形
TColor *tc;
if(bmpi->width%4==0)//-----------因為BMP圖像4位元組對齊
wc=bmpi->width/4*4;
else
wc=(bmpi->width/4+1)*4;
long a;
long pos=0;
for( i=0;i<bmpi->height;i++)
{
for(j=0;j<wc;j++)
{
//-----原始圖形
a= c[image[pos]];
SwitchColor(a);
Form1->Canvas->Pixels[10+j][600-i]=a;
//------新圖形
a= c[newimage[pos]];
SwitchColor(a);
Form1->Canvas->Pixels[300+j][600-i]=a;
pos++;
}
}
fclose(f);
}
這個程序使用256色BMP文件,但程序代碼是針對灰度圖像的,用於彩色圖像時得出一些古怪色彩配合而已。
在對灰度圖像均衡化時
如果原始圖像對比度本來就很高,如果再均衡化則灰度調和,對比度降低。
在泛白緩和的圖像中,由於均衡化過程中會合並一些象素灰度,則會增大對比度,這里255灰度級太多,合並不明顯。
http://hi..com/j_fo/blog/item/09a6adc3f8078855b319a8ac.html
還有詳細的說明和圖解
C. 數字圖像處理Python實現圖像灰度變換、直方圖均衡、均值濾波
import CV2
import
import numpy as np
import random
使用的是pycharm
因為最近看了《銀翼殺手2049》,裡面Joi實在是太好看了所以原圖像就用Joi了
要求是灰度圖像,所以第一步先把圖像轉化成灰度圖像
# 讀入原始圖像
img = CV2.imread('joi.jpg')
# 灰度化處理
gray = CV2.cvtColor(img, CV2.COLOR_BGR2GRAY)
CV2.imwrite('img.png', gray)
第一個任務是利用分段函數增強灰度對比,我自己隨便寫了個函數大致是這樣的
def chng(a):
if a < 255/3:
b = a/2
elif a < 255/3*2:
b = (a-255/3)*2 + 255/6
else:
b = (a-255/3*2)/2 + 255/6 +255/3*2
return b
rows = img.shape[0]
cols = img.shape[1]
cover = .deep(gray)
for i in range(rows):
for j in range(cols):
cover[i][j] = chng(cover[i][j])
CV2.imwrite('cover.png', cover)
下一步是直方圖均衡化
# histogram equalization
def hist_equal(img, z_max=255):
H, W = img.shape
# S is the total of pixels
S = H * W * 1.
out = img.()
sum_h = 0.
for i in range(1, 255):
ind = np.where(img == i)
sum_h += len(img[ind])
z_prime = z_max / S * sum_h
out[ind] = z_prime
out = out.astype(np.uint8)
return out
covereq = hist_equal(cover)
CV2.imwrite('covereq.png', covereq)
在實現濾波之前先添加高斯雜訊和椒鹽雜訊(代碼來源於網路)
不知道這個椒鹽雜訊的名字是誰起的感覺隔壁小孩都饞哭了
用到了random.gauss()
percentage是雜訊佔比
def GaussianNoise(src,means,sigma,percetage):
NoiseImg=src
NoiseNum=int(percetage*src.shape[0]*src.shape[1])
for i in range(NoiseNum):
randX=random.randint(0,src.shape[0]-1)
randY=random.randint(0,src.shape[1]-1)
NoiseImg[randX, randY]=NoiseImg[randX,randY]+random.gauss(means,sigma)
if NoiseImg[randX, randY]< 0:
NoiseImg[randX, randY]=0
elif NoiseImg[randX, randY]>255:
NoiseImg[randX, randY]=255
return NoiseImg
def PepperandSalt(src,percetage):
NoiseImg=src
NoiseNum=int(percetage*src.shape[0]*src.shape[1])
for i in range(NoiseNum):
randX=random.randint(0,src.shape[0]-1)
randY=random.randint(0,src.shape[1]-1)
if random.randint(0,1)<=0.5:
NoiseImg[randX,randY]=0
else:
NoiseImg[randX,randY]=255
return NoiseImg
covereqg = GaussianNoise(covereq, 2, 4, 0.8)
CV2.imwrite('covereqg.png', covereqg)
covereqps = PepperandSalt(covereq, 0.05)
CV2.imwrite('covereqps.png', covereqps)
下面開始均值濾波和中值濾波了
就以n x n為例,均值濾波就是用這n x n個像素點灰度值的平均值代替中心點,而中值就是中位數代替中心點,邊界點周圍補0;前兩個函數的作用是算出這個點的灰度值,後兩個是對整張圖片進行
#均值濾波模板
def mean_filter(x, y, step, img):
sum_s = 0
for k in range(x-int(step/2), x+int(step/2)+1):
for m in range(y-int(step/2), y+int(step/2)+1):
if k-int(step/2) 0 or k+int(step/2)+1 > img.shape[0]
or m-int(step/2) 0 or m+int(step/2)+1 > img.shape[1]:
sum_s += 0
else:
sum_s += img[k][m] / (step*step)
return sum_s
#中值濾波模板
def median_filter(x, y, step, img):
sum_s=[]
for k in range(x-int(step/2), x+int(step/2)+1):
for m in range(y-int(step/2), y+int(step/2)+1):
if k-int(step/2) 0 or k+int(step/2)+1 > img.shape[0]
or m-int(step/2) 0 or m+int(step/2)+1 > img.shape[1]:
sum_s.append(0)
else:
sum_s.append(img[k][m])
sum_s.sort()
return sum_s[(int(step*step/2)+1)]
def median_filter_go(img, n):
img1 = .deep(img)
for i in range(img.shape[0]):
for j in range(img.shape[1]):
img1[i][j] = median_filter(i, j, n, img)
return img1
def mean_filter_go(img, n):
img1 = .deep(img)
for i in range(img.shape[0]):
for j in range(img.shape[1]):
img1[i][j] = mean_filter(i, j, n, img)
return img1
完整main代碼如下:
if __name__ == "__main__":
# 讀入原始圖像
img = CV2.imread('joi.jpg')
# 灰度化處理
gray = CV2.cvtColor(img, CV2.COLOR_BGR2GRAY)
CV2.imwrite('img.png', gray)
rows = img.shape[0]
cols = img.shape[1]
cover = .deep(gray)
for i in range(rows):
for j in range(cols):
cover[i][j] = chng(cover[i][j])
CV2.imwrite('cover.png', cover)
covereq = hist_equal(cover)
CV2.imwrite('covereq.png', covereq)
covereqg = GaussianNoise(covereq, 2, 4, 0.8)
CV2.imwrite('covereqg.png', covereqg)
covereqps = PepperandSalt(covereq, 0.05)
CV2.imwrite('covereqps.png', covereqps)
meanimg3 = mean_filter_go(covereqps, 3)
CV2.imwrite('medimg3.png', meanimg3)
meanimg5 = mean_filter_go(covereqps, 5)
CV2.imwrite('meanimg5.png', meanimg5)
meanimg7 = mean_filter_go(covereqps, 7)
CV2.imwrite('meanimg7.png', meanimg7)
medimg3 = median_filter_go(covereqg, 3)
CV2.imwrite('medimg3.png', medimg3)
medimg5 = median_filter_go(covereqg, 5)
CV2.imwrite('medimg5.png', medimg5)
medimg7 = median_filter_go(covereqg, 7)
CV2.imwrite('medimg7.png', medimg7)
medimg4 = median_filter_go(covereqps, 7)
CV2.imwrite('medimg4.png', medimg4)