OpenCV-图像分割与提取

  • Post author:
  • Post category:其他



图像分割与提取的概念


在图像处理的过程中, 经常需要从图像中将前景对象作为目标图像提取出来。例如无人驾驶技术, 我们关心的是周围的交通工具, 其他障碍物等, 而对于背景本身并不关注, 故而, 我们需要将这些东西从图片(视频)中提取出来, 而忽略那些只有背景的图像。


常见的图像分割方法

  1. 基于阈值的分割方法:全局阈值法、局部阈值法等
  2. 基于区域的分割方法:分水岭方法、区域生长法等
  3. 基于边缘的分割方法:Canny边缘检测、轮廓检测等
  4. 基于特定理论的分割方法:聚类、模糊集等(机器学习)


    分水岭算法的概念


    图像的灰度空间很像地球表面的整个地理结构,每个像素的灰度值代表高度。其中的灰度值较大的像素连成的线可以看做山脊,也就是分水岭。

    当水平面上升到一定高度时,水就会溢出当前山谷,可以通过在分水岭上修大坝,从而避免两个山谷的水汇集,这样图像就被分成2个像素集,一个是被水淹没的山谷像素集,一个是分水岭线像素集。最终这些大坝形成的线就对整个图像进行了分区,实现对图像的分割。

    在这里插入图片描述
    在这里插入图片描述



分水岭算法的步骤


原图


在这里插入图片描述



二值化

import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread("coins.jpg")
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)

plt.imshow(thresh,cmap='gray'
plt.show()

在这里插入图片描述



形态学操作(移除噪声)

先使用开运算去除图像中的细小白色噪点,然后通过腐蚀运算移除边界像素,得到的图像中的白色区域肯定是真实前景,即靠近硬币中心的区域;膨胀运算使得一部分背景成为了物体的边界,得到的图像中的黑色区域肯定是真实背景,即远离硬币傅区域。剩下的区域(硬币的边界附近)还不能确定是前景函数背景,可通过膨胀图减去腐蚀图得到。


开运算

kernel = cv2.getStructuringElement(cv2.MORPH_RECT,  (3,3))
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN,kernel,iterations=2)
plt.imshow(opening,cmap='gray')
plt.show()

在这里插入图片描述


膨胀-确认背景区域

sure_bg = cv2.dilate(opening,kernel,iterations=2)
plt.imshow(sure_bg,cmap='gray')
plt.show()

在这里插入图片描述


腐蚀-确定前景区域

sure_fg = cv2.erode(opening,kernel,iterations=2)
plt.imshow(sure_fg,cmap='gray')
plt.show()

在这里插入图片描述


减法-不确定区域

unknown = cv2.subtract(sure_bg,sure_fg)
plt.imshow(unknown,cmap='gray')
plt.show()

在这里插入图片描述


距离变换


剩下的区域不确定是硬币还是背景,这些区域通常在前景和背景接触的区域(或者两个不同硬币接触的区域),我们称之为边界。通过分水岭算法应该能找到确定的边界。

由于硬币之间彼此接触,我们使用另一个确定前景的方法,就是带阈值的距离变换。

下面左边的图为得到的距离转换图像,其实每个像素的值为其到最近的背景像素(灰度值为0)的距离,可以看到硬币的中心像素值最大(中心离背景像素最远)。对其进行二值处理就得到了分离的前景图(下面中间的图),白色区域肯定是硬币区域,而且还相互分离,下面右边的图为之前的膨胀图减去中间这个表示前景的图。

dist_transform=cv2.distanceTransform(opening,cv2.DIST_L2,5)
cv2.normalize(dist_transform,dist_transform,0,1.0,cv2.NORM_MINMAX)
plt.imshow(dist_transform,cmap='gray')
plt.show()

在这里插入图片描述


阈值变换-寻找前景区域

ret,sure_fg = cv2.threshold(dist_transform,0.5*dist_transform.max(),255,0)
plt.imshow(sure_fg,cmap='gray')
plt.show()

