1. 程式人生 > >【數字影象處理系列三】影象增強:線性、 分段線性、 對數、 反對數、 冪律(伽馬)變換、直方圖均衡

【數字影象處理系列三】影象增強:線性、 分段線性、 對數、 反對數、 冪律(伽馬)變換、直方圖均衡

本系列python版本:python3.5.4
本系列opencv-python版本:opencv-python3.4.2.17
本系列使用的開發環境是jupyter notebook,是一個python的互動式開發環境,測試十分方便,並集成了vim操作,安裝教程可參考:windows上jupyter notebook主題背景、字型及擴充套件外掛配置(整合vim環境)


上文【數字影象處理系列二】基本概念:亮度、對比度、飽和度、銳化、解析度 中我們一起學習了數字影象概念和形成原理,並在實踐和對比中一起分析了影象亮度、對比度、飽和度、銳化、解析度等屬性。影象的基礎知識介紹完畢,此後將開始著重介紹數字影象處理和分析,本文便從 影象灰度變換增強

開始說起,這在影象預處理中也可以說是很重要的了,槓~~

老規矩,先上圖:
在這裡插入圖片描述

經過影象增強後的圖片:
在這裡插入圖片描述





一、影象增強知識預熱



1、為啥要做影象增強

在影象形成的過程中,存在很多因素影響影象的清晰度,列舉幾個常見影響影象清晰度因素如下:

  • 光照不夠均勻,這會造成影象灰度過於集中
  • 由CCD(攝像頭)獲得影象時金A/D(數模)轉換、線路傳送時產生噪聲汙染,影響影象質量

因此必須在影象處理分析之前對影象的質量進行改善,改善方法分為兩類:

  • 影象增強
  • 影象復原

影象增強不考慮影象質量下降的原因,只將影象中感興趣的特徵有選擇的突出,而衰減不需要的特徵,它的目的主要是提高影象的可懂度; 影象復原技術與增強技術不同,它需要了解影象質量下降的原因,首先要建立"降質模型",再利用該模型,恢復原始影象。

本文便重點研究一下影象增強技術,影象增強主要是以對比度和閾值處理為目的,且都是在空間域進行的操作。我們假設 f(x,y) 是輸入影象,g(x,y) 為經過增強變換處理後的影象,則有:

這裡的 T 便是在點 (x,y) 的鄰域定義的關於 f 的運算元,根據鄰域的不同,影象增強分為兩種:

  • 鄰域處理技術
  • 點處理技術,又稱 影象灰度變換增強技術,本文重點將討論


2、影象增強鄰域處理技術

空間域處理 g(x,y) = T[f(x, y)] 中點 (x,y) 便是影象的畫素座標,而鄰域是中心在 (x,y) 的矩形,其尺寸比影象要小得多,如下圖所示:
在這裡插入圖片描述

影象增強鄰域處理技術的核心思想便是: 當對 (x,y)

做變換時,變換運算元 T 是對以 (x,y) 為中心的鄰域作用然後得出變換結果,如上圖,假如該鄰域的大小是3*3的正方形,運算元 T 定義為 計算該鄰域的平均灰度,如 (x,y) = (100,200),則 g(100,200) 的結果便是計算 f(100,200) 和它的8個鄰點的和,然後在除以9 (即是由鄰域包圍的畫素灰度的平均值),當然這種鄰域處理思想在後續的影象濾波處理中是基本應用,如均值濾波、中值濾波和高斯濾波等都是採用鄰域處理技術。



3、影象增強點處理技術

點處理技術,既是當鄰域的大小為 1*1 時,此時 g 的值僅僅取決於點 (x,y) 處的值,此時 T 稱為灰度變換函式,此種影象增強方式就是影象灰度變換增強,定義公式如下:

其中 s 是變換後的畫素,r 是變換前的畫素,此後討論的線性變換、 分段線性變換、 對數變換、 反對數變換、 冪律(伽馬)變換均是基於此公式為基礎進行的,因此影象灰度變換便是基於點的增強方式,它將每一個畫素的灰度值按照一定的數學變換公式轉換為一個新的灰度值,達到增強影象的效果。





二、影象灰度變換增強



說在前面: 線性變換和分段線性變換一般對灰度影象進行處理,也可以直接對RGB影象進行處理;對數變換、 反對數變換、 冪律(伽馬)變換則是直接對RGB影象進行相應的影象增強,達到自己想要的結果

如果對RGB影象進行變換之後需要多兩部操作:(1) 影象歸一化、(2) 影象畫素值型別轉化為np.uint8,需要用到兩個函式,在這裡做簡單介紹,後續示例程式中將會使用到:

函式cv2.normalize()

#數字影象歸一化操作
cv2.normalize(src, dst, alpha, beta, norm_type, dtype, mask) → dst

引數解釋:

  • src: 原影象物件

  • dst: 經過轉化後的影象物件

  • alpha: 歸一化後灰度畫素最小值,一般為0

  • beta: 歸一化後灰度畫素最大值,一般為255

  • norm_type: 歸一化的型別,可以取以下值

    (1) cv2.NORM_MINMAX: 陣列的數值被平移或縮放到一個指定的範圍,線性歸一化,一般較常用
    (2) cv2.NORM_INF:此型別的定義沒有查到,根據OpenCV 1的對應項,可能是歸一化陣列的C-範數(絕對值的最大值)
    (3) cv2.NORM_L1 : 歸一化陣列的L1-範數(絕對值的和)
    (4) cv2.NORM_L2: 歸一化陣列的(歐幾里德)L2-範數

  • dtype: 為負數時,輸出影象畫素值的type與輸入相同,一般使用預設

  • mask: 操作掩膜,用於指示函式是否僅僅對指定的元素進行操作,一般使用預設


函式cv2.convertScaleAbs()

#該函式較簡單,便是將原影象的畫素值均轉化成型別為np.uint8
dst = cv2.convertScaleAbs(src)



1、線性變換

影象增強線性變換主要可以對影象的對比度和亮度進行調整,線性變換公式如下:

引數 a 影響影象的對比度,引數 b 影響影象的亮度,具體分為可分為以下幾種情況:

  • a>1: 增強影象的對比度,影象看起來更加清晰
  • a<1: 減小了影象的對比度, 影象看起來變暗
  • a<0 and b=0:影象的亮區域變暗,暗區域變亮
  • a=1 and b≠0:影象整體的灰度值上移或者下移,也就是影象整體變亮或者變暗,不會改變影象的對比度,b>0時影象變亮,b<0時影象變暗
  • a=-1 and b=255:影象翻轉

上程式碼:

import cv2
import random
import imutils
import numpy as np

#彩色影象每個畫素值是[x,y,z], 灰度影象每個畫素值便是一個np.uint8
image = cv2.imread('E:/peking_rw/ocr_project/base_prehandle/img/ali.jpg')
gray_img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

#影象大小調整
ori_h, ori_w = image.shape[:2]
height, width = gray_img.shape[:2]
image = cv2.resize(image, (int(ori_w/ori_h*400), 400), interpolation=cv2.INTER_CUBIC)
gray_img = cv2.resize(gray_img, (int(width/height*400), 400), interpolation=cv2.INTER_CUBIC)

#a<0 and b=0: 影象的亮區域變暗,暗區域變亮
a, b = -0.5, 0
new_img1 = np.ones((gray_img.shape[0], gray_img.shape[1]), dtype=np.uint8)
for i in range(new_img1.shape[0]):
    for j in range(new_img1.shape[1]):
        new_img1[i][j] = gray_img[i][j]*a + b
        
#a>1: 增強影象的對比度,影象看起來更加清晰
a, b = 1.5, 20
new_img2 = np.ones((gray_img.shape[0], gray_img.shape[1]), dtype=np.uint8)
for i in range(new_img2.shape[0]):
    for j in range(new_img2.shape[1]):
        if gray_img[i][j]*a + b > 255:
            new_img2[i][j] = 255
        else:
            new_img2[i][j] = gray_img[i][j]*a + b

#a<1: 減小了影象的對比度, 影象看起來變暗
a, b = 0.5, 0
new_img3 = np.ones((gray_img.shape[0], gray_img.shape[1]), dtype=np.uint8)
for i in range(new_img3.shape[0]):
    for j in range(new_img3.shape[1]):
        new_img3[i][j] = gray_img[i][j]*a + b

