Preface
黑色五月即将告一段落,百忙之中还是把标题中几个算法给手动实现了一遍,发现这也太有趣了吧!但是目前还存在一些效率方面的问题,比如说,吴恩达老师一开始就说过尽量减少for loop的使用,但是能力有限555目前所有的算法都是用for loop实现的,而且看起来逻辑比较直接,等之后用矩阵算法优化后再把代码加过来。
Part 1
首先是直方图均衡,具体算法过程这里不过多赘述。但是会罗列出实现时踩过的坑以及必须要注意的一些细节!
实现代码:
## 直方图均衡算法# 参数:输入灰度图像矩阵,返回结果矩阵# 问题:当前算法只能对灰度图进行直方图均衡#img = cv2.imread("src/pic/1.jpg", 1)img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 记录图片的长宽height = img.shape[0]width = img.shape[1]# 生成结果矩阵arr = np.zeros((256))outimg = np.zeros((height, width)).astype(np.float32)# 统计直方图数组for i in img:for j in i:arr[j] += 1# 将直方图数组进行归一化for i in range(256):arr[i] /= width * height# 下面进行累积直方图均衡化crr = np.zeros(256)drr = np.zeros(256)for i in range(256):for j in range(i):crr[i] += arr[j]for i in range(256):drr[i] = (int)(crr[i] * 255 + 0.5)# 最后处理,将图片进行均衡操作for i in range(height):for j in range(width):img[i][j] = drr[img[i][j]]img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)plt.imshow(img)plt.show()
原图:
实现效果图:
调用API效果图:
附上API代码:
# 调用API进行直方图均衡dst = cv2.equalizeHist(gray)dst = cv2.cvtColor(dst, cv2.COLOR_BGR2RGB)plt.imshow(dst)
要注意的问题
一定要留意输出图片数组的格式,如果是float32,那么最后结果需要除255.0累积直方图那里,数组下标的对应关系一定要理解透彻,累积直方图本身是一个单调递增的图,我们需要用原图的灰度值从累积直方图上映射。
Part 2
下面是中值滤波器部分,理论知识很好理解,实现也不是很复杂,但是对于细节的操作一定要注意。
实现代码:
# 中值滤波器# 遇到的错误就是,误认为cvt彩色模式转换无损# 解决方法就是,将三个通道分别进行处理 最后用merge合并# 最终得到效果图,对于椒盐噪声比较严重的图片效果比较好# 对于普通图片,可能会产生模糊效果f_img = cv2.imread("src/pic/0.jpg", 1)# 定义滤波器参数filter_core = 3height = f_img.shape[0]width = f_img.shape[1]deNum = (int)(filter_core / 2) # 定义输出图片矩阵out_img = np.zeros((3, height, width)).astype(np.float32)for channel in range(3):for i in range(height):for j in range(width):# 边缘不做处理if(i >= deNum and j >= deNum and i < height - 1 and j < width - 1): # 记录窗口内的值memo = np.zeros(9).astype(np.float32)flag = 0for k in range(i - deNum , i + deNum + 1):for m in range(j - deNum, j + deNum + 1):memo[flag] = f_img[k][m][channel]flag += 1 memo = np.sort(memo)# 获取窗口的中值midNum = (float)(memo[4])out_img[channel][i][j] = midNumelse :# 这里采用简单的方法将Padding直接copy# 当然后期我们会使用镜像复制的方式处理Padding# 否则,不仅会丢失很多边界细节,其次可能会让我们的图像越来越小out_img[channel][i][j] = f_img[i][j][channel]resimg = cv2.merge([out_img[0], out_img[1], out_img[2]])resimg = resimg / 255.0plt.imshow(resimg)plt.show()plt.imshow(f_img)plt.show()
原图:
实现效果图:
调用API效果图:
附上API代码:
apiimg = cv2.medianBlur(f_img, 3)plt.imshow(apiimg)
其实这三个图的效果看起来没有什么太大区别,甚至处理过以后图片变得更加模糊了,这是因为选取的图像本身没有明显的噪声,当图片中存在较多的椒盐噪声时,效果就会比较明显。均值滤波的实现代码与之类似,但是针对椒盐噪声而言,均值滤波的效果很差,而且会导致图片异常模糊,所以我们使用中值滤波较多一点。
要注意的问题
首先最基本的,我们对一个三通道彩色图进行处理的时候,一定要对三个通道分别进行处理,在矩阵的角度理解就是,我们一定要用一个三维矩阵来处理。同时,一个彩色图转换成灰度图是有数据丢失的,一旦进行处理后便不能转换成三通道图。常用的做法时,对三个通道处理完用merge函数进行合并。
Part 3
下面时拉普拉斯算子的实现,用于锐化图片,或者提取图片的细节。实现原理简单描述就是,计算灰度值的二阶梯度。计算过程中,我们不必真的去计算两次xy方向上的导数,而是用滤波器的方式,用矩阵计算一次即可。同样,这里使用的是for loop实现。
实现代码:
# 拉普拉斯算子 锐化图像lapimg = cv2.imread("src/pic/Moon.jpg", 1)# 定义图片的参数height = lapimg.shape[0]width = lapimg.shape[1]# 定义拉普拉斯滤波器lapVector = np.array([[0, 1, 0],[1, -4, 1],[0, 1, 0]])# 定义输出图像矩阵outimg = np.zeros((3, height, width)).astype(np.float32)# 用拉普拉斯算子锐化图像for channel in range(3):for i in range(height): for j in range(width): if(i >= 1 and j >= 1 and i < height - 1 and j < width - 1):# 找到中心坐标左上角的坐标initial_x = i - 1initial_y = j - 1# 用于记录每次滤波的结果curnum = 0.0# 对本窗口进行滤波for m in range(3):for n in range(3):# 二阶微分curnum += (float)(lapVector[m][n] * lapimg[initial_x + m][initial_y + n][channel] * 0.2)# 这里一定要对计算结果取绝对值,因为若为负值会使图片颜色溢出outimg[channel][i][j] = np.abs(curnum)resimg = cv2.merge([outimg[0], outimg[1],outimg[2]])resimg /= 255.0plt.imshow(resimg)plt.show()plt.imshow(lapimg)plt.show()addimg = (resimg + lapimg)addimg = addimg.astype(np.int)plt.imshow(addimg)plt.show()
三张对比图放在一起:
第一张是提取出来的细节部分
第二张是原图
第三张是锐化后的结果
需要注意的问题
计算二价导数时会产生负数值,我们一定要对负数取正,否则后果非常严重代码中0.2那个系数可以随意更改,是锐化权重
Part 4
最后是Sobel算子进行边缘检测,这里我只实现了对Y方向的检测,输出一个灰度图,对X方向的操作相同,只需要转换一下对应的核。这里说明,我们对图像进行的滤波操作,其实也可以称作为卷积,哈哈哈哈哈猝不及防,其实卷积这个词也没有很高大上。但是一定要注意,我们教科书上对卷积的定义和这里说到的不一样,严格上说我们现在的操作是相关,真正的卷积是需要将核进行xy方向倒置。但是在实践中,尤其是在卷积神经网络中,我们没有必要进行这一步,只会为我们增加多余的计算量,所以你会看到,在大多数文献中,会将相关操作称为卷积,那我们这里也同样称之为卷积。
# 使用Sobel过滤器进行边缘检测#sobimg = cv2.imread("src/pic/1.jpg", 1)height = sobimg.shape[0]width = sobimg.shape[1]outimg = np.zeros((3, height, width)).astype(np.float32)# 先定义Sobel过滤器矩阵sobelVector = np.array([[1, 0, -1],[2, 0, -2],[1, 0, -1]])for channel in range(3) :for i in range(height) :for j in range(width) :if(i >= 1 and j >= 1 and i < height - 1 and j < width - 1):# 初始化参数in_x = i - 1in_y = j - 1curnum = 0for m in range(3) :for n in range(3) : curnum += sobelVector[m][n] * sobimg[in_x + m][in_y + n][channel]outimg[channel][i][j] = np.abs(curnum)resimg = cv2.merge([outimg[0], outimg[1], outimg[2]]) resmax = np.amax(resimg)resimg /= resmaxresimg = cv2.cvtColor(resimg, cv2.COLOR_BGR2GRAY)resimg = cv2.cvtColor(resimg, cv2.COLOR_RGB2BGR)plt.imshow(resimg)
原图:
实现效果图:调用API的效果图:
第一张是Y方向
第二张是X方向
第三张是将两张图融合
附上API代码:
# 调用API实现xy方向的sobel检测sobimg = cv2.imread("src/pic/1.jpg", 0)x = cv2.Sobel(sobimg,cv2.CV_16S,1,0)#1,0代表只计算x方向计算边缘y = cv2.Sobel(sobimg,cv2.CV_16S,0,1)#0,1代表只在y方向计算边缘absX = cv2.convertScaleAbs(x)absY = cv2.convertScaleAbs(y)dst = cv2.addWeighted(absX,0.5,absY,0.5,0)absX = cv2.cvtColor(absX, cv2.COLOR_BGR2RGB)absY = cv2.cvtColor(absY, cv2.COLOR_BGR2RGB)dst = cv2.cvtColor(dst, cv2.COLOR_BGR2RGB)plt.imshow(absX)plt.show()plt.imshow(absY)plt.show()plt.imshow(dst)plt.show()
需要注意的问题
在进行Sobel算子计算时,最后的结果有时除以255也会溢出,于是我们采用统计归一化的方法进行处理。
OpenCV - 数字图像处理(python代码实现):直方图均衡化 中值滤波器 拉普拉斯算子 Sobel算子综合