在这里插入图片描述


减法-寻找不确定区域

sure_fg = np.uint8(sure_fg)
unknow = cv2.subtract(sure_bg,sure_fg)
plt.imshow(unknow,camp='gray')
plt,show()

在这里插入图片描述


标记图像


现在我们可以确定哪些是硬币区域,哪些是背景区域。然后创建标记(marker,它是一个与原始图像大小相同的矩阵,int32数据类型),表示其中的每个区域,分水岭算法将标记为0的区域视为不确定区域,将标记为1的区域视为背景区域,将标记大于1的正整数表示我们想得到的前景。

我们可以使用cv2.connectedComponent()来实现这个功能,它是用0标记图像的背景,用大于0的整数标记其他对象,所以我们需要对其进行加一,用1来标记图像的背景。

ret,markers = cv2.connectedComponnents(sure_fg)
markers = markers+1
markers[unknown==255] = 0


显示makers

markers_copy = markers_copy()
markers_copy[markers==0] = 150
markers_copy[markers==1] = 0
markers_copy[markers>1] = 255

markers_copy = np.uint8(markers_copy)
plt.imshow(markers_copy,cmap='gray')
plt.show()



应用分水岭算法

markers = cv2.watershed(img,markers)
img[markers==-1] = [0,-,255]
img = cv2.cvtColor(img,cv2.COLOR_RGB2BGR)
plt.figure(1)
plt.imshow(markers,cmap='gray')
plt.figure(2)
plt.imshow(img,cmap='gray')
plt.show()

在这里插入图片描述



鼠标交互

opencv的鼠标交互操作主要通过两个函数实现:

第一个是

cv2.setMouseCallback(windowName, onMouse [, param])


第二个是setMouseCallback()的第二个参数,称为鼠标回调函数

onMouse(event, x, y, flags, param)



setMouseCallback()

cv2.setMouseCallback(windowName, onMouse [, param])

windowName:必需。类似于cv.imshow()函数,opencv具体操作哪个窗口以窗口名作为识别标识,这有点类似窗口句柄的概念。

onMouse:必需。鼠标回调函数。鼠标回调函数的定义是onMouse(event, x, y, flags, param),我们想要做什么鼠标操作,都是在这个函数内实现。


onMouse()

onMouse(event, x, y, flags,param)

event:由回调函数根据鼠标对图像的操作自动获得,内容包含左键点击,左键弹起,右键点击…等等等非常多的操作。

x,y:由回调函数自动获得,记录了鼠标当前位置的坐标,坐标mothod以图像左上角为原点(0, 0),x方向向右为正,y方向向下为正。

flags:记录了一些专门的操作,下面有说明。

param:从setMouseCallback()里传递过来的参数。该参数在setMouseCallback()处是可选参数,所以可以不设置。


event类型



cv2.EVENT_MOUSEMOVE

鼠标滑动


cv2.EVENT_LBUTTONDOWN

左键点击


cv2.EVENT_RBUTTONDOWN

右键点击


cv2.EVENT_MBUTTONDOWN

中键点击


cv2.EVENT_LBUTTONUP

左键放开


cv2.EVENT_RBUTTONUP

右键放开


cv2.EVENT_MBUTTONUP

中键放开


cv2.EVENT_LBUTTONDBLCLK

左键双击


cv2.EVENT_RBUTTONDBLCLK

右键双击


cv2.EVENT_MBUTTONDBLCLK

中键双击


cv2.EVENT_FLAG_LBUTTON

左键拖曳


cv2.EVENT_FLAG_RBUTTON

右键拖曳


cv2.EVENT_FLAG_MBUTTON

中键拖曳


cv2.EVENT_FLAG_CTRLKEY

按Ctrl不放事件


cv2.EVENT_FLAG_SHIFTKEY

按Shift不放事件


cv2.EVENT_FLAG_ALTKEY

按Alt不放事件



鼠标画圆

