点运算
点运算是最简单的一类图像处理方法,它不依赖它邻域像素。图像可分为灰度图、RGB图和HSV图,目前本篇主要以灰度图和RGB图为主,介绍一些经典例子。图像灰度、RGB互转的原理,会后面专门开一个文章介绍。
点运算最经典的两个算子是:,其中是增益参数,是偏差参数,他们分别控制图像的对比度和亮度;对于两幅输入图像的处理,有,其中从0到1变化,可以实现图像的渐变切换、甚至是合成与抠图,这里随手写了一个渐变代码。
import cv2
import numpy as np
img = cv2.imread('image.jpg')
img1 = cv2.imread('image1.jpg')
img = cv2.resize(img,(500,500))
img1 = cv2.resize(img1,(500,500))
newImg = np.zeros(img.shape,np.uint8)
for i in np.arange(0,1,0.1):
newImg = (1-i)*img + i*img1
newImg = newImg.astype(np.uint8)
cv2.imshow('newImg',newImg)
cv2.waitKey(100)
负片
负片(Negative Film)是经曝光和显影加工后得到的影像,其明暗与被摄体相反,其色彩则为被摄体的补色,一般来说,可以用像素值最大值减去当前值,即,代码如下:
import cv2
import numpy as np
img = cv2.imread('image.png')
img = 255-img
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
直接用RGB图片,处理后结果为:
伽马校正
伽马校正是一个很常见的非线性算子,它可以去除输入辐射量和量化的像素值之间的非线性映射,一般用,其中取2.2合适。
import cv2
import numpy as np
img = cv2.imread('image.png')
newImg = np.zeros(img.shape,np.uint8)
gamma = 2.2
newImg = pow(img,1/gamma)
newImg = newImg.astype(np.uint8)
cv2.imshow('newImg',newImg)
cv2.waitKey(0)
cv2.destroyAllWindows()
直方图均衡化、规定化
直方图是检测图像亮度值的集合,可以从中读取到最大小值、平均亮度值等,为了更好显示直方图,我们一般会将直方图归一化,即用概率表示像素值的数量出现的概率。
为了将直方图的对比度均匀处理,采用概率密度函数绘制累积直方图,然后利用映射函数求出新图像。
直方图规定化希望将原图像和目标图像的分布相匹配,主要利用累积直方图。原图像素值对应的累积直方图数值=目标图累积直方图数值对应的像素值,一一赋值即可,以下为一个灰度图的直方图规定化:
import cv2
import numpy as np
import matplotlib.pyplot as plt
import copy
def hist(img):#直方图
hist_number = np.zeros(256,np.float64)
for i in range(img.shape[0]): #遍历像素点
for j in range(img.shape[1]):
k = img[i][j] #获取灰度值k
hist_number[k] += 1 #在hist_number数组里计数,对应k级灰度值的数量
hist_number = hist_number/(img.shape[0]*img.shape[1]) #归一化
return hist_number #返回各灰度值占比的数组
def acc_hist(acc_hist_number): #累积直方图
for k in range(1,256): #注意累加应该从第二个元素开始
acc_hist_number[k] += acc_hist_number[k-1] #累加
return acc_hist_number #返回各累积灰度值占比的数组
def plt_hist(image,array_hist,array_acc_hist,title): #绘制直方图和累积直方图
plt.subplot(211)
plt.title(title)
plt.bar(range(256),array_hist,width=1)
plt.subplot(212)
plt.plot(range(256),array_acc_hist)
plt.show()
if __name__ == '__main__':
#读取原图片和匹配目标图片
originImg = cv2.imread('image.png',0)
targetImg = cv2.imread('image1.png',0)
#直方图、累积直方图
originImg_hist_number = hist(originImg)
originImg_acc_hist_number = acc_hist(copy.copy(originImg_hist_number)) #不能直接在原数组上操作,故用copy函数,将原数组复制分离
targetImg_hist_number = hist(targetImg)
targetImg_acc_hist_number = acc_hist(copy.copy(targetImg_hist_number))
# 将两张图片的灰度级进行匹配
newImg = np.zeros(originImg.shape, np.uint8)
for j in range(256):
temp = abs(targetImg_acc_hist_number - originImg_acc_hist_number[j]) #如果两数据相近,则相减后应趋向0
# 找出最接近的灰度级并进行匹配
temp = temp.tolist()
newImg[originImg[:, :] == j] = temp.index(min(temp))
newImg_hist_number = hist(newImg)
newImg_acc_hist_number = acc_hist(copy.copy(newImg_hist_number))
#绘制
plt.figure(1)
plt_hist(originImg,originImg_hist_number,originImg_acc_hist_number,'originImg')
plt.figure(2)
plt_hist(targetImg,targetImg_hist_number,targetImg_acc_hist_number,'targetImg')
plt.figure(3)
plt_hist(newImg_hist_number,newImg_acc_hist_number,'newImg')
#保存图片
cv2.imwrite('newImg_path.png', newImg)
对比度拉伸
某些图像的直方图会出现较窄而高的情况,分布不均匀,对比度不高,除了直方图均衡化外,还可以将窄而高的区间,线性映射到整个像素大小空间,公式为:
其中Imin,Imax是原始图像的最小灰度值和最大灰度值,MIN和MAX是要拉伸到的灰度空间的灰度最小值和最大值。
import cv2
import numpy as np
img = cv2.imread('image1.png')
Imax = np.max(img)
Imin = np.min(img)
newImg = (img - Imin) / (Imax - Imin) * 255
newImg = newImg.astype(np.uint8)
cv2.imshow('newImg',newImg)
cv2.waitKey(0)
cv2.destroyAllWindows()
窗口窗位调整
滤波
线性滤波器
线性滤波器主要由原图像、滤波算子组成,以3×3大小的算子为例,从原图像左上角开始滑动,遍历整个图像,每滑动到一处,就计算算子与算子所在的3×3图像对应位置的乘积,最后求和,得到一个数,将该数赋值给新图像对应位置即可。
不过,算子滑动不可能超出原图像边界,所以新图像总有赋不到值的地方,我们希望通过一些填充手段,塞满无赋值区域。目前常见的有零填塞、常数填塞、夹取填塞、重叠填塞、镜像填塞和延长。
线性滤波算子主要有均值算子、平移算子、Sobel算子、Scharr算子、Laplace算子、锐化算子、高斯算子等。
以下为Sobel算子的代码,它在求图像梯度,进行边缘检测上有很大用处。
import cv2
import numpy as np
def Filter(img):
core_size = 3
# 零填充
pad = core_size//2
newImg = np.zeros((img.shape[0]+2*pad,img.shape[1]+2*pad),dtype=np.uint8)
newImg[pad:pad+img.shape[0],pad:pad+img.shape[1]] = img.astype(np.uint8)
#滤波计算
tmp = newImg.copy()
core = np.array([[1,0,-1],[2,0,-2],[1,0,-1]])/9.0
for y in range(img.shape[0]):
for x in range(img.shape[1]):
newImg[pad+y,pad+x] = np.sum(tmp[y:y+core_size,x:x+core_size]*core)
newImg = newImg[pad:pad+img.shape[0],pad:pad+img.shape[1]].astype(np.uint8)
return newImg
img = cv2.imread("image.png",0)
newImg = Filter(img)
cv2.imshow("img",img)
cv2.imshow("newImg",newImg)
cv2.waitKey(0)
cv2.destroyAllWindows()
下面是原图像与滤波后图像:
但是这样计算滤波,每个像素点要算次,如果能将滤波算子写成列向量×行向量的形式,每个像素点就只需要算次了,大大减轻运算负担,而专门针对x轴和y轴的滤波,对完成某些特定任务有很大帮助。
如果觉得手动填充太麻烦,numpy.pad给了我们很方便的填充方式,如下,这也是opencv的sobel函数中默认的填充方式。
filter = img.astype(np.int32)
filter = np.pad(filter,((pad,pad),(pad,pad)),'reflect')
模板匹配
将需要找寻的小图片作为滤波器,与原图像做处理。得出的图像哪里最亮,就说明哪里匹配度高。但作为找寻的图片,如果和原图像中的图不同(如被旋转、放缩等),则匹配度就不会很高。
高斯模糊
高斯滤波,也叫高斯模糊,属于线性滤波器的一种,它让滤波算子的值符合高斯分布:
其中,以3×3高斯算子为例,设中点为(0,0)点,依次求解,并归一化(这样就不会改变图片亮暗关系),以下是求高斯算子代码:
import numpy as np
import math
gaussian = np.zeros((3,3))
kesai = 1
for dx in range(-1,2):
for dy in range(-1,2):
gaussian[dx+1,dy+1] = (1/(2*kesai*math.pi))*math.exp(-((dx**2+dy**2)/(2*kesai**2)))
gaussian = gaussian/np.sum(gaussian)
print(gaussian)
高斯差分滤波器
高斯差分滤波器是两个不同的高斯算子进行相减,能够提高滤波效率。
中值滤波
中值滤波它将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值,可以将窗口中心位置的像素值替换为该窗口内的中位数,可更好处理椒盐噪声、保留边缘。
频域分析
采样
隔一定距离采样赋值给新图像,可以缩小图像,但图像会损失信息,变得模糊。
如果采样频率小于2倍的信号频率,则会导致原本的高频信号被采样成低频信号,即高频信号被混叠成了低频信号,图像失真。如拍照频率不足的话,很可能看到正在行驶的车轮“向后转”。