©FuXianjun
在图像处理的过程中, 经常需要从图像中将前景对象作为目标图像提取出来。例如无人驾驶技术, 我们关心的是周围的交通工具, 其他障碍物等, 而对于背景本身并不关注, 故而, 我们需要将这些东西从图片(视频)中提取出来, 而忽略那些只有背景的图像。
图像的灰度空间很像地球表面的整个地理结构,每个像素的灰度值代表高度。其中的灰度值较大的像素连成的线可以看做山脊,也就是分水岭。
当水平面上升到一定高度时,水就会溢出当前山谷,可以通过在分水岭上修大坝,从而避免两个山谷的水汇集,这样图像就被分成2个像素集,一个是被水淹没的山谷像素集,一个是分水岭线像素集。最终这些大坝形成的线就对整个图像进行了分区,实现对图像的分割。
opencv的鼠标交互操作主要通过两个函数实现:
第一个是cv2.setMouseCallback(windowName, onMouse [, param])
第二个是setMouseCallback()的第二个参数,称为鼠标回调函数onMouse(event, x, y, flags, param)
cv2.setMouseCallback(windowName, onMouse [, param])
windowName:必需。类似于cv.imshow()函数,opencv具体操作哪个窗口以窗口名作为识别标识,这有点类似窗口句柄的概念。
onMouse:必需。鼠标回调函数。鼠标回调函数的定义是onMouse(event, x, y, flags, param),我们想要做什么鼠标操作,都是在这个函数内实现。
onMouse(event, x, y, flags,param)
event:由回调函数根据鼠标对图像的操作自动获得,内容包含左键点击,左键弹起,右键点击…等等等非常多的操作。
x,y:由回调函数自动获得,记录了鼠标当前位置的坐标,坐标以图像左上角为原点(0, 0),x方向向右为正,y方向向下为正。
flags:记录了一些专门的操作,下面有说明。
param:从setMouseCallback()里传递过来的参数。该参数在setMouseCallback()处是可选参数,所以可以不设置。
选取某个种子点(一般实际交互时就是鼠标点击的位置),从图像种子点位置开始,将种子点相邻的符合某个阈值范围内的像素添加到生长区域中,接着判断下一个像素点,直到没有可以符合条件的像素为止,此时分割完毕。
使用鼠标交互函数,实现图像数据标注
import cv2
drawing = False # 是否开始画图
start = (-1,-1)
# 鼠标的回调函数的参数格式是固定的,不要随意更改
def mouse_event(event,x,y,flags,param):
global start,drawing, mode
#左键按下,开始画图
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
start = (x,y)
# 鼠标移动,画图
elif event == cv2.EVENT_MOUSEMOVE:
if drawing:
cv2.circle(img,(x,y),5,(0,0,255),-1)
cv2.imwrite("drawing.png",img)
img = cv2.imread("yiqing.png")
cv2.namedWindow(winname="drawing")
cv2.setMouseCallback("drawing",mouse_event)
while True:
cv2.imshow("drawing",img)
# 按 q 键推出
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
import cv2
# 编写回调函数
def draw_circle(event,x,y,flags,param):
#鼠标左键按下去,实心圆
if event == cv2.EVENT_LBUTTONDOWN:
cv2.circle(img,center=(x,y),radius=5,
color=(0,255,0),thickness=-1)
# 鼠标右键按下去,空心圆
elif event == cv2.EVENT_MOUSEMOVE:
cv2.circle(img,center=(x,y),radius=5,
color=(0,255,0),thickness=1)
img = cv2.imread("yiqing.png")
cv2.namedWindow(winname="drawing")
cv2.setMouseCallback("drawing",draw_circle)
while True:
cv2.imshow("drawing",img)
# 按 q 键推出
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
固定种子点的区域生长算法
import cv2
import numpy as np
class Point(object):
def __init__(self,x,y):
self.x = x
self.y = y
def getX(self):
return self.x
def getY(self):
return self.y
def getGrayDiff(img,currentPoint,tmpPoint):
return abs(int(img[currentPoint.x,currentPoint.y]) - int(img[tmpPoint.x,tmpPoint.y]))
def selectConnects(p):
if p == 8:
connects = [Point(-1,-1),Point(0,-1),Point(1,-1),Point(1,0),Point(1,1), \
Point(0,1),Point(-1,1),Point(-1,0)]# 八邻域
else:
connects = [Point(0,-1),Point(1,0),Point(0,1),Point(-1,0)]# 四邻域
return connects
def regionGrow(img,seeds,thresh):
# 读取图像的宽高,并建立一个和原图大小相同的seedMark
height,width = img.shape
seedMark = np.zeros(img.shape)
#将定义的种子点放入种子点序列seedList
seedList = []
for seed in seeds:
seedList.append(seed)
label = 1
# 选择邻域
# connects = selectConnects(p)
p=4
connects = selectConnects(p)
#逐个点开始生长,生长的结束条件为种子序列为空,既没有生长点
while(len(seedList)>0):
#弹出种子点序列的第一个点作为生长点
currentPoint = seedList.pop(0)#弹出第一个元素
# 并将生长点对应seedMark点赋值label(1),即为白色
seedMark[currentPoint.x,currentPoint.y]= label
#以种子点为中心,四邻域的像素进行比较
for i in range(p):
tmpX = currentPoint.x + connects[i].x
tmpY = currentPoint.y + connects[i].y
#判断是否为图像外的点,若是则跳过。如果种子点是图像的边界点,邻域点就会落在图像外
if tmpX < 0 or tmpY < 0 or tmpX >= height or tmpY >=width:
continue
#判断邻域点和种子点的差值
grayDiff = getGrayDiff(img,currentPoint,Point(tmpX,tmpY))
# 如果邻域点和种子点的差值小于阈值并且是没有被分类的点,则认为是和种子点同类,赋值label
# 并作为下一个种子点放入seedList
if grayDiff < thresh and seedMark[tmpX,tmpY] == 0:
seedMark[tmpX,tmpY] = label
seedList.append(Point(tmpX,tmpY))
return seedMark
# 读入图像的灰度图像
img = cv2.imread("peppa.jpg",0)
cv2.namedWindow('gray')
# 选定种子点
seeds = [Point(251,208)]
binaryImg = regionGrow(img,seeds,5)
cv2.imshow("segment",binaryImg)
cv2.imshow("gray",img)
cv2.waitKey(0)
cv2.destroyAllWindows()