#a=1且b≠0, 影象整體的灰度值上移或者下移, 也就是影象整體變亮或者變暗, 不會改變影象的對比度
a, b = 1, -50
new_img4 = np.ones((gray_img.shape[0], gray_img.shape[1]), dtype=np.uint8)
for i in range(new_img4.shape[0]):
    for j in range(new_img4.shape[1]):
        pix = gray_img[i][j]*a + b
        if pix > 255:
            new_img4[i][j] = 255
        elif pix < 0:
            new_img4[i][j] = 0
        else:
            new_img4[i][j] = pix

#a=-1, b=255, 影象翻轉
new_img5 = 255 - gray_img

cv2.imshow('origin', imutils.resize(image, 800))
cv2.imshow('gray', imutils.resize(gray_img, 800))
cv2.imshow('a<0 and b=0', imutils.resize(new_img1, 800))
cv2.imshow('a>1 and b>=0', imutils.resize(new_img2, 800))
cv2.imshow('a<1 and b>=0', imutils.resize(new_img3, 800))
cv2.imshow('a=1 and b><0', imutils.resize(new_img4, 800))
cv2.imshow('a=-1 and b=255', imutils.resize(new_img5, 800))
if cv2.waitKey(0) == 27:
    cv2.destroyAllWindows()

程式碼執行效果,原灰度影象:
在這裡插入圖片描述

通過對影象對比度、亮度增強之後效果如下:

在這裡插入圖片描述

注: 明白一點就是影象增強從來沒有通用的理論,在不同的場景下會有不同的需求,影象灰度增強最簡單的線性變換可以滿足對影象對比度和亮度進行隨意的調整,得到自己想要的結果




2、分段線性變換

分段線性變換函式來增強影象對比度的方法實際是增強原圖各部分的反差,即增強輸入影象中感興趣的灰度區域,相對抑制那些不感興趣的灰度區域。增分段線性函式的主要優勢在於它的形式可任意合成,而其缺點是需要更多的使用者輸入。分段線性函式通用公式如下:

下面我們便通過例項來感受一下分段線性函式對於影象增強的幾個方面的應用,並體會上面公式的含義!


2.1 分段線性變換應用之對比度拉伸、閾值處理

低對比度影象一般由光照不足,成像感測器動態範圍太小,影象獲取過程中鏡頭光圈設定錯誤引起,對比度拉伸是擴充套件影象灰度級動態範圍的處理。變換運算元 T 如下所示:

在這裡插入圖片描述

根據r1、s1、r2、s2可分為以下兩種增強情況:

  • r1 <= r2、s1 <= s2: 對比度拉伸,增強感興趣區域
  • r1 = r2: 閾值處理,產生一個二值影象


(1) 對比度拉伸例項演示

下面令(r1, s1) = (rmin,0)、(r2, s2) = (rmax,L-1),其中 rmin、rmax 分別代表數字影象中最小灰度級和最大灰度級,此變換函式將灰度級由原來範圍線性拉伸到整個範圍[0,L-1], 上示例程式碼:

import cv2
import imutils
import numpy as np

image = cv2.imread('E:/peking_rw/ocr_project/base_prehandle/img/pollen.jpg')
gray_img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

#在灰度圖進行分段線性對比度拉伸
#此種方式變換函式把灰度級由原來的線性拉伸到整個範圍[0, 255]
r_min, r_max = 255, 0
for i in range(gray_img.shape[0]):
    for j in range(gray_img.shape[1]):
        if gray_img[i, j] > r_max:
            r_max = gray_img[i, j]
        if gray_img[i, j] < r_min:
            r_min = gray_img[i, j]
r1, s1 = r_min, 0
r2, s2 = r_max, 255

precewise_img = np.zeros((gray_img.shape[0], gray_img.shape[1]), dtype=np.uint8)
k1 = s1/r1
k3 = (255-s2)/(255-r2)
k2 = (s2 - s1)/(r2 - r1)
for i in range(gray_img.shape[0]):
    for j in range(gray_img.shape[1]):
        if r1 <= gray_img[i, j] <= r2:
            precewise_img[i, j] = k2*(gray_img[i, j] - r1)
        elif gray_img[i, j] < r1:
            precewise_img[i, j] = k1*gray_img[i, j]
        elif gray_img[i, j] > r2:
            precewise_img[i, j] = k3*(gray_img[i, j] - r2)
            
cv2.imshow('origin image', imutils.resize(image, 480))
cv2.imshow('precewise image', imutils.resize(precewise_img, 480))
if cv2.waitKey(0) == 27:
    cv2.destroyAllWindows()

上述程式碼是在灰度圖片上進行的影象增強,我們也可以直接在原圖上進行影象增強,顯示出來的效果都是相同的,程式碼如下:

import cv2
import imutils
import numpy as np

image = cv2.imread('E:/peking_rw/ocr_project/base_prehandle/img/pollen.jpg')

#直接在原圖上進行分段線性對比度拉伸
#此種方式變換函式把灰度級由原來的線性拉伸到整個範圍[0, 255] 
r_min, r_max = 255, 0
for i in range(image.shape[0]):
    for j in range(image.shape[1]):
        for k in range(image.shape[2]):
            if image[i, j, k] > r_max:
                r_max = image[i, j, k]
            if image[i, j, k] < r_min:
                r_min = image[i, j, k]
r1, s1 = r_min, 0
r2, s2 = r_max, 255
k1 = s1/r1
k3 = (255-s2)/(255-r2)
k2 = (s2 - s1)/(r2 - r1)

precewise_img = np.zeros((image.shape[0], image.shape[1], 3), dtype=np.float32)
for i in range(image.shape[0]):
    for j in range(image.shape[1]):
        for k in range(image.shape[2]):
            if r1 <= image[i, j, k] <= r2:
                precewise_img[i, j, k] = k2*(image[i, j, k] - r1)
            elif image[i, j, k] < r1:
                precewise_img[i, j, k] = k1*gray_img[i, j, k]
            elif image[i, j, k] > r2:
                precewise_img[i, j, k] = k3*(gray_img[i, j, k] - r2)

#原圖中做分段線性變化後需要對影象進行歸一化操作,並將資料型別轉換到np.uint8
cv2.normalize(precewise_img, precewise_img, 0, 255, cv2.NORM_MINMAX)
precewise_img = cv2.convertScaleAbs(precewise_img)

cv2.imshow('origin image', imutils.resize(image, 480))
cv2.imshow('precewise image', imutils.resize(precewise_img, 480))
if cv2.waitKey(0) == 27:
    cv2.destroyAllWindows()

花粉原圖和經過對比度拉伸後的影象效果如下:
在這裡插入圖片描述



(2) 閾值處理例項演示

import cv2
import imutils
import numpy as np

image = cv2.imread('E:/peking_rw/ocr_project/base_prehandle/img/pollen.jpg')
gray_img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

#閾值處理函式:當r1=r2, s1=0, s2=L-1時,此時分段線性函式便是閾值處理函式
plist = []
for i in range(gray_img.shape[0]):
    for j in range(gray_img.shape[1]):
        plist.append(gray_img[i, j])
r_avg = int(sum(plist)/len(plist))
thresh_img = np.zeros((gray_img.shape[0], gray_img.shape[1]), dtype=np.uint8)
for i in range(gray_img.shape[0]):
    for j in range(gray_img.shape[1]):
        if gray_img[i, j] < r_avg:
            thresh_img[i, j] = 0
        else:
            thresh_img[i, j] = 255

cv2.imshow('origin image', imutils.resize(image, 480))
cv2.imshow('thresh image', imutils.resize(thresh_img, 480))
if cv2.waitKey(0) == 27:
    cv2.destroyAllWindows()

經過閾值處理後的花粉影象如下所示:



2.2 分段線性變換應用之灰度級分層

為了在數字影象中突出我們感興趣的灰度級區域 [A,B],在實際情況下可以有兩種處理方式,對應下圖:

  • 突出灰度範圍在 [A,B] 的區域,將其他區域灰度級降低到一個更低的級別
  • 突出灰度範圍在 [A,B] 的區域,其他區域保持原灰度級不變

在這裡插入圖片描述

