这里会用到几何学中的极几何(Epipolar Geometry),它属于立体视觉(stereo vision)几何学,立体视觉是计算机视觉的一个分支,它从同一物体的两张不一样图像提取三维信息。html
极几何的工做原理:python
它跟踪从摄像头到图像上每一个物体的虚线,而后再第二张图像作一样的操做,并根据同一物体对应的线的交叉来计算距离。git
在使用 OpenCV 如何使用极几何来计算所谓的视差图,它是如图像中检测到不一样深度的基本表示,这样就可以提取出一张图片的前景部分而抛弃其他部分。github
注意:进行深度估计须要同一物体在不一样视角下拍摄的两幅图像,可是要注意这两幅图像是距物体相同距离,不然计算将会失败,视差图也就没有意义。算法
下图为工做原理示意图segmentfault
下面例子是使用同一物体的两幅图像来计算视差图,距离摄像头近的点在视差图中会有更明亮的颜色,黑色区域表明两幅图像的差别部分。数组
import numpy as np import cv2 def update(val = 0): # disparity range is tuned for 'aloe' image pair stereo.setBlockSize(cv2.getTrackbarPos('window_size','disparity')) stereo.setUniquenessRatio(cv2.getTrackbarPos('uniquenessRatio','disparity')) stereo.setSpeckleWindowSize(cv2.getTrackbarPos('speckleWindowsize','disparity')) stereo.setSpeckleRange(cv2.getTrackbarPos('speckleRange','disparity')) stereo.setDisp12MaxDiff(cv2.getTrackbarPos('disp12MaxDiff','disparity')) print('computing disparity...') disp = stereo.compute(imgL, imgR).astype(np.float32) / 16.0 # cv2.imshow('left', imgL) cv2.imshow('disparity', (disp-min_disp) / num_disp) if __name__ == '__main__': window_size = 5 min_disp = 16 num_disp = 192 - min_disp blockSize = window_size uniquenessRatio = 1 speckleRange = 3 speckleWindowSize = 3 disp12MaxDiff = 200 P1 = 600 P2 = 2400 # 加载两幅图 imgL = cv2.imread('tsukuba_right.jpg') imgR = cv2.imread('tsukuba_left.jpg') cv2.namedWindow('disparity') cv2.createTrackbar('speckleRange','disparity',speckleRange,50,update) cv2.createTrackbar('window_size','disparity',window_size,21,update) cv2.createTrackbar('speckleWindowSize','disparity',speckleWindowSize,200,update) cv2.createTrackbar('uniquenessRatio','disparity',uniquenessRatio,50,update) cv2.createTrackbar('disp12MaxDiff','disparity',disp12MaxDiff,250,update) # 建立一个StereoSGBM实例,是一种计算视图差的算法 # 并建立几个跟踪条来调整算法参数,而后调用update函数 # update函数将跟踪条的值传给StereoSGBM实例 # StereoSGBM是semiglobal block matching 的缩写 stereo = cv2.StereoSGBM_create( minDisparity = min_disp, numDisparities = num_disp, blockSize = window_size, uniquenessRatio = uniquenessRatio, speckleRange = speckleRange, speckleWindowSize = speckleWindowSize, disp12MaxDiff = disp12MaxDiff, P1 = P1, P2 = P2 ) update() cv2.waitKey()
GrabCut是一种基于图切割的图像分割方法。GrabCut算法是基于Graph Cut算法的改进。dom
基于要被分割对象的指定边界框开始,使用高斯混合模型估计被分割对象和背景的颜色分布(注意,这里将图像分为被分割对象和背景两部分)。简而言之,就是只需确认前景和背景输入,该算法就能够完成前景和背景的最优分割。ide
该算法利用图像中纹理(颜色)信息和边界(反差)信息,只要少许的用户交互操做就可获得较好的分割效果,和分水岭算法比较类似,但计算速度比较慢,获得的结果比较精确。若从静态图像中提取前景物体(例如从一个图像剪切到另一个图像),采用GrabCut算法是最好的选择。函数
cv2.grabCut(img, mask, rect, bgdModel, fgdModel, iterCount[, mode]) -> mask, bgdModel, fgdModel
参数:
img - 8 位 3 通道图像。这也说明输入的为彩色图像
mode - 操做模式,能够是 GrabCutModes 模式中的一种。枚举值enmu
引用原语句
enum cv::GrabCutModes { cv::GC_INIT_WITH_RECT = 0, cv::GC_INIT_WITH_MASK = 1, cv::GC_EVAL = 2 }详细内容
Enumerator GC_INIT_WITH_RECT The function initializes the state and the mask using the provided rectangle. After that it runs iterCount iterations of the algorithm.
该函数使用提供的矩形初始化状态和掩码。以后,它运行算法的iterCount迭代
GC_INIT_WITH_MASK The function initializes the state using the provided mask. Note that GC_INIT_WITH_RECT and GC_INIT_WITH_MASK can be combined. Then, all the pixels outside of the ROI are automatically initialized with GC_BGD .
该函数使用提供的掩码初始化状态。请注意,能够组合GC_INIT_WITH_RECT和GC_INIT_WITH_MASK。而后,使用GC_BGD自动初始化ROI外部的全部像素。
GC_EVAL The value means that the algorithm should just resume.
该值意味着算法应该恢复
参考:
https://docs.opencv.org/3.1.0/d7/d1b/group__imgproc__misc.html
https://docs.opencv.org/3.1.0/d7/d1b/group__imgproc__misc.html#gaf8b5832ba85e59fc7a98a2afd034e558
mask - 输入/输出 8 位单通道掩码, 当mode = GC_INIT_WITH_RECT时,该函数初始化掩码。若使用掩码进行初始化,那么 mask 保存初始化掩码信息,在执行分割的时候,也将用户交互所设定的前景与背景保存到mask中,而后再传入grabCut函数;在处理结束以后,mask中会保存结果。mask只能取如下四种值:
rect - 包含分割对象的矩形ROI(Region of Interesting,ROI,感兴趣区域),ROI外部的像素标记为背景,ROI内部的像素标记为前景。该参数仅在mode=GC_INIT_WITH_RECT状况下使用。(用于限定须要进行分割的图像范围,只有该矩形窗口内的图像部分才被处理)。注意,矩形的形式为(x, y, 宽, 高 ) 。
bgdModel - 背景模型的临时数组。 处理同一图像时,请勿修改它。
fgdModel - 前景模型的临时数组。 处理同一图像时,请勿修改它。
iterCount - 返回结果以前算法应该进行的迭代次数。 请注意,可使用mode == GC_INIT_WITH_MASK或mode == GC_EVAL进一步调用结果。
1.在图片中定义含有(一个或者多个)物体的矩形
2.矩形外的区域被自动认为是背景
3.对于用户定义的矩形区域,可用背景中的数据来区别它里面的前景和背景区域
4.用高斯混合模型来对背景和前景建模,并将未定义的像素标记为可能的前景或背景
5.图像中欧冠的每个像素都被看做经过虚拟边与周围像素相链接,而每条边都有一个属于前景或背景的几率,这基于它与周围颜色上的类似性
6.每个像素(即算法中的节点)会与一个前景或背景节点连接
7.在节点完成连接后,若节点之间的边属于不一样终端,则会切断它们之间的边,这就能将图像各部分分割出来
import numpy as np import cv2 from matplotlib import pyplot as plt #使用分水岭和GrabCut算法进行物体分割 img = cv2.imread('small.jpg') # img.shape=(1039, 690, 3) # img.shape[0:2]=(1039, 690) mask = np.zeros(img.shape[:2],np.uint8) # 背景色bgdModel,前景色fgdModel bgdModel = np.zeros((1,65),np.float64) fgdModel = np.zeros((1,65),np.float64) # 感兴趣区域ROI的x,y,宽度,高度 rect = (100,1,500,1000) # 得到返回值mask、bgdModel、fgdModel。 # 目标图像、掩码、感兴趣区域,背景、前景、算法迭代次数、操做模式 cv2.grabCut(img,mask,rect,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT) # 通过图像分割法grabCut处理以后, # print(set(mask.ravel())) -> {0,2,3} # mask的掩码元素{0}->{0,2,3} # where(condition,x,y),condition为array_like或bool # 真yield x,假yield y # mask==0背景、==1前景、==2可能的背景、==3可能的前景 # 当为背景/多是背景时赋0,当为前景/可能背景赋1 mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8') # mask2.shape=(1039,690), # img.shape=(1039,690,3) # 二者乘积则报错: # 操做数没法与形状一块儿广播 # ValueError: operands could not be broadcast together with shapes (1039,690,3) (1039,690) # 为了保持数形一致,增长np.newaxis # mask2[:,:,np.newaxis].shape=(1039,690,1) # 这样当行列值不相等时可进行广播计算 # 通过计算后,将背景色赋值为0,即为黑色 img = img*mask2[:,:,np.newaxis] # subplot(121)建立1行2列,当前位置为1 plt.subplot(121), plt.imshow(img) plt.title("grabcut"), plt.xticks([]), plt.yticks([]) # subplot(122)当前位置为2 plt.subplot(122), plt.imshow(cv2.cvtColor(cv2.imread('small.jpg'), cv2.COLOR_BGR2RGB)) plt.title("original"), plt.xticks([]), plt.yticks([]) plt.show()
运行
opencv python 基于GrabCut算法的交互式前景提取
OpenCV 深度估计与分割 - 深度估计与分割 该博客与opencv 3 有些同步,挺好的
分水岭算法 watershed algorithm方法是一种基于边界点的分割算法。
任何灰度图均可以当作带有等高线的地形图,灰度值越高,其海拔越高。若向该地貌中注水,则海拔低处优先被淹没,同时水也会聚集,为了防止水的聚集,则筑坝对不一样区域的水源进行分割,此时的坝就是区域的边界。
从上述较为形象化的分析能够看出:因为灰度值图像中的噪声和局部的不规则性,该方法可能会形成过分分割
针对上述的缺点进行优化 - 分水岭的标记控制 Marker-controlled watershed。该方法能够有效地防止过分分割。
详细内容参看
图像分割与数学形态学(IMAGE SEGMENTATION AND MATHEMATICAL MORPHOLOGY),该文章来源于 数学形态学中心 / MINES ParisTech的图像处理实验室
基于边缘的图像分割——分水岭算法(watershed)算法分析(附opencv源码分析)
做用:基于标记的分水岭算法进行图像分割
cv2.watershed(image, markers) -> markers
参数:
image - 8 位 3通道图像
markers - 输入 / 输出标记的32位单通道图像(映射),它应该与图像大小相同。
注意:
图像 image 参数必须提早处理,使用正(\>0)索引粗略勾画图像标记中的所需区域。所以,每一个区域被表示为具备像素值1,2,3等的一个或多个连通份量。 可使用# findContours 和 # drawContours 从二进制掩码中检索此类标记(请参阅watershed.cpp演示)。标记是将来图像区域的“种子”。标记中的全部其余像素(其与轮廓区域的关系未知且应由算法定义)应设置为0。 在函数输出中,标记中的每一个像素设置为“种子”组件的值,或者设置为区域之间的边界处的-1。
从代码和最终结果里能够大体的看出算法的流程:
1.进行灰度化
2.高斯滤波以消除噪声的干扰
3.用canny算子检测边缘
4.用findcontours查找轮廓
5.利用轮廓特征,实现图像分割
这段代码没有验证。直接复制。
import numpy as np import cv2 from matplotlib import pyplot as plt #使用分水岭算法进行图像分割 img = cv2.imread('timg.jpg') gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #将颜色转为灰度后,可为图像设一个阈值,将图像分为两部分:黑色部分和白色部分 ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU) # noise removal 噪声去除,morphologyEx是一种对图像进行膨胀以后再进行腐蚀的操做 kernel = np.ones((3,3),np.uint8) opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2) # sure background area 肯定背景区域,图像进行膨胀操做 sure_bg = cv2.dilate(opening,kernel,iterations=3) # Finding sure foreground area,经过distanceTransform来获取肯定的前景区域 dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5) ret, sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0) # Finding unknown region sure_fg = np.uint8(sure_fg) unknown = cv2.subtract(sure_bg,sure_fg) # Marker labelling ret, markers = cv2.connectedComponents(sure_fg) # Add one to all labels so that sure background is not 0, but 1 markers = markers+1 # Now, mark the region of unknown with zero markers[unknown==255] = 0 markers = cv2.watershed(img,markers) img[markers == -1] = [255,0,0] plt.imshow(img) plt.show()
运行
该代码没有调试,没有深究代码中的逻辑关系,部分不理解的函数做用及参数、返回值没有深究。
附原图:
基于边缘的图像分割——分水岭算法(watershed)算法分析(附opencv源码分析) 从源代码深层次理解分水岭算法
数字图像处理——图像分割(五)——分水岭算法 分水岭算法的理解及相关的数学分析
OpenCV—图像分割中的分水岭算法原理与应用 对其代码应用有较多地描写
OpenCV 深度估计与分割 - 深度估计与分割 重要参考博文
opencv(28)---GrabCut & FloodFill图像分割 虽然是转载的,感受还可哟