import cv2
def draw_circle(event,x,y,flags,param):
    if event == cv2.EVENT_LBUTTONDOWN:
        cv2.circle(img,center=(x,y),redius=5,
                   color=(255,0,0),thickness=-1)
       
    elif event == cv2.EVENT_RBUTTONDOWN:
        cv2.circle(img,center=(x,y),radius=5,
                   color=(0,255,0),thickness=1)
img = cv2.cv2.imread("peppa.jpg")

cv2.namedWindow(winname='drawing')
cv2.setMouseCallback('drawing',draw_circle)

while True:
    cv2.imshow('drawing',img)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cv2.destroyAllWindows()

在这里插入图片描述


使用鼠标交互函数,实现图像数据标注软件demo

import cv2
drawing = False
start = (-1,-1)
def mouse_event(event,x,y,flags,param):
    global start,drawing,made
    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)
    elif event == cv2.EVENT_LBUTTONUP:
        drawing = False
        cv2.circle(img,(x,y),5(0,0,255),-1)
        cv2.imwrite("draw.png",img)
        
img = cv2.imread('peppa.jpg')

cv2.namedWindow(winname='drawing')
cv2.setMouseCallback('drawing',mouse_event)

while True:
    cv2.imshow('drawing',img)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cv2.destroyAllWindows()

在这里插入图片描述



区域生长算法

选取某个种子点(一般实际交互时就是鼠标点击的位置),从图像种子点位置开始,将种子点相邻的符合某个阈值范围内的像素添加到生长区域中,接着判断下一个像素点,直到没有可以符合条件的像素为止,此时分割完毕。


区域生长算法的步骤


[

定义point类

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.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 getGrayDiff(img,currentPoint,tmpPoint):
    return abs(int(img[currentPoint.x,currentPoint.y]) - int(img[tmpPoint.x,tmpPoint]))

def selectConnects(p):
    if p == 8:
        connects = [Print(-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):
    height,width = img.shape
    seedMark = np.zeros(img.shape)
    seedList = []
    for seed in seeds:
        seedList.append(seed)
    label = 1
    p=4
    connects = selectConnects(p)
    while(len(seedList)>0):
        currentPoint = seedList.pop(0)
        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))
            if grayDiff < thresh and seedMark[tmpX,tmpY] == 0:
                seedMark[tmpX,tmpY] = label
                seedList.append(Point(tmpX,tmpY))
    return seedMark



使用鼠标交互实现区域生长算法

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():
    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)]
    return connects
def regionGrow(img,seeds,thresh,p = 1):
    height, weight = img.shape
    seedMark = np.zeros(img.shape)
    seedList = []
    for seed in seeds:
        seedList.append(seed)
        label = 1
    connects = selectConnects()
    while(len(seedList)>0):
        currentPoint = seedList.pop(0)
        seedMark[currentPoint.x,currentPoint.y] = label
        for i in range(8):
            tmpX = currentPoint.x + connects[i].x
            tmpY = currentPoint.y + connects[i].y
            if tmpX < 0 or tmpY < 0 or tmpX >= height or tmpY >= weight:
                continue
            grayDiff = getGrayDiff(img,currentPoint,Point(tmpX,tmpY))
            if grayDiff < thresh and seedMark[tmpX,tmpY] == 0:
                seedMark[tmpX,tmpY] = label
                seedList.append(Point(tmpX,tmpY))
    return seedMark
def mouse(event,x,y,flages,param):
    if event==cv2.EVENT_LBUTTONDOWN:
        seeds=[Point(x,y)]
        binaryImg=regionGrow(img,seeds,3)
        cv2.imshow("xxw1",binaryImg)
img=cv2.imread("peppa.jpg",0)
cv2.namedWindow('gray')
cv2.setMouseCallback("gray",mouse)
while True:
    cv2.imshow("xxw",img)
    if cv2.waitKey(1)==27:
        break
cv2.destroyAllWindows()

在这里插入图片描述



版权声明:本文为h6111原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。