原理应该是这样的:
首先为了方便处理,我们通常会对图片进行灰度转换(即将图片转换成只有一个图层的灰色图像)。
然后我们分析一下,在上面的图片中有三个主色调,分别是字体颜色(黑色)、纸张颜色(偏白)、阴影颜色(灰色)。知道这点后我们就好办了。我们只需要把灰色和白色部分都处理为白色就好了。
那要我怎么才知道白色和灰色区域呢?对于一个8位的灰度图,黑色部分的像素大致在0-30左右。白色和灰色应该在31-255左右(这个范围只是大致估计,实际情况需要看图片)。如图:
左边是原图,右边是处理后的图片。我们将灰色和接近白色的部分都处理成了白色。
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 只适用于比较简单的图片,比如试卷这种黑白分明的,有一些灰色就可以很好去掉
# 删除阴影时,有两件事要注意。由于图像是灰度图像,如果图像背景较浅且对象较暗,则必须先执行最大滤波,然后再执行最小滤波。
# 如果图像背景较暗且物体较亮,我们可以先执行最小滤波,然后再进行最大滤波。
# 【最大滤波】:让我们假设我们有一定大小的图像I。我们编写的算法应该逐个遍历I的像素,并且对于每个像素(x,y),它必须找到该像素周围的邻域(大小为N*N
# 的窗口)中的最大灰度值,并进行写入A中相应像素位置(x,y)的最大灰度值。所得图像A称为输入图像I的最大滤波图像。
# 如果图像的背景较浅,执行最大过滤,这将为我们提供增强的背景
def max_filtering(N, I_temp): # N=20 # I_temp就是个ndarray
# 它最初在输入数组周围创建一个“墙”(带有-1的填充),当我们遍历边缘像素时会有所帮助。
a = I_temp.shape[0] # 图片的行数
b = I_temp.shape[1]
c = (N // 2) * 2 # c就是20,为什么是这个取值呢?为什么不直接取跟下面的nn一样的10呢?因为这堵墙厚度10,所以行增加20,列也是增加20。并不是上下都增加20
hang = a + c
lie = b + c
wall = np.full((hang, lie), -1) # 创建一个比原图大的-1的一堵墙(wall也是ndarray)。-1比0小,肯定不会被amax返回
ws0 = wall.shape[0] # 就是上面的hang=a+c=a+20
ws1 = wall.shape[1]
nn = (N // 2) # N=20时,nn=10
wall[nn:ws0 - nn, nn:ws1 - nn] = I_temp.copy() # 这估计就是往这面大墙里面塞图片的像素,让这堵墙变成围墙。wall和图的大小一样,这里容易搞错,其实是一样的
# 然后,我们创建一个“ temp”变量,将计算出的最大值复制到其中。
temp = np.full((a + c, b + c), -1) # 就跟原来那堵墙一样
for y in range(0, ws0):
for x in range(0, ws1):
if wall[y, x] != -1: # 如果这个像素点不属于墙
window = wall[y - nn:y + nn + 1, x - nn:x + nn + 1] # 遍历该数组并围绕大小为 NxN 的当前像素创建一个窗口。
num = np.amax(window) # 使用“ amax()”函数在该ndarray中的最大值
temp[y, x] = num # 一个像素点一个像素点地替换,num是170、148……这样子的一个个整数
A = temp[nn:ws0 - nn, nn:ws1 - nn].copy() # nn:ws0 - nn, nn:ws0 - nn 就是真实图片大小,在这里已经被滤波处理了。用copy是为了深拷贝,让两个不指向同一块内存
# nn:ws0 - nn,意思是从nn行到ws0-nn行
return A # A是输入I的最大滤波图像
# 此算法与最大滤波完全相同,但是我们没有找到附近的最大灰度值,而是在该像素周围的N x N邻域中找到了最小值,并将该最小灰度值写入B中的(x,y)。
# 所得图像B称为图像I的经过最小滤波的图像
def min_filtering(N, A):
wall_min = np.full((A.shape[0] + (N // 2) * 2, A.shape[1] + (N // 2) * 2), 300)
wall_min[(N // 2):wall_min.shape[0] - (N // 2), (N // 2):wall_min.shape[1] - (N // 2)] = A.copy()
temp_min = np.full((A.shape[0] + (N // 2) * 2, A.shape[1] + (N // 2) * 2), 300)
for y in range(0, wall_min.shape[0]):
for x in range(0, wall_min.shape[1]):
if wall_min[y, x] != 300:
window_min = wall_min[y - (N // 2):y + (N // 2) + 1, x - (N // 2):x + (N // 2) + 1]
num_min = np.amin(window_min)
temp_min[y, x] = num_min
B = temp_min[(N // 2):wall_min.shape[0] - (N // 2), (N // 2):wall_min.shape[1] - (N // 2)].copy()
return B
def background_subtraction(I, B): # 归一化将白色背景修改贴近原图
O = I - B
norm_img = cv2.normalize(O, None, 0, 255, norm_type=cv2.NORM_MINMAX)
return norm_img
def min_max_filtering(M, N, I): # N=20
if M == 0:
A = max_filtering(N, I) # I就是个ndarray图片
B = min_filtering(N, A) # 将最大过滤后的图像传递给最小过滤功能,该功能将负责实际的内容增强。
print(B)
# subtraction
normalised_img = background_subtraction(I, B) # 因此,执行最小-最大滤波后,我们获得的值不在0-255的范围内。为啥?
# 因此,我们必须归一化使用背景减法获得的最终阵列,该方法是将原始图像减去最小-最大滤波图像,以获得去除阴影的最终图像。
print(normalised_img)
elif M == 1:
A = min_filtering(N, I)
B = max_filtering(N, A)
# subtraction
normalised_img = background_subtraction(I, B)
return normalised_img
P = cv2.imread('Test_image.jpg', 0) # P是个ndarray数组,图片变数组
plt.imshow(P, cmap='gray') # 变成灰度图
plt.title("original image")
plt.show()
# We can edit the N and M values here for P and C images
O_P = min_max_filtering(M=0, N=20, I=P) # 如果图像的背景较浅,我们要先执行最大过滤
# 变量N(用于过滤的窗口大小)将根据图像中粒子或内容的大小进行更改。对于测试图像,选择大小N = 20。
# Display final output
plt.imshow(O_P, cmap='gray')
plt.title("Final output")
plt.show()
归一化