上示例程式碼(下面展示的是:其他區域保持原灰度級不變的情況)

import cv2
import imutils
import numpy as np

#在某一範圍(A, B)突出灰度,其他灰度值保持不變
image = cv2.imread('E:/peking_rw/ocr_project/base_prehandle/img/kidney.jpg')
gray_img = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

r_left, r_right = 150, 230
r_min, r_max = 0, 255
level_img = np.zeros((gray_img.shape[0], gray_img.shape[1]), dtype=np.uint8)
for i in range(gray_img.shape[0]):
    for j in range(gray_img.shape[1]):
        if r_left <= gray_img[i, j] <= r_right:
            level_img[i, j] = r_max
        else:
            level_img[i, j] = gray_img[i, j]
            
cv2.imshow('origin image', imutils.resize(image, 480))
cv2.imshow('level image', imutils.resize(level_img, 480))
if cv2.waitKey(0) == 27:
    cv2.destroyAllWindows()

原圖和效果圖如下所示:
在這裡插入圖片描述




3、對數變換

對數變換將影象的低灰度值部分擴充套件,將其高灰度值部分壓縮,以達到強調影象低灰度部分的目的;同時可以很好的壓縮畫素值變化較大的影象的動態範圍,目的是突出我們需要的細節。反對數變換則與對數函式不同的是,強調的是影象的高灰度部分,對數變換公式如下:

下面示例將演示對數變換:

import cv2
import imutils
import numpy as np

image = cv2.imread('E:/peking_rw/ocr_project/base_prehandle/img/DFT_no_log.jpg')
log_img = np.zeros((image.shape[0], image.shape[1], 3), dtype=np.float32)
for i in range(image.shape[0]):
    for j in range(image.shape[1]):
        log_img[i, j, 0] = math.log(1 + image[i, j, 0])
        log_img[i, j, 1] = math.log(1 + image[i, j, 1])
        log_img[i, j, 2] = math.log(1 + image[i, j, 2])
cv2.normalize(log_img, log_img, 0, 255, cv2.NORM_MINMAX)
log_img = cv2.convertScaleAbs(log_img)
cv2.imshow('image', imutils.resize(image, 400))
cv2.imshow('log transform', imutils.resize(log_img, 400))
if cv2.waitKey(0) == 27:
    cv2.destroyAllWindows()

傅立葉頻譜和經過對數變換增強後的頻譜影象
在這裡插入圖片描述




4、冪律變換

冪律變換主要用於影象的校正,對漂白的圖片或者是過黑的圖片進行修正,冪律變換的公式如下:

根據 φ 的大小,主要可分為一下兩種情況:

  • φ > 1: 處理漂白的圖片,進行灰度級壓縮
  • φ < 1: 處理過黑的圖片,對比度增強,使得細節看的更加清楚

4.1 當φ > 1時示例如下:

#冪律變換 φ>1
image = cv2.imread('E:/peking_rw/ocr_project/base_prehandle/img/aerial.jpg')
gamma_img1 = np.zeros((image.shape[0], image.shape[1], 3), dtype=np.float32)
for i in range(image.shape[0]):
    for j in range(image.shape[1]):
        gamma_img1[i, j, 0] = math.pow(image[i, j, 0], 5)
        gamma_img1[i, j, 1] = math.pow(image[i, j, 1], 5)
        gamma_img1[i, j, 2] = math.pow(image[i, j, 2], 5)
cv2.normalize(gamma_img1, gamma_img1, 0, 255, cv2.NORM_MINMAX)
gamma_img1 = cv2.convertScaleAbs(gamma_img1)
cv2.imshow('image', imutils.resize(image, 400))
cv2.imshow('gamma1 transform', imutils.resize(gamma_img1, 400))
if cv2.waitKey(0) == 27:
    cv2.destroyAllWindows()

漂白圖片和經過冪律變換後圖像對比效果:
在這裡插入圖片描述


4.2 當φ< 1時示例如下:

#冪律變換,φ<1
image = cv2.imread('E:/peking_rw/ocr_project/base_prehandle/img/fractured_spine.jpg')
gamma_img2 = np.zeros((image.shape[0], image.shape[1], 3), dtype=np.float32)
for i in range(image.shape[0]):
    for j in range(image.shape[1]):
        gamma_img2[i, j, 0] = math.pow(image[i, j, 0], 0.4)
        gamma_img2[i, j, 1] = math.pow(image[i, j, 1], 0.4)
        gamma_img2[i, j, 2] = math.pow(image[i, j, 2], 0.4)
cv2.normalize(gamma_img2, gamma_img2, 0, 255, cv2.NORM_MINMAX)
gamma_img2 = cv2.convertScaleAbs(gamma_img2)
cv2.imshow('image', imutils.resize(image, 400))
cv2.imshow('gamma2 transform', imutils.resize(gamma_img2, 400))
if cv2.waitKey(0) == 27:
    cv2.destroyAllWindows()

過黑圖片和經過冪律變換後效果展示圖:
在這裡插入圖片描述





三、直方圖均衡化


說在前面: 數字影象直方圖均衡化目的就是提升影象的對比度,將較亮或者較暗區域的輸入畫素對映到整個區域的輸出畫素,是影象增強一種很好的且方便的方式,因此單獨列為一節進行講解



1、認識影象的直方圖

影象直方圖:反應影象強度分佈的總體概念,寬泛的來說直方圖給出了影象對比度、亮度和強度分佈資訊。下面從理論的角度闡述一下影象直方圖的公式定義

對於灰度級範圍為[0,L-1]的數字影象的直方圖是離散函式:

其中 rk 是第k級灰度值,nk 便是影象中灰度為 rk 的畫素個數。當然如果數字影象長寬維度為MN,則對直方圖歸一化之後的公式為:

下面我們先從直觀的角度來感受一下不同影象對應的直方圖的區別,這對後續直方圖均衡化增強影象理解有很大的幫助

在這裡插入圖片描述


從上圖中我們注意到: 在暗影象中,直方圖的分佈都集中在灰度級的低(暗)端; 亮影象直方圖的分佈集中在灰度級的高階;低對比度影象具有較窄的直方圖,且都集中在灰度級的中部;而高對比度的影象直方圖的分量覆蓋了很寬的灰度範圍,且畫素分佈也相對均勻,此時圖片的效果也相對很不錯。於是我們可以得出結論:若一幅影象的畫素傾向於佔據整個可能的灰度級並且分佈均勻,則該影象有較高的對比度並且影象展示效果會相對好,於是便引出影象直方圖均衡化,對影象會有很強的增強效果



2、影象增強之直方圖均衡化

這裡不詳細介紹直方圖均衡化原理,想了解請看部落格: 直方圖均衡化原理

下面給出直方圖均衡化的實現:

import cv2
import imutils
import numpy as np
import matplotlib.pyplot as plt

wiki_img = cv2.imread('E:/peking_rw/ocr_project/base_prehandle/img/wiki.jpg')
wiki_gray = cv2.cvtColor(wiki_img, cv2.COLOR_BGR2GRAY)

#對影象進行均衡化處理,增強影象對比度
wiki_equ = cv2.equalizeHist(wiki_gray)

hist = cv2.calcHist([wiki_gray], [0], None, [256], [0, 256])
equ_hist = cv2.calcHist([wiki_equ], [0], None, [256], [0, 256])
fig = plt.figure()
ax1 = fig.add_subplot(1, 2, 1)
ax1.plot(hist)
ax2 = fig.add_subplot(1, 2, 2)
ax2.plot(equ_hist)
plt.show()

cv2.imshow('wiki_origin', imutils.resize(wiki_img, 400))
cv2.imshow('wiki_gray', imutils.resize(wiki_gray, 400))
cv2.imshow('wiki_equ', imutils.resize(wiki_equ, 400))
if cv2.waitKey(0) == 27:
    cv2.destroyAllWindows()

直方圖均衡化斥候:直方圖前後對比圖、影象前後對比圖
在這裡插入圖片描述





四、總結



不總結了,本篇分享到此結束,碼字碼到眼睛疼,如果諸位看管覺得還可以幫忙點個贊,後續將持續更新數字影象處理和分析等操作,也歡迎關注博主微信公眾號

在這裡插入圖片描述