霍夫变换应用于检测任何形状,即使有稍微破损或者变形,都可以检测出来。这里我们将它作用在直线上。
根据直线方程:y=ax+b。
从原点做直线的垂线,和x正方向形成θ夹角,原点到直线的距离为ρ。当θ小于180°时,认为它是正的,大于180°时,θ是负的。这样就可以把原点到直线的距离用参数的形式表示出来:ρ=xcosθ+ysinθ。所以任意一条直线,都可以用ρ和θ表示。当直线平行于x轴,θ值为0°,垂直于x轴,θ为90°。
我们可以创建一个二维数组,它的列数表示θ的值,比如180列,每列表示1°,数字越大,分的越小,数组里面的数字表示ρ的值,可以以像素为单位设置多行,增加同一角度下面对ρ的投票数。然后在坐标中,由于我们已经知道了直线的x,y坐标,所以我们拿θ和ρ放到方程中,得到新的直线,最后,投票得数最多的,就是最终的θ和ρ。
在OpenCV中,我们使用函数cv.HoughLines对二进制输入图像进行霍夫线变换,所以,在图片输入后,需要使用阈值或者Canny方法,将图像转换为二进制,再输入到cv.HoughLines中。投票数取决于线上的点的个数,即像素个数,所以它实际上是表示能检测到的最短的线的长度。
img=cv.imread("text.jpg")
gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
edges=cv.Canny(gray,50,150,apertureSize=3)
#(二进制输入,ρ阈值,θ阈值,行数阈值-可以理解为多少像素以上才算)
lines=cv.HoughLines(edges,1.5,np.pi/90,160)
#在原图中画出来,rho是ρ,theta是θ
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))
cv.line(img,(x1,y1),(x2,y2),(0,0,255),2)
plt.imshow(img),plt.title("Hough Lines")
plt.show()
概率霍夫变换时霍夫变换的优化版,它不需要考虑所有点,它采用随机的点子集,就可以进行线变换。OpenCV中使用cv.HoughLinesP进行概率霍夫线变换。
img=cv.imread(cv.samples.findFile("text.jpg"))
gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
edges=cv.Canny(gray,50,150,apertureSize=3)
#增加了最小线长限制,最大间隙限制两个参数
lines=cv.HoughLinesP(edges,1,np.pi/180,100,minLineLength=100,maxLineGap=10)
#概率霍夫变换直接输出线的两个端点,使表示显得简洁,
#不像上面的线变换,仅能获得两个参数ρ和θ,还需要变换才能显示曲线。
for line in lines:
x1,y1,x2,y2=line[0]
cv.line(img,(x1,y1),(x2,y2),(0,255,0),2)
plt.imshow(img)
plt.show()
线变换就是找到图中的线条,圈变换就是找到图中的圆。
在霍夫线变化中,是无法找到圆的。
园的方程是:(x-x0)2 +(y-y0)2=r2,如果我们按照霍夫线变换的方式转化成参数方程,需要三个参数表示,也就是需要3D累加器进行霍夫变换。在OpenCV中,使用霍夫梯度方法,采集边缘的信息。
使用cv.HoughCircles进行霍夫圈变换。
img=cv.imread("gy.jpg",0)
img=cv.medianBlur(img,5)
cimg=cv.cvtColor(img,cv.COLOR_GRAY2BGR)
#(灰度图像,变换方式,分辨率,两个圆之间的最小距离,Canny阈值,累加器阈值,最小半径,最大半径)
circles=cv.HoughCircles(img,cv.HOUGH_GRADIENT,1,20,param1=80,param2=40,minRadius=0,maxRadius=0)
#四舍五入要不要都可以
circles=np.uint16(np.around(circles))
for i in circles[0,:]:
cv.circle(cimg,(i[0],i[1]),i[2],(0,255,0),2)
cv.circle(cimg,(i[0],i[1]),2,(0,0,255),3)
plt.subplot()
plt.imshow(cimg)
plt.show()
Watershed的思想:如果把一张灰度图当作一个地形图,检测到边缘的地方就是山脉,其余地方是低谷。由于山脉和低谷的存在,就会形成一个个泻湖,凹地,火山口等等封闭或者半封闭的范围。然后再往里面灌注海水,海水会漫过同等海拔的地方,此时需要在缺口处设置watershed挡着,不让水通过。这样就可以利用山脉将每个区域独立起来。也就是说,watershed就是填补了这些缺口,利用边缘分割图像。
但是这种方式会受到噪声和其它不规则边缘的干扰,因此在OpenCV中,设置了一个watershed标记,可以指定哪些是哪些不是。
img=cv.imread("pg.jpg")
gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret,thresh=cv.threshold(gray,0,255,cv.THRESH_BINARY_INV+cv.THRESH_OTSU)
#去除噪声
kernel=np.ones((3,3),np.uint8)
opening=cv.morphologyEx(thresh,cv.MORPH_OPEN,kernel,iterations=2)
#找背景
sure_bg=cv.dilate(opening,kernel,iterations=3)
dist_transform=cv.distanceTransform(opening,cv.DIST_L2,5)
#找前景
ret,sure_fg=cv.threshold(dist_transform,0.005*dist_transform.max(),255,0)
sure_fg=np.uint8(sure_fg)
#未知区域
unknow=cv.subtract(sure_bg,sure_fg)
#标记
ret,markers=cv.connectedComponents(sure_fg)
markers=markers+1
markers[unknow==255]=0
markers=cv.watershed(img,markers)
img[markers==-1]=[255,0,0]
plt.subplot(221),plt.imshow(unknow,"gray"),plt.title("unknow area")
plt.subplot(222),plt.imshow(sure_bg,"gray"),plt.title("back ground")
plt.subplot(223),plt.imshow(img,"gray"),plt.title("marked image")
plt.subplot(224),plt.imshow(markers,"gray"),plt.title("marked area")
plt.show()
实现GrabCut算法逻辑是,用矩形在画面中将前景包含起来,前景的所有要素都必须在框内,框外全部作为背景。如果图像出现错误,将前景错误标记为背景,或者将背景错误标记成前景,则手动标记,算法会更加准确地找到前景。这里的关键在于,关键时刻有人来进行标记前景后景,使前景最大限度地正确显示。
类似于聚类。每个前景像素都连接到源节点,每个背景像素都连接到接收器节点。 通过像素是前景/背景的概率来定义将像素连接到源节点/末端节点的边缘的权重。像素之间的权重由边缘信息或像素相似度定义。如果像素颜色差异很大,则它们之间的边缘将变低。然后使用mincut算法对图进行分割。它将图切成具有最小成本函数的两个分离的源节点和宿节点。成本函数是被切割边缘的所有权重的总和。剪切后,连接到“源”节点的所有像素都变为前景,而连接到“接收器”节点的像素都变为背景。 - 继续该过程,直到分类收敛为止。
使用cv.grabCut函数进行前景提取。
函数:cv.grabCut( InputArray img, InputOutputArray mask, Rect rect,
InputOutputArray bgdModel, InputOutputArray fgdModel,
int iterCount, int mode = GC_EVAL );
img:输入图像
mask:得到掩码矩阵,其值为以下四种
cv::GC_BGD == 0//表示是背景
cv::GC_FGD == 1//表示是前景
cv::GC_PR_BGD == 2//表示可能是背景
cv::GC_PR_FGD == 3//表示可能是前景
rect:指定的包含目标对象的矩阵
bdgModel:背景模型,如果为null,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型(CV_32FC1)图像,且行数只能为1,列数只能为13*5
fgdModel:前景模型,如果为null,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型(CV_32FC1)图像,且行数只能为1,列数只能为13x5;(bgdModel , fgdModel可以在 cv::GC_INIT_WITH_MASK下使用,可以在以往迭代的基础上用它们保存的信息继续迭代)
iterCount:指定迭代次数
mode:有三个值可用
cv::GC_INIT_WITH_RECT//用矩阵初始化grabCut
cv::GC_INIT_WITH_MASK//用掩码初始化grabCut
cv::GC_EVAL//执行分割
img=cv.imread("mario1.jpg")
img1=np.copy(img)
drawing=False
ix,iy=-1,-1
nx,ny=0,0
def draw(event,x,y,flag,param):
global drawing,ix,iy,nx,ny
if event==cv.EVENT_LBUTTONDOWN:
drawing=True
ix,iy=x,y
print(x,y)
if event==cv.EVENT_LBUTTONUP:
cv.rectangle(img1,(ix,iy),(x,y),(0,255,0),2)
drawing=False
nx,ny=x,y
print(x,y,"/",nx,ny)
for i in range(5):
while(1):
cv.imshow("image",img1)
k=cv.waitKey(1)&0xFF
if k==27:
break
cv.setMouseCallback("image", draw)
cv.destroyAllWindows()
mask=np.zeros(img.shape[:2],np.uint8)
bgdModel=np.zeros((1,65),np.float64)
fgdModel=np.zeros((1,65),np.float64)
rect=(ix,iy,nx,ny)
cv.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv.GC_INIT_WITH_RECT)
mask2=np.where((mask==2)|(mask==0),0,1).astype("uint8")
img=img*mask2[:,:,np.newaxis]
plt.imshow(img)
plt.show()