Q.21. 直方图归一化( Histogram Normalization )
归一化直方图吧!
有时直方图会存在偏差。比如说,数据集中在 0 处(左侧)的图像全体会偏暗,数据集中在255 处(右侧)的图像会偏亮。如果直方图有所偏向,那么其动态范围( dynamic range )就会较低。为了使人能更清楚地看见图片,让直方图归一化、平坦化是十分必要的。
这种归一化直方图的操作被称作灰度变换(Grayscale Transformation)。像素点取值范围从 [c,d] 转换到 [a,b] 的过程由下式定义。这回我们将imori_dark.jpg的灰度扩展到 [0, 255] 范围。
xout = { a (xin < c)
(b-a)/(d-c) * (xin-c) + a (c <= xin <= d)
b (d < xin)
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread("imori_dark.jpg").astype(np.float)
H, W, C = img.shape
# 范围 [0, 255]
a, b = 0., 255.
vmin = img.min()
vmax = img.max()
out = img.copy()
out[out<a] = a
out[out>b] = b
out = (b-a) / (vmax - vmin) * (out - vmin) + a
out = out.astype(np.uint8)
# 显示直方图
plt.hist(out.ravel(), bins=255, rwidth=0.8, range=(0, 255))
plt.savefig("out_his21.png")
plt.show()
# 保存图像
cv2.imshow("result21", out)
cv2.waitKey(0)
cv2.imwrite("out21.jpg", out)
输入:
输出:
Q.22. 直方图操作
让直方图的平均值m0=128,标准差s0=52吧!
这里并不是变更直方图的动态范围,而是让直方图变得平坦。
可以使用下式将平均值为m标准差为s的直方图变成平均值为m0标准差为s0的直方图:
xout = s0 / s * (xin - m) + m0
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread("imori_dark.jpg").astype(np.float)
H, W, C = img.shape
# 范围 [0, 255]
m0 = 128
s0 = 52
m = np.mean(img)
s = np.std(img)
out = img.copy()
out = s0 / s * (out - m) + m0
out[out < 0] = 0
out[out > 255] = 255
out = out.astype(np.uint8)
# 显示中方图
plt.hist(out.ravel(), bins=255, rwidth=0.8, range=(0, 255))
plt.savefig("out_his22.png")
plt.show()
# 保存结果
cv2.imshow("result22", out)
cv2.waitKey(0)
cv2.imwrite("out22.jpg", out)
输入:
输出:
Q.23. 直方图均衡化( Histogram Equalization )
让均匀化直方图!
直方图均衡化是使直方图变得平坦的操作,是不需要计算上面的问题中的平均值、标准差等数据使直方图的值变得均衡的操作。
均衡化操作由以下式子定义。S是总的像素数;Zmax是像素点的最大取值(在这里是 255);h(z)表示取值为z的累积分布函数:
Z’ = Zmax / S * Sum{i=0:z} h(z)
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread("imori.jpg").astype(np.float)
H, W, C = img.shape
# 直方图平坦
S = H * W * C * 1.
out = img.copy()
sum_h = 0.
z_max = 255.
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)
# 显示直方图
plt.hist(out.ravel(), bins=255, rwidth=0.8, range=(0, 255))
plt.savefig("out_his23.png")
plt.show()
# 保存结果
cv2.imshow("result23", out)
cv2.waitKey(0)
cv2.imwrite("out23.jpg", out)
输入:
输出:
Q.24. 伽玛校正(Gamma Correction)
对imori_gamma.jpg进行伽马校正(c=1,g=2.2)吧!
伽马校正用来对照相机等电子设备传感器的非线性光电转换特性进行校正。如果图像原样显示在显示器等上,画面就会显得很暗。伽马校正通过预先增大 RGB 的值来排除显示器的影响,达到对图像修正的目的。
由于下式引起非线性变换,在该式中,x被归一化,限定在[0,1]范围内。c是常数,g为伽马变量(通常取2.2):
x’ = c * Iin ^ g
因此,使用下面的式子进行伽马校正:
Iout = (1/c * Iin) ^ (1/g)
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread("imori_gamma.jpg").astype(np.float)
# Gammma 校正
c = 1.
g = 2.2
out = img.copy()
out /= 255.#归一化
out = (1/c * out) ** (1/g)#伽马校正
out *= 255
out = out.astype(np.uint8)
# 保存结果
cv2.imshow("result24", out)
cv2.waitKey(0)
cv2.imwrite("out24.jpg", out)
输入:
输出:
Q.25. 最邻近插值( Nearest-neighbor Interpolation )
使用最邻近插值将图像放大1.5倍吧!
最近邻插值在图像放大时补充的像素取最临近的像素的值。由于方法简单,所以处理速度很快,但是放大图像画质劣化明显。
使用下面的公式放大图像吧!I’为放大后图像,I为放大前图像,a为放大率,方括号为取整操作:
I’(x,y) = I([x/a], [y/a])
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread("imori.jpg").astype(np.float)
H, W, C = img.shape
# 最邻近法
a = 1.5
aH = int(a * H)
aW = int(a * W)
y = np.arange(aH).repeat(aW).reshape(aW, -1)
x = np.tile(np.arange(aW), (aH, 1))
y = np.round(y / a).astype(np.int)
x = np.round(x / a).astype(np.int)
out = img[y,x]
out = out.astype(np.uint8)
# 保存图像
cv2.imshow("result25", out)
cv2.waitKey(0)
cv2.imwrite("out25.jpg", out)
输入:
输出:
Q.26. 双线性插值( Bilinear Interpolation )
使用双线性插值将图像放大1.5倍吧!
双线性插值考察4邻域的像素点,根据距离设置权值。虽然计算量增大使得处理时间变长,但是可以有效抑制画质劣化。
- 放大图像的座标(x’,y’)除以放大率a,得到对应原图像的座标floor(x’/a, y’/a)。
- 求原图像的座标(x’/a, y’/a)周围4邻域的座标I(x,y), I(x+1,y), I(x,y+1), I(x+1, y+1)
I(x,y) I(x+1,y)- (x’/a,y’/a)
I(x,y+1) I(x+1,y+1)
- (x’/a,y’/a)
- 分别求这4个点与(x’/a, y’/a)的距离,根据距离设置权重:w = d / Sum d
- 根据下式求得放大后图像(x’,y’)处的像素值: dx = x’/a - x , dy = y’/a - y
I’(x’,y’) = (1-dx)(1-dy)I(x,y) + dx(1-dy)I(x+1,y) + (1-dx)dyI(x,y+1) + dxdyI(x+1,y+1)
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread("imori.jpg").astype(np.float)
H, W, C = img.shape
# 双线性
a = 1.5
aH = int(a * H)
aW = int(a * W)
y = np.arange(aH).repeat(aW).reshape(aW, -1)
x = np.tile(np.arange(aW), (aH, 1))
y = (y / a)
x = (x / a)
ix = np.floor(x).astype(np.int)
iy = np.floor(y).astype(np.int)
ix = np.minimum(ix, W-2)
iy = np.minimum(iy, H-2)
dx = x - ix
dy = y - iy
dx = np.repeat(np.expand_dims(dx, axis=-1), 3, axis=-1)
dy = np.repeat(np.expand_dims(dy, axis=-1), 3, axis=-1)
out = (1-dx) * (1-dy) * img[iy, ix] + dx * (1 - dy) * img[iy, ix+1] + (1 - dx) * dy * img[iy+1, ix] + dx * dy * img[iy+1, ix+1]
out[out>255] = 255
out = out.astype(np.uint8)
# 保存图像
cv2.imshow("result26", out)
cv2.waitKey(0)
cv2.imwrite("out26.jpg", out)
输入:
输出:
Q.27. 双三次插值( Bicubic Interpolation )
使用双三次插值将图像放大1.5倍吧!
双三次插值是双线性插值的扩展,使用邻域16像素进行插值。
I(x-1,y-1) I(x,y-1) I(x+1,y-1) I(x+2,y-1)
I(x-1,y) I(x,y) I(x+1,y) I(x+2,y)
I(x-1,y+1) I(x,y+1) I(x+1,y+1) I(x+2,y+1)
I(x-1,y+2) I(x,y+2) I(x+1,y+2) I(x+2,y+2)
各自像素间的距离由下式决定:
dx1 = x’/a - (x-1) , dx2 = x’/a - x , dx3 = (x+1) - x’/a , dx4 = (x+2) - x’/a
dy1 = y’/a - (y-1) , dy2 = y’/a - y , dy3 = (y+1) - y’/a , dy4 = (y+2) - y’/a
基于距离的权重函数由以下函数取得,a在大部分时候取-1:
h(t) = { (a+2)|t|^3 - (a+3)|t|^2 + 1 (when |t|<=1)
a|t|^3 - 5a|t|^2 + 8a|t| - 4a (when 1<|t|<=2)
0 (when 2<|t|)
利用上面得到的权重,通过下面的式子扩大图像。将每个像素与权重的乘积之和除以权重的和。
I’(x’, y’) = (Sum{i=-1:2}{j=-1:2} I(x+i,y+j) * wxi * wyj) / Sum{i=-1:2}{j=-1:2} wxi * wyj
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread("imori.jpg").astype(np.float32)
H, W, C = img.shape
# 双三次插值
a = 1.5
aH = int(a * H)
aW = int(a * W)
y = np.arange(aH).repeat(aW).reshape(aW, -1)
x = np.tile(np.arange(aW), (aH, 1))
y = (y / a)
x = (x / a)
ix = np.floor(x).astype(np.int)
iy = np.floor(y).astype(np.int)
ix = np.minimum(ix, W-1)
iy = np.minimum(iy, H-1)
dx2 = x - ix
dy2 = y - iy
dx1 = dx2 + 1
dy1 = dy2 + 1
dx3 = 1 - dx2
dy3 = 1 - dy2
dx4 = 1 + dx3
dy4 = 1 + dy3
dxs = [dx1, dx2, dx3, dx4]
dys = [dy1, dy2, dy3, dy4]
def weight(t):
a = -1.
at = np.abs(t)
w = np.zeros_like(t)
ind = np.where(at <= 1)
w[ind] = ((a+2) * np.power(at, 3) - (a+3) * np.power(at, 2) + 1)[ind]
ind = np.where((at > 1) & (at <= 2))
w[ind] = (a*np.power(at, 3) - 5*a*np.power(at, 2) + 8*a*at - 4*a)[ind]
return w
w_sum = np.zeros((aH, aW, C), dtype=np.float32)
out = np.zeros((aH, aW, C), dtype=np.float32)
for j in range(-1, 3):
for i in range(-1, 3):
ind_x = np.minimum(np.maximum(ix + i, 0), W-1)
ind_y = np.minimum(np.maximum(iy + j, 0), H-1)
wx = weight(dxs[i+1])
wy = weight(dys[j+1])
wx = np.repeat(np.expand_dims(wx, axis=-1), 3, axis=-1)
wy = np.repeat(np.expand_dims(wy, axis=-1), 3, axis=-1)
w_sum += wx * wy
out += wx * wy * img[ind_y, ind_x]
out /= w_sum
out[out>255] = 255
out = out.astype(np.uint8)
# 保存结果
cv2.imshow("result27", out)
cv2.waitKey(0)
cv2.imwrite("out27.jpg", out)
输入:
输出:
Q.28. 仿射变换( Afine Transformations )——平行移动
利用仿射变换让图像在x方向上+30,在y方向上-30吧!
仿射变换利用3x3的矩阵来进行图像变换。
变换的方式有平行移动(问题28)、放大缩小(问题29)、旋转(问题30)、倾斜(问题31)等。
原图像记为(x,y),变换后的图像记为(x’,y’)。
图像放大缩小矩阵为下式:
[ x' ] = [a b][x]
y' c d y
另一方面,平行移动按照下面的式子计算:
[ x' ] = [x] + [tx]
y' y + ty
把上面两个式子盘成一个:
x' a b tx x
[ y' ] = [ c d ty ][ y ]
1 0 0 1 1
特别的,使用以下的式子进行平行移动:
x' 1 0 tx x
[ y' ] = [ 0 1 ty ][ y ]
1 0 0 1 1
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread("imori.jpg").astype(np.float32)
H, W, C = img.shape
# 仿射
a = 1.
b = 0.
c = 0.
d = 1.
tx = 30
ty = -30
y = np.arange(H).repeat(W).reshape(W, -1)
x = np.tile(np.arange(W), (H, 1))
out = np.zeros((H+1, W+1, C), dtype=np.float32)
x_new = a * x + b * y + tx
y_new = c * x + d * y + ty
x_new = np.minimum(np.maximum(x_new, 0), W).astype(np.int)
y_new = np.minimum(np.maximum(y_new, 0), H).astype(np.int)
out[y_new, x_new] = img[y, x]
out = out[:H, :W]
out = out.astype(np.uint8)
# 保存图像
cv2.imshow("result28", out)
cv2.waitKey(0)
cv2.imwrite("out28.jpg", out)
输入:
输出:
Q.29. 仿射变换( Afine Transformations )——放大缩小
使用仿射变换,将图片在x方向上放大1.3倍,在y方向上缩小至0.8倍。
在上面的条件下,同时在x方向上像右平移30(+30),在y方向上向上平移30(-30)。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
_img = cv2.imread("imori.jpg").astype(np.float32)
H, W, C = _img.shape
# 仿射
a = 1.3
b = 0.
c = 0.
d = 0.8
tx = 30
ty = -30
img = np.zeros((H+2, W+2, C), dtype=np.float32)
img[1:H+1, 1:W+1] = _img
H_new = np.round(H * d).astype(np.int)
W_new = np.round(W * a).astype(np.int)
out = np.zeros((H_new+1, W_new+1, C), dtype=np.float32)
x_new = np.tile(np.arange(W_new), (H_new, 1))
y_new = np.arange(H_new).repeat(W_new).reshape(H_new, -1)
adbc = a * d - b * c
x = np.round((d * x_new - b * y_new) / adbc).astype(np.int) - tx + 1
y = np.round((-c * x_new + a * y_new) / adbc).astype(np.int) - ty + 1
x = np.minimum(np.maximum(x, 0), W+1).astype(np.int)
y = np.minimum(np.maximum(y, 0), H+1).astype(np.int)
out[y_new, x_new] = img[y, x]
out = out[:H_new, :W_new]
out = out.astype(np.uint8)
# 保存图像
cv2.imshow("result29", out)
cv2.waitKey(0)
cv2.imwrite("out29.jpg", out)
输入:
输出:
Q.30. 仿射变换( Afine Transformations )——旋转
使用仿射变换,逆时针旋转30度。
使用仿射变换,逆时针旋转30度并且能让全部图像显现(也就是说,单纯地做仿射变换会让图片边缘丢失,这一步中要让图像的边缘不丢失,需要耗费一些工夫)。
使用下面的式子进行逆时针方向旋转A度的仿射变换:
x' cosA -sinA tx x
[ y' ] = [ sinA cosA ty ][ y ]
1 0 0 1 1
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
_img = cv2.imread("imori.jpg").astype(np.float32)
H, W, C = _img.shape
# 仿射
A = 30.
theta = - np.pi * A / 180.
a = np.cos(theta)
b = -np.sin(theta)
c = np.sin(theta)
d = np.cos(theta)
tx = 0
ty = 0
img = np.zeros((H+2, W+2, C), dtype=np.float32)
img[1:H+1, 1:W+1] = _img
H_new = np.round(H).astype(np.int)
W_new = np.round(W).astype(np.int)
out = np.zeros((H_new, W_new, C), dtype=np.float32)
x_new = np.tile(np.arange(W_new), (H_new, 1))
y_new = np.arange(H_new).repeat(W_new).reshape(H_new, -1)
adbc = a * d - b * c
x = np.round((d * x_new - b * y_new) / adbc).astype(np.int) - tx + 1
y = np.round((-c * x_new + a * y_new) / adbc).astype(np.int) - ty + 1
x = np.minimum(np.maximum(x, 0), W+1).astype(np.int)
y = np.minimum(np.maximum(y, 0), H+1).astype(np.int)
out[y_new, x_new] = img[y, x]
out = out.astype(np.uint8)
# 保存图像
cv2.imshow("result301", out)
cv2.waitKey(0)
cv2.imwrite("out301.jpg", out)
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
_img = cv2.imread("imori.jpg").astype(np.float32)
H, W, C = _img.shape
# 仿射
A = 30.
theta = - np.pi * A / 180.
a = np.cos(theta)
b = -np.sin(theta)
c = np.sin(theta)
d = np.cos(theta)
tx = 0
ty = 0
img = np.zeros((H + 2, W + 2, C), dtype=np.float32)
img[1:H + 1, 1:W + 1] = _img
H_new = np.round(H).astype(np.int)
W_new = np.round(W).astype(np.int)
out = np.zeros((H_new, W_new, C), dtype=np.float32)
x_new = np.tile(np.arange(W_new), (H_new, 1))
y_new = np.arange(H_new).repeat(W_new).reshape(H_new, -1)
adbc = a * d - b * c
x = np.round((d * x_new - b * y_new) / adbc).astype(np.int) - tx + 1
y = np.round((-c * x_new + a * y_new) / adbc).astype(np.int) - ty + 1
dcx = (x.max() + x.min()) // 2 - W // 2
dcy = (y.max() + y.min()) // 2 - H // 2
x -= dcx
y -= dcy
x = np.minimum(np.maximum(x, 0), W + 1).astype(np.int)
y = np.minimum(np.maximum(y, 0), H + 1).astype(np.int)
out[y_new, x_new] = img[y, x]
out = out.astype(np.uint8)
# 保存图像
cv2.imshow("result302", out)
cv2.waitKey(0)
cv2.imwrite("out302.jpg", out)
输入:
输出: