一条直线可以用数学表达式 y = m x + c y = mx + c y=mx+c(斜截式) 或者 ρ \rho ρ = x c o s =x cos =xcos θ \theta θ + y s i n + y sin +ysin θ \theta θ(法线式) 表示。 ρ \rho ρ是从原点到直线的垂直距离, θ \theta θ是直线的垂线与横轴顺时针方向的夹角(OpenCV 坐标系)。如下图所示:
Hough变换检测形状的方法:
accumulator | 1° | 2° | … | 90° | … | 180° |
---|---|---|---|---|---|---|
1 | ||||||
… | ||||||
50 | 1 |
|||||
… | ||||||
142 |
代码速记:
参数解释:
lines = cv2.HoughLines(edges,1,np.pi/180,200)
像素
, θ \theta θ 的单位是弧度
。实战:
已知 ρ \rho ρ = x c o s =x cos =xcos θ \theta θ + y s i n + y sin +ysin θ \theta θ,先求出 ρ \rho ρ和直线的垂点
处的坐标 ( x 0 , y 0 ) (x_0,y_0) (x0,y0)为 ( ρ c o s θ , ρ s i n θ ) (\rho cos\theta,\rho sin\theta) (ρcosθ,ρsinθ),直线上取两个点的坐标: ( x 1 , y 1 ) (x_1,y_1) (x1,y1)为 ( x 0 + 1000 ⋅ ( − s i n θ ) , y 0 + 1000 ⋅ c o s θ ) (x_0+1000\cdot(- sin\theta),y_0+1000\cdot cos\theta) (x0+1000⋅(−sinθ),y0+1000⋅cosθ) ,这个点是在x轴的负方向,y轴的正方向的极远处。 ( x 2 , y 2 ) (x_2,y_2) (x2,y2)为 ( x 0 − 1000 ⋅ ( − s i n θ ) , y 0 − 1000 ⋅ c o s θ ) (x_0-1000\cdot(- sin\theta),y_0-1000\cdot cos\theta) (x0−1000⋅(−sinθ),y0−1000⋅cosθ) ,这个点是在x轴的正方向,y轴的负方向的极远处。
def Line(self):
img_copy1=self.img.copy()
img_copy2 = self.img.copy()
#【1】通过边缘检测,得到二值化图像
img_gray = cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(img_gray, 50, 150, apertureSize=3)
#【2】霍夫直线检测
lines = cv2.HoughLines(edges, 1, np.pi / 180, 200)
#【3】确定直线上的两点,画出其中一条直线
for rho, theta in lines[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_copy1, (x1, y1), (x2, y2), (0, 0, 255), 2)
#画出图像上检测到的所有直线
for line in lines:
for rho, theta in line:
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_copy2, (x1, y1), (x2, y2), (0, 0, 255), 2)
titles = ['raw', 'line_one','line_all']
imgs = [self.img, img_copy1,img_copy2]
for i in range(3):
plt.subplot(1, 3, i + 1), plt.imshow(cv2.cvtColor(imgs[i],cv2.COLOR_BGR2RGB))
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
从上边的过程我们可以发现:仅仅是一条直线都需要两个参数,这需要大量的计算。Probabilistic_Hough_Transform 是对霍夫变换的一种优化。它不会对每一个点都进行计算,而是从一幅图像中随机选取一个点集进行计算,对于直线检测来说这已经足够了。但是使用这种变换我们必须要降低阈值(总的点数都少了,阈值肯定也要小呀!)
代码速记:
参数解释:
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 100, minLineLength, maxLineGap)
实战:
def LineP(self):
img_copy1=self.img.copy()
img_copy2 = self.img.copy()
#【1】边缘检测得到二值化图像
img_gray = cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(img_gray, 50, 150, apertureSize=3)
#【2】优化霍夫直线变换
minLineLength = 100
maxLineGap = 10
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, 100, minLineLength, maxLineGap)
#【3】画出一条直线、所有直线
for x1, y1, x2, y2 in lines[0]:
cv2.line(img_copy1, (x1, y1), (x2, y2), (0, 255, 0), 2)
for line in lines:
for x1, y1, x2, y2 in line:
cv2.line(img_copy2, (x1, y1), (x2, y2), (0, 255, 0), 2)
titles = ['raw', 'line_p_one','line_p_all']
imgs = [self.img, img_copy1,img_copy2]
for i in range(3):
plt.subplot(1, 3, i + 1), plt.imshow(cv2.cvtColor(imgs[i],cv2.COLOR_BGR2RGB))
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
代码速记:
参数解释:
circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=30, minRadius=0, maxRadius=0)
实战:
def circle(self):
#【1】变灰度图像、中值模糊
#*****因为霍夫圆检测对噪声比较敏感,所以首先要对图像做中值滤波
img_gray = cv2.cvtColor(self.img, cv2.COLOR_BGR2GRAY)
img_gray = cv2.medianBlur(img_gray, 5)
#【2】霍夫梯度圆形检测
circles = cv2.HoughCircles(img_gray, cv2.HOUGH_GRADIENT, 1, 20,
param1=50, param2=30, minRadius=0, maxRadius=0)
circles = np.uint16(np.around(circles))#四舍五入
#【3】画图
cimg = cv2.cvtColor(img_gray, cv2.COLOR_GRAY2BGR)#画图函数的img要求是三通道的,所以转BGR
for i in circles[0, :]:
# 画出圆形的轮廓
cv2.circle(cimg, (i[0], i[1]), i[2], (0, 255, 0), 2)
# 画出圆心
cv2.circle(cimg, (i[0], i[1]), 2, (0, 0, 255), 3)
titles = ['raw', 'circle']
imgs = [self.img, cimg]
for i in range(2):
plt.subplot(1, 2, i + 1), plt.imshow(cv2.cvtColor(imgs[i],cv2.COLOR_BGR2RGB))
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
原理:
代码速记:
参数解释:
dist_transform = cv2.distanceTransform(opening, 1, 5)#source,distanceType,maskSize
实战:
def water_shed(self):
copy=self.img.copy()
gray = cv2.cvtColor(copy, cv2.COLOR_BGR2GRAY)
# 【1】Otsu's二值化:找到硬币的近似估计
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# 【2】开运算:去除图像中的所有的白噪声
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)
#已知靠近对象中心的区域肯定是前景,而远离对象中心的区域肯定是背景
#不能确定的区域就是硬币之间的边界(前景与背景的交界)
#当硬币之间没有接触时,可以用【腐蚀】去除边缘像素。但是现在硬币之间是接触的。所以要用距离变换再加上合适的阈值。
# 【3】膨胀:将对象的边界延伸到背景中去。
#这样由于边界区域被去处理,我们就可以知道哪些区域肯定是前景,哪些肯定是背景。
sure_bg = cv2.dilate(opening, kernel, iterations=3)
# 【4】确定前景区域:先进行距离变换,确定每个像素到零像素的最短距离。设置阈值筛选前景(距离越短越好)
dist_transform = cv2.distanceTransform(opening, 1, 5)#source,distanceType,maskSize
ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
# 【5】从sure_bg中减去肯定sure_fg就得到了边界区域
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)
# 【6】创建标签,标记标签(一个与原图像大小相同,数据类型为in32 的数组)
ret, markers1 = cv2.connectedComponents(sure_fg)
#这个函数会把将背景标记为0,其他的对象使用从1 开始的正整数标记。
#但在分水岭算法中unknown才应该是0。所有标签先加一。
markers = markers1 + 1
#现在把是unknown的像素标记为0
markers[unknown == 255] = 0
# 【7】实施分水岭算法
markers3 = cv2.watershed(copy, markers)#source,markers
#这个函数会修改标签图像,边界区域的标记将变为-1.
copy[markers3 == -1] = [255, 0, 0]#在原图中把边界区域设置为红色
#### 画图
titles = ['raw', 'thresh','opening','sure_bg','dist_transform','sure_fg',
'unknown','markers1','markers','markers3','result']
imgs = [self.img,thresh, opening,sure_bg,dist_transform,sure_fg,
unknown,markers1,markers,markers3,copy]
for i in range(11):
plt.subplot(2, 6, i + 1), plt.imshow(imgs[i],'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
代码速记:
参数解释:
cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
#原图、mask、包含前景的矩形、算法内部使用两个的数组、迭代次数、
#进行修改的方式(cv2.GC_INIT_WITH_RECT 或cv2.GC_INIT_WITH_MASK,也可以联合使用。)
实战:
def grab_cut(self):
img = cv2.imread('../images/messi.jpg')
mask = np.zeros(img.shape[:2], np.uint8)
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)
rect = (50, 50, 450, 290)
# 函数的返回值是更新的mask, bgdModel, fgdModel
cv2.grabCut(img, mask, rect, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
#画图:
img = img * mask2[:, :, np.newaxis]
plt.imshow(cv2.cvtColor(img,cv2.COLOR_BGR2RGB)), plt.colorbar()
plt.xticks([]), plt.yticks([])
plt.show()
用ps重新制作一张mask图像。如果用cv2.GC_INIT_WITH_MASK参数,bgdModel和fgdModel为零会报错。如果用cv2.GC_INIT_WITH_RECT则没有什么反应。(此处留坑)
# newmask is the mask image I manually labelled
newmask = cv2.imread('newmask.png',0)
# whereever it is marked white (sure foreground), change mask=1
# whereever it is marked black (sure background), change mask=0
mask[newmask == 0] = 0
mask[newmask == 255] = 1
mask, bgdModel, fgdModel = cv2.grabCut(img,mask,None,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_MASK)
mask = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img = img*mask[:,:,np.newaxis]
plt.imshow(img),plt.colorbar(),plt.show()