一、CANNY边缘检测
边缘检测是从图像中提取有用的结构信息的一种技术,如果学过信息论就会知道,一面充满花纹的墙要比一面白墙的信息量大很多,没学过也没关系,直观上也能理解:充满花纹的图像要比单色图像信息更丰富。为什么要检测边缘?因为我们需要计算机自动的提取图像的底层(纹理等)或者高层(时间地点人物等)的信息,边缘可以说是最直观、最容易发现的一种信息了。Canny提出了一个对于边缘检测算法的评价标准,包括:
1)以低的错误率检测边缘,也即意味着需要尽可能准确的捕获图像中尽可能多的边缘。
2)检测到的边缘应精确定位在真实边缘的中心。
3)图像中给定的边缘应只被标记一次,并且在可能的情况下,图像的噪声不应产生假的边缘。
简单来说就是,检测算法要做到:边缘要全,位置要准,抵抗噪声的能力要强。
Canny 检测算法包含下面几个阶段:
1、高斯模糊
2、计算图片梯度幅值
3、非极大值抑制
4、双阈值选取
代码演示:
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('../data/messi5.jpg',0)
edges = cv2.Canny(img, 100, 200)
cv2.imshow('Edges',edges)
cv2.waitKey(0)
演示结果:
二、图像金字塔
原理:一般情况下,我们要处理是一副具有固定分辨率的图像。但是特别情况下我们需要对同一个图像的不同分辨率的子图像进行处理,如查找图像中的某个目标,如人脸,我们不知道目标在图像中的尺寸大小。这种情况下,我们需要创建一组图像,这些图像是具有不同分辨率的原始图像。我们把这组图像叫做图像金字塔。就是同一图像的不同分辨率的子图集合。我们把最大的图像放在底部,最小的放在顶部,看起来就像一座金字塔。
有两类:高斯金字塔和拉普拉斯金字塔。
高斯金字塔的顶部是通过将底部图像中的连续的行和列去除得到的。顶部图像中的每个像素值等于下一层图像中5个像素的高斯加权平均值。这样操作一次一个MxN的图像就变成了一个M/2xN/2的图像。所以这幅图像的面积就变为原来图像面积的四分之一。这被称为Octave。
连续这样的操作,我们就会得到一个分辨率不断下降的图像金字塔。
可以使用函数**cv2.pyrDown()和cv2.pyrUp()**构建图像金字塔。
cv2.pyrDown从一个高分辨率大尺寸的图像向上构建一个金字塔(尺寸变小,分辨率降低)
cv2.pyrUp从一个低分辨率小尺寸的图像向上构建一个金字塔(尺寸变大,但分辨率不会增加)
代码演示:
import cv2
import numpy as np
higher_reso = cv2.imread('../data/messi5.jpg')
# 函数 cv2.pyrDown()从一个分辨率大尺寸的图像向上构建一个金字塔 尺寸变小 分辨率降低 。
lower_reso = cv2.pyrDown(higher_reso)
cv2.imshow('lower_reso', lower_reso)
# 从一个低分辨率小尺寸的图像向下构建一个金字塔 尺寸变大 但分辨率不会增加
higher_reso2 = cv2.pyrUp(lower_reso)
cv2.imshow('higher_reso2', higher_reso2)
# 是 higher_reso2 和 higher_reso 是不同的。因为一旦使用 cv2.pyrDown() 图像的分辨率就会低 ,信息就会丢失
cv2.waitKey(0)
cv2.destroyAllWindows()
演示结果:
示例:
橘子、苹果的金字塔融合代码演示:
import cv2
import numpy as np, sys
A = cv2.imread('apple.jpg')
B = cv2.imread('orange.jpg')
# generate Gaussian pyramid for A
G = A.copy()
gpA = [G]
for i in range(6):
G = cv2.pyrDown(G)
gpA.append(G)
# generate Gaussian pyramid for B
G = B.copy()
gpB = [G]
for i in range(6):
G = cv2.pyrDown(G)
gpB.append(G)
# generate Laplacian Pyramid for A
lpA = [gpA[5]]
for i in range(5, 0, -1):
GE = cv2.pyrUp(gpA[i])
if gpA[i - 1].shape[0] < GE.shape[0]:
L = cv2.subtract(gpA[i - 1], cv2.resize(GE, (gpA[i - 1].shape[1], gpA[i - 1].shape[0])))
elif gpA[i - 1].shape[0] > GE.shape[0]:
L=cv2.subtract(cv2.resize(gpA[i - 1],(GE.shape[1],GE.shape[0])), GE)
else:
L = cv2.subtract(gpA[i - 1], GE)
lpA.append(L)
lpB = [gpB[5]]
for i in range(5, 0, -1):
GE = cv2.pyrUp(gpB[i])
if gpB[i - 1].shape[0] < GE.shape[0]:
L = cv2.subtract(gpB[i - 1], cv2.resize(GE, (gpB[i - 1].shape[1], gpB[i - 1].shape[0])))
elif gpB[i - 1].shape[0] > GE.shape[0]:
L=cv2.subtract(cv2.resize(gpB[i - 1],(GE.shape[1],GE.shape[0])), GE)
else:
L = cv2.subtract(gpB[i - 1], GE)
lpB.append(L)
LS = []
for la, lb in zip(lpA, lpB):
rows, cols, dpt = la.shape
ls = np.hstack((la[:, 0:cols // 2], lb[:, cols // 2:]))
LS.append(ls)
print(ls.shape)
ls_ = LS[0]
for i in range(1, 6):
ls_ = cv2.pyrUp(ls_)
print(ls_.shape)
print(LS[i].shape)
if ls_.shape[0] < LS[i].shape[0]:
ls_ = cv2.add(ls_, cv2.resize(LS[i], (ls_.shape[1], ls_.shape[0])))
elif ls_.shape[0] > LS[i].shape[0]:
ls_ = cv2.add(cv2.resize(ls_, (LS[i].shape[1], LS[i].shape[0])), LS[i])
else:
ls_ = cv2.add(ls_, LS[i])
real = np.hstack((A[:, :cols // 2], B[:, cols // 2:]))
cv2.imwrite('Pyramid_blending2.jpg', ls_)
cv2.imshow("Org",A)
cv2.imshow("Org1",B)
cv2.imshow("Pyramid_blending",ls_)
cv2.imwrite('Direct_blending.jpg', real)
cv2.imshow("Direct_blending",real)
cv2.waitKey()
cv2.destroyAllWindows()
演示结果:
Hough 变换的具体实现步骤如下:
(1) 建立一个参数(λ,θ) 空间的二维的数组,该数组相当于一个累加器。
(2) 顺序搜索图像中所有目标(黑色)像素,对于每一个目标像素,在参数空间中根据公式找到对应位置,然后在累加器的对应位置加 1。
(3) 求出参数空间(累加器)中最大值,其位置为(λ’,θ’)。
(4) 通过参数空间位置(λ’,θ’) ,根据式(3)找到图像空间中相对应的直线参数。
对于直线hough变换过程,可以这么认为。**依次遍历图像的所有像素,对每个像素判断是否满足某个特定条件,若满足,则经过该像素的所有直线区域的计数器加1。**为了得到经过某个像素的所有直线区域,可以依次用θ的所有可能取值(例如θ可以以1度为增量,从-90度取到+90度),根据(3)式求出λ的值,从而得到很多组(λ,θ),就对应了所有经过此像素的直线。
代码演示:
import cv2
import numpy as np
img = cv2.imread('../data/sudoku.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# edges = cv2.Canny(gray,50,150,apertureSize = 3)
edges = cv2.Canny(gray, 10, 50, apertureSize=3)
cv2.imshow("edges", edges)
lines = cv2.HoughLines(edges, 1, np.pi / 180, 200)
print("Len of lines:", len(lines))
# print lines
for line in lines:
rho, theta = line[0]
a = np.cos(theta)
b = np.sin(theta)
x0 = a * rho
y0 = b * rho
x1 = int(x0 + 1000 * (-b))
y1 = int(y0 + 1000 * (a))
x2 = int(x0 - 1000 * (-b))
y2 = int(y0 - 1000 * (a))
cv2.line(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv2.imwrite('houghlines3.jpg',img)
cv2.imshow("houghlines3.jpg", img)
cv2.waitKey(1000)
cv2.waitKey(0)
cv2.destroyAllWindows()
演示结果:
hough圆检测
其实检测圆形和检测直线的原理差别不大,只不过直线是在二维空间,因为y=kx+b,只有k和b两个自由度。
而圆形的一般性方程表示为(x-a)²+(y-b)²=r²。那么就有三个自由度圆心坐标a,b,和半径r。这就意味着需要更多的计算量,而OpenCV中提供的cvHoughCircle()函数里面可以设定半径r的取值范围,相当于有一个先验设定,在每一个r来说,在二维空间内寻找a和b就可以了,能够减少计算量。
具体步骤如下:
1.对输入图像进行边缘检测,获取边界点,即前景点。
2.假如图像中存在圆形,那么其轮廓必定属于前景点(此时请忽略边缘提取的准确性)。
3.同霍夫变换检测直线一样,将圆形的一般性方程换一种方式表示,进行坐标变换。由x-y坐标系转换到a-b坐标系。写成如下形式(a-x)²+(b-y)²=r²。那么x-y坐标系中圆形边界上的一点对应到a-b坐标系中即为一个圆。
4.那x-y坐标系中一个圆形边界上有很多个点,对应到a-b坐标系中就会有很多个圆。由于原图像中这些点都在同一个圆形上,那么转换后a,b必定也满足a-b坐标系下的所有圆形的方程式。直观表现为这许多点对应的圆都会相交于一个点,那么这个交点就可能是圆心(a, b)。
5.统计局部交点处圆的个数,取每一个局部最大值,就可以获得原图像中对应的圆形的圆心坐标(a,b)。一旦在某一个r下面检测到圆,那么r的值也就随之确定。
代码演示:
import cv2
import numpy as np
img = cv2.imread('../data/OpenCV_Logo_with_text.png', 0)
img = cv2.medianBlur(img, 5)
cimg = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
circles = cv2.HoughCircles(img, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=30, minRadius=0, maxRadius=0)
circles = np.uint16(np.around(circles))
print(circles)
for i in circles[0, :]:
# draw the outer circle
cv2.circle(cimg, (i[0], i[1]), i[2], (0, 255, 0), 2)
# draw the center of the circle
cv2.circle(cimg, (i[0], i[1]), 2, (0, 0, 255), 3)
cv2.imshow('detected circles', cimg)
cv2.waitKey(0)
cv2.destroyAllWindows()
演示结果:
示例:棋盘检测
import cv2
import numpy as np
from collections import Counter
def detect_weiqi(img): # 检测棋子的颜色
txt = 'black'
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, threshold = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)
c = Counter(list(threshold.flatten()))
print(c.most_common())
if c.most_common()[0][0] != 0:
txt = 'white'
return txt, threshold
img = cv2.imread('../data/weiqi.png')
img = cv2.medianBlur(img, 5)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# ret, threshold = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY)#不行
cv2.imshow('gray', gray)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20, param1=100, param2=30, minRadius=10, maxRadius=50)
if circles is None:
exit(-1)
circles = np.uint16(np.around(circles))
print(circles)
cv2.waitKey(0)
font = cv2.FONT_HERSHEY_SIMPLEX
for i in circles[0, :]:
# draw the outer circle
cv2.circle(img, (i[0], i[1]), i[2], (0, 255, 0), 2)
# draw the center of the circle
cv2.circle(img, (i[0], i[1]), 2, (0, 0, 255), 3)
x, y, r = i
crop_img = img[y - r:y + r, x - r:x + r]
# 检测围棋
txt, threshold = detect_weiqi(crop_img)
print('颜色:', '黑色' if txt == 'black' else '白色')
cv2.putText(threshold, text=txt, org=(0, 0), fontFace=font, fontScale=0.5, color=(0, 255, 0), thickness=2)
cv2.imshow('threshold', threshold)
cv2.imshow('crop_img', crop_img)
cv2.moveWindow('crop_img', x=0, y=img.shape[0])
cv2.imshow('detected chess', img)
cv2.moveWindow('detected chess', y=0, x=img.shape[1])
cv2.waitKey(1500)
cv2.waitKey(0)
cv2.destroyAllWindows()
演示结果: