1. RGB圖像灰度化
-
分量法
將彩色圖像中的三分量的亮度作為三個灰度圖像的灰度值,可根據應用需要選取一種灰度圖像。
-
最大值法
將彩色圖像中的三分量亮度的最大值作為灰度圖的灰度值。
-
平均值法
將彩色圖像中的三分量亮度求平均得到一個灰度值。
-
加權平均法
根據重要性及其它指標,將三個分量以不同的權值進行加權平均。由於人眼對綠色的敏感最高,對藍色敏感最低,因此,按下式對RGB三分量進行加權平均能得到較合理的灰度圖像。
def rgb2gray(im): # return np.dot(im[..., :3], [0.299, 0.587, 0.144]) weights = np.c_[0.2989, 0.5870, 0.1140] # 按照reps指定的次數在相應的維度上重復A矩陣來構建一個新的矩陣 tile = np.tile(weights, reps=(im.shape[0], im.shape[1], 1)) return np.sum(tile * im, axis=2, dtype=im.dtype)
2. 二值化
import cv2
import numpy as np
# Read image
img = cv2.imread("imori.jpg").astype(np.float)
b = img[:, :, 0].copy()
g = img[:, :, 1].copy()
r = img[:, :, 2].copy()
# Grayscale
out = 0.2126 * r + 0.7152 * g + 0.0722 * b
out = out.astype(np.uint8)
# Binarization
th = 128
out[out < th] = 0
out[out >= th] = 255
# Save result
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
3. OSTU: 大津二值化算法
最大類間方差法是由日本學者大津於1979年提出的,是一種自適應的閾值確定的方法,又叫大津法,簡稱OTSU。它是按圖像的灰度特性,將圖像分成背景和目標2部分。背景和目標之間的類間方差越大,說明構成圖像的2部分的差別越大,當部分目標錯分為背景或部分背景錯分為目標都會導致2部分差別變小。因此,使類間方差最大的分割意味着錯分概率最小。
也就是說:
import cv2
import numpy as np
# Read image
img = cv2.imread("imori.jpg").astype(np.float)
H, W, C = img.shape
# Grayscale
out = 0.2126 * img[..., 2] + 0.7152 * img[..., 1] + 0.0722 * img[..., 0]
out = out.astype(np.uint8)
# Determine threshold of Otsu's binarization
max_sigma = 0
max_t = 0
for _t in range(1, 255):
v0 = out[np.where(out < _t)]
m0 = np.mean(v0) if len(v0) > 0 else 0.
w0 = len(v0) / (H * W)
v1 = out[np.where(out >= _t)]
m1 = np.mean(v1) if len(v1) > 0 else 0.
w1 = len(v1) / (H * W)
sigma = w0 * w1 * ((m0 - m1) ** 2)
if sigma > max_sigma:
max_sigma = sigma
max_t = _t
# Binarization
print("threshold >>", max_t)
th = max_t
out[out < th] = 0
out[out >= th] = 255
# Save result
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
4. Pooling: 最大池化
# Max Pooling
out = img.copy()
H, W, C = img.shape
G = 8
Nh = int(H / G)
Nw = int(W / G)
for y in range(Nh):
for x in range(Nw):
for c in range(C):
out[G*y:G*(y+1), G*x:G*(x+1), c] = np.max(out[G*y:G*(y+1), G*x:G*(x+1), c])
5. 高斯濾波
高斯濾波器是一種可以使圖像平滑的濾波器,用於去除噪聲。可用於去除噪聲的濾波器還有中值濾波器,平滑濾波器、LoG 濾波器。
高斯濾波器將中心像素周圍的像素按照高斯分布加權平均進行平滑化。這樣的(二維)權值通常被稱為卷積核或者濾波器。
但是,由於圖像的長寬可能不是濾波器大小的整數倍,因此我們需要在圖像的邊緣補0。這種方法稱作 Zero Padding。並且權值(卷積核)要進行歸一化操作 ( ∑g=1
)。
權值 g(x,y,s) = 1/ (s*sqrt(2 * pi)) * exp( - (x^2 + y^2) / (2*s^2))
標准差 s = 1.3 的 8 近鄰 高斯濾波器如下:
1 2 1
K = 1/16 * [ 2 4 2 ]
1 2 1
代碼實現:
# Gaussian Filter
K_size = 3
sigma = 1.3
## 6. Zero padding
pad = K_size // 2
out = np.zeros((H + pad*2, W + pad*2, C), dtype=np.float)
out[pad:pad+H, pad:pad+W] = img.copy().astype(np.float)
## 7. Kernel
K = np.zeros((K_size, K_size), dtype=np.float)
for x in range(-pad, -pad+K_size):
for y in range(-pad, -pad+K_size):
K[y+pad, x+pad] = np.exp( -(x**2 + y**2) / (2* (sigma**2)))
K /= (sigma * np.sqrt(2 * np.pi))
K /= K.sum()
tmp = out.copy()
for y in range(H):
for x in range(W):
for c in range(C):
out[pad+y, pad+x, c] = np.sum(K * tmp[y:y+K_size, x:x+K_size, c])
out = out[pad:pad+H, pad:pad+W].astype(np.uint8)
8. 最鄰近插值
使用最鄰近插值將圖像放大1.5倍吧!
Opencv最近鄰插值在圖像放大時補充的像素取最臨近的像素的值。由於方法簡單,所以處理速度很快,但是放大圖像畫質劣化明顯。
使用下面的公式放大圖像吧!I’為放大后圖像,I為放大前圖像,a為放大率,方括號是四舍五入取整操作:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Nereset Neighbor interpolation
def nn_interpolate(img, ax=1, ay=1):
H, W, C = img.shape
aH = int(ay * H)
aW = int(ax * W)
y = np.arange(aH).repeat(aW).reshape(aW, -1)
x = np.tile(np.arange(aW), (aH, 1))
y = np.round(y / ay).astype(np.int)
x = np.round(x / ax).astype(np.int)
out = img[y,x]
out = out.astype(np.uint8)
return out
# Read image
img = cv2.imread("imori.jpg").astype(np.float)
# Nearest Neighbor
out = nn_interpolate(img, ax=1.5, ay=1.5)
9. Canny邊緣檢測
import cv2
import numpy as np
import matplotlib.pyplot as plt
def Canny_step1(img):
# Gray scale
def BGR2GRAY(img):
...
def gaussian_filter(img, K_size=3, sigma=1.3):
...
def sobel_filter(img, K_size=3):
...
def get_edge_angle(fx, fy):
# get edge strength
edge = np.sqrt(np.power(fx, 2) + np.power(fy, 2))
# fx[np.abs(fx) < 1e-5] = 1e-5
fx = np.maximum(fx, 1e-5)
angle = np.arctan(fy / fx)
return edge, angle
def angle_quantization(angle):
angle = angle / np.pi * 180
angle[angle < -22.5] = 180 + angle[angle < -22.5]
_angle = np.zeros_like(angle, dtype=np.uint8)
_angle[np.where(angle <= 22.5)] = 0
_angle[np.where((angle > 22.5) & (angle <= 67.5))] = 45
_angle[np.where((angle > 67.5) & (angle <= 112.5))] = 90
_angle[np.where((angle > 112.5) & (angle <= 157.5))] = 135
return _angle
gray = BGR2GRAY(img)
gaussian = gaussian_filter(gray, K_size=5, sigma=1.4)
fy, fx = sobel_filter(gaussian, K_size=3)
edge, angle = get_edge_angle(fx, fy)
angle = angle_quantization(angle)
return edge, angle
# Read image
img = cv2.imread("imori.jpg").astype(np.float32)
# Canny (step1)
edge, angle = Canny_step1(img)
edge = edge.astype(np.uint8)
angle = angle.astype(np.uint8)
# Save result
cv2.imwrite("out.jpg", edge)
cv2.imshow("result", edge)
cv2.imwrite("out2.jpg", angle)
cv2.imshow("result2", angle)
cv2.waitKey(0)
cv2.destroyAllWindows()
10. Hough Transform: 霍夫變換
霍夫變換,是將坐標由直角座標系變換到極座標系,然后再根據數學表達式檢測某些形狀(如直線和圓)的方法。當直線上的點變換到極座標中的時候,會交於一定的rr、tt的點。這個點即為要檢測的直線的參數。通過對這個參數進行逆變換,我們就可以求出直線方程。
方法如下:
- 我們用邊緣圖像來對邊緣像素進行霍夫變換。
- 在霍夫變換后獲取值的直方圖並選擇最大點。
- 對極大點的r和t的值進行霍夫逆變換以獲得檢測到的直線的參數。
在這里,進行一次霍夫變換之后,可以獲得直方圖。算法如下:
import cv2
import numpy as np
import matplotlib.pyplot as plt
def Canny(img):
def BGR2GRAY(img):
# Gray scale
def gaussian_filter(img, K_size=3, sigma=1.3):
# Gaussian filter for grayscale
def sobel_filter(img, K_size=3):
# sobel filter
def get_edge_angle(fx, fy):
# 同上
def angle_quantization(angle):
# 同上
def non_maximum_suppression(angle, edge):
H, W = angle.shape
_edge = edge.copy()
for y in range(H):
for x in range(W):
if angle[y, x] == 0:
dx1, dy1, dx2, dy2 = -1, 0, 1, 0
elif angle[y, x] == 45:
dx1, dy1, dx2, dy2 = -1, 1, 1, -1
elif angle[y, x] == 90:
dx1, dy1, dx2, dy2 = 0, -1, 0, 1
elif angle[y, x] == 135:
dx1, dy1, dx2, dy2 = -1, -1, 1, 1
if x == 0:
dx1 = max(dx1, 0)
dx2 = max(dx2, 0)
if x == W-1:
dx1 = min(dx1, 0)
dx2 = min(dx2, 0)
if y == 0:
dy1 = max(dy1, 0)
dy2 = max(dy2, 0)
if y == H-1:
dy1 = min(dy1, 0)
dy2 = min(dy2, 0)
if max(max(edge[y, x], edge[y + dy1, x + dx1]), edge[y + dy2, x + dx2]) != edge[y, x]:
_edge[y, x] = 0
return _edge
def hysterisis(edge, HT=100, LT=30):
H, W = edge.shape
# Histeresis threshold
edge[edge >= HT] = 255
edge[edge <= LT] = 0
_edge = np.zeros((H + 2, W + 2), dtype=np.float32)
_edge[1 : H + 1, 1 : W + 1] = edge
## 8 - Nearest neighbor
nn = np.array(((1., 1., 1.), (1., 0., 1.), (1., 1., 1.)), dtype=np.float32)
for y in range(1, H+2):
for x in range(1, W+2):
if _edge[y, x] < LT or _edge[y, x] > HT:
continue
if np.max(_edge[y-1:y+2, x-1:x+2] * nn) >= HT:
_edge[y, x] = 255
else:
_edge[y, x] = 0
edge = _edge[1:H+1, 1:W+1]
return edge
gray = BGR2GRAY(img)
gaussian = gaussian_filter(gray, K_size=5, sigma=1.4)
fy, fx = sobel_filter(gaussian, K_size=3)
edge, angle = get_edge_angle(fx, fy)
angle = angle_quantization(angle)
edge = non_maximum_suppression(angle, edge)
out = hysterisis(edge, 100, 30)
return out
def Hough_Line_step1(edge):
## Voting
def voting(edge):
H, W = edge.shape
drho = 1
dtheta = 1
# get rho max length
rho_max = np.ceil(np.sqrt(H ** 2 + W ** 2)).astype(np.int)
# hough table
hough = np.zeros((rho_max * 2, 180), dtype=np.int)
# get index of edge
ind = np.where(edge == 255)
## hough transformation
for y, x in zip(ind[0], ind[1]):
for theta in range(0, 180, dtheta):
# get polar coordinat4s
t = np.pi / 180 * theta
rho = int(x * np.cos(t) + y * np.sin(t))
# vote
hough[rho + rho_max, theta] += 1
out = hough.astype(np.uint8)
return out
out = voting(edge)
return out
# Read image
img = cv2.imread("thorino.jpg").astype(np.float32)
edge = Canny(img)
out = Hough_Line_step1(edge)
out = out.astype(np.uint8)
# Save result
cv2.imwrite("out.jpg", out)
cv2.imshow("result", out)
cv2.waitKey(0)
cv2.destroyAllWindows()
11. Hessian角點檢測
12. gamma變換
def adjust_gamma(img, gamma=1.0):
invGamma = 1.0/gamma
table = []
for i in range(256):
table.append(((i / 255.0) ** invGamma) * 255)
table = np.array(table).astype("uint8")
return cv2.LUT(img, table)
提到LUT,很多人可能都會覺得這是一個很專業很高深的詞匯,LUT其實就是Look Up Table(顏色查找表)的縮寫,簡單點兒理解就是:通過LUT,我們可以將一組RGB值輸出為另一組RGB值,從而改變畫面的曝光與色彩。
LUT文件就是一個包含了可以改變輸入顏色信息的矩陣數據。LUT本身並不進行運算,只需在其中列舉一系列輸入與輸出數據即可,這些數據呈一一對應的關系,系統按照此對應關系為每一個輸入值查找到與其對應的輸出值,這樣即可完成轉換,也是LUT基本不消耗CPU資源的原因。
當然在實際的 3D-LUT
轉換中,算法要比這復雜很多。
12.1. 一種局部Gamma校正對比度增強算法
今天為大家介紹一個用於解決光照不均勻的圖像自適應校正算法。光照不均勻其實是非常常見的一種狀況,為了提升人類的視覺感受或者是為了提升諸如深度學習之類的算法准確性,人們在解決光照不均衡方面已經有大量的工作。論文:《基於二維伽馬函數的光照不均勻圖像自適應校正算法》。
論文使用了Retinex的多尺度高斯濾波求取光照分量,然后使用了二維Gamma函數針對原圖的HSV空間的V(亮度)分量進行亮度改變,得到結果。原理還是蠻簡單的,因為是中文論文,且作者介紹得很清楚,我就不細說了,可以自己看論文,論文地址見附錄。本文的重點在於對算法步驟的解讀和OpenCV復現。
# %% 基於二維伽馬函數的光照不均勻圖像自適應校正算法
HSV = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
H,S,V = cv2.split(HSV)
# %%
k = min(V.shape)
if k % 2 == 0:
k -= 1
kernel = (k, k)
# %%
V_float = V.astype("float")
SIGMA1 = 15
SIGMA2 = 80
SIGMA3 = 250
q = math.sqrt(2)
F1 = cv2.GaussianBlur(V_float, kernel, SIGMA1 / q)
F2 = cv2.GaussianBlur(V_float, kernel, SIGMA2 / q)
F3 = cv2.GaussianBlur(V_float, kernel, SIGMA3 / q)
F = (F1 + F2 + F3) / 3
# %%
h, w = F.shape
average = np.mean(F)
out = np.zeros((h, w), "uint8")
for i in range(h):
for j in range(w):
y = (average - F[i][j]) / average
gamma = np.power(0.5, y)
out[i][j] = np.power(V[i][j] / 255, gamma) * 255
# uu.imshow(out)
# %%
im_merge = cv2.merge([H,S,out])
im_rgb = cv2.cvtColor(im_merge, cv2.COLOR_HSV2BGR)
# im_rgb = cv2.cvtColor(im_merge, cv2.COLOR_HSV2RGB)
uu.imshow(im_rgb)