图像处理之分割图像

2017-12-17 17:50:34 sinat_36458870 阅读数 68017

第一天

老师:你知道么,今天有人问了我一个问题。
~.我:什么?
老师:他说很难。
~.我:关于什么的?
老师:图像处理。

~.我:喔,你说说看,我确实做了不少图像处理的东西(心里默念,你不知知道你给过我多少图像吗?)
老师:好嘞!在用深度学习的时候,比如说面对一张图像,对某个区域感兴趣怎么办?
~.我:他傻啊,切割出来啊,只需要训练感兴趣的部分就好啦。

老师:哎,那你给我一个教程,我正好顺手把他的问题解决了。
~.我:好的(黑人脸.gif)
老师:我回头把图片数据发给你。
~.我:好的好的,老师,by the way, 有多少数据啊?
老师:也不多,一个U盘够了,这样吧,明天你过来拷一下吧。
~.我:好的(hello?一个U盘?)

这里写图片描述

第二天

这里写图片描述


有这么一个文件
看了里面。。。我要爆炸了。。。
这里写图片描述

> 598M * 15 = 8970M = 8.97G 我的个妈呀。

打开一看 全是密密麻麻的------虫子!!!
为了视觉体验,自动屏蔽,请大家自行去谷歌:虫子、worm、bug、insects。。。

三天后

~.我: 老师, 我就给一个方法啊, 不同的虫子他们可以自己调阈值和方法,我已经有写说明文件。
老师: 好的,我看看。

考虑到视觉忍受能力,我用一个可爱的虫子做为一个示例,其他的都差不多,大家自行尝试。

目标是把虫子区域抠出来

这里写图片描述


环境:

例图:谷歌,可爱的虫子–image
软件:Anaconda 4.20,Opencv3.2
OpenCv的安装:
1.1安装Python3.60
1.2下载安装opencv3.2


具体思路如下:

1.获取图片,这个简单哈

img_path = r'C:\Users\aixin\Desktop\chongzi.png'
img = cv2.imread(img_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

看,这不就是你处理初始的样子?
这里写图片描述

2.转换灰度并去噪声

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (9, 9),0) 

我们可以得到这两张图,第一张是灰度图,第二张是去噪之后的,另外说一下,去噪咱们有很多种方法,均值滤波器、高斯滤波器、中值滤波器、双边滤波器等。

这里取高斯是因为高斯去噪效果是最好的。

这里写图片描述

3.提取图像的梯度

gradX = cv2.Sobel(gray, ddepth=cv2.CV_32F, dx=1, dy=0)
gradY = cv2.Sobel(gray, ddepth=cv2.CV_32F, dx=0, dy=1)

gradient = cv2.subtract(gradX, gradY)
gradient = cv2.convertScaleAbs(gradient)

以Sobel算子计算x,y方向上的梯度,之后在x方向上减去y方向上的梯度,通过这个减法,我们留下具有高水平梯度和低垂直梯度的图像区域。

此时,我们会得到

这里写图片描述

4.我们继续去噪声

考虑到图像的孔隙 首先使用低通滤泼器平滑图像, 这将有助于平滑图像中的高频噪声。 低通滤波器的目标是降低图像的变化率。
如将每个像素替换为该像素周围像素的均值, 这样就可以平滑并替代那些强度变化明显的区域。

对模糊图像二值化,顾名思义,就是把图像数值以某一边界分成两种数值,细节我会附在文章底部,如果还是不懂,去cao文档吧。

blurred = cv2.GaussianBlur(gradient, (9, 9),0)
(_, thresh) = cv2.threshold(blurred, 90, 255, cv2.THRESH_BINARY)

此时,我们会得到
这里写图片描述

其实就算手动分割我们也是需要找到一个边界吧,可以看到轮廓出来了,但是我们最终要的是整个轮廓,所以内部小区域就不要了

5.图像形态学(牛逼吧、唬人的)

在这里我们选取ELLIPSE核,采用CLOSE操作,具体细节你依旧可以参考我的附录文档,及拓展。

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (25, 25))
closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)

此时,我们会得到
这里写图片描述

6.细节刻画

从上图我们可以发现和原图对比,发现有细节丢失,这会干扰之后的昆虫轮廓的检测,要把它们扩充,分别执行4次形态学腐蚀与膨胀(附录文档)

closed = cv2.erode(closed, None, iterations=4)
closed = cv2.dilate(closed, None, iterations=4)

此时,我们会得到
这里写图片描述

7.找出昆虫区域的轮廓

此时用cv2.findContours()函数
第一个参数是要检索的图片,必须是为二值图,即黑白的(不是灰度图)

(_, cnts, _) = cv2.findContours(
	参数一: 二值化图像
	closed.copy(),
	参数二:轮廓类型
    # cv2.RETR_EXTERNAL,             #表示只检测外轮廓
    # cv2.RETR_CCOMP,                #建立两个等级的轮廓,上一层是边界
    # cv2.RETR_LIST,                 #检测的轮廓不建立等级关系
    # cv2.RETR_TREE,                 #建立一个等级树结构的轮廓
    # cv2.CHAIN_APPROX_NONE,         #存储所有的轮廓点,相邻的两个点的像素位置差不超过1
    参数三:处理近似方法
    # cv2.CHAIN_APPROX_SIMPLE,         #例如一个矩形轮廓只需4个点来保存轮廓信息
    # cv2.CHAIN_APPROX_TC89_L1,
    # cv2.CHAIN_APPROX_TC89_KCOS
    )

8.画出轮廓

找到轮廓了,接下来,要画出来的,即用cv2.drawContours()函数。

c = sorted(cnts, key=cv2.contourArea, reverse=True)[0]

# compute the rotated bounding box of the largest contour
rect = cv2.minAreaRect(c)
box = np.int0(cv2.boxPoints(rect))

# draw a bounding box arounded the detected barcode and display the image
draw_img = cv2.drawContours(img.copy(), [box], -1, (0, 0, 255), 3)
cv2.imshow("draw_img", draw_img)

此时,我们会得到
这里写图片描述

9.裁剪出来就完成啦

方法嘛,这不就是么,找到这四个点切出来就好啦
我们放大一点看一下细节
这里写图片描述+

Xs = [i[0] for i in box]
Ys = [i[1] for i in box]
x1 = min(Xs)
x2 = max(Xs)
y1 = min(Ys)
y2 = max(Ys)
hight = y2 - y1
width = x2 - x1
crop_img= img[y1:y1+hight, x1:x1+width]
cv2.imshow('crop_img', crop_img)

其实,box里保存的是绿色矩形区域四个顶点的坐标。 我将按下图红色矩形所示裁剪昆虫图像。
找出四个顶点的x,y坐标的最大最小值。新图像的高=maxY-minY,宽=maxX-minX
这里写图片描述

终于我们得到了可爱的小虫子。
得到了目标区域,那么你想拿它干什么就干什么!我不管你哈。

考虑到现在的python教程一般都是一上来就是list、tuple什么的,而不是文件的读写和保存,包括批量读取等等,我特地加入了python版的文件批量读写和保存等附录文件。

快快快、夸我!

附录1.实现代码

#-*- coding: UTF-8 -*- 

'''
Author: Steve Wang
Time: 2017/12/8 10:00
Environment: Python 3.6.2 |Anaconda 4.3.30 custom (64-bit) Opencv 3.3
'''

import cv2
import numpy as np


def get_image(path):
    #获取图片
    img=cv2.imread(path)
    gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    
    return img, gray
    
def Gaussian_Blur(gray):
    # 高斯去噪
    blurred = cv2.GaussianBlur(gray, (9, 9),0)
    
    return blurred
    
def Sobel_gradient(blurred):
    # 索比尔算子来计算x、y方向梯度
    gradX = cv2.Sobel(blurred, ddepth=cv2.CV_32F, dx=1, dy=0)
    gradY = cv2.Sobel(blurred, ddepth=cv2.CV_32F, dx=0, dy=1)
    
    gradient = cv2.subtract(gradX, gradY)
    gradient = cv2.convertScaleAbs(gradient)
    
    return gradX, gradY, gradient

def Thresh_and_blur(gradient):
    
    blurred = cv2.GaussianBlur(gradient, (9, 9),0)
    (_, thresh) = cv2.threshold(blurred, 90, 255, cv2.THRESH_BINARY)
    
    return thresh
    
def image_morphology(thresh):
    # 建立一个椭圆核函数
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (25, 25))
    # 执行图像形态学, 细节直接查文档,很简单
    closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
    closed = cv2.erode(closed, None, iterations=4)
    closed = cv2.dilate(closed, None, iterations=4)
    
    return closed
    
def findcnts_and_box_point(closed):
    # 这里opencv3返回的是三个参数
    (_, cnts, _) = cv2.findContours(closed.copy(), 
        cv2.RETR_LIST, 
        cv2.CHAIN_APPROX_SIMPLE)
    c = sorted(cnts, key=cv2.contourArea, reverse=True)[0]
    # compute the rotated bounding box of the largest contour
    rect = cv2.minAreaRect(c)
    box = np.int0(cv2.boxPoints(rect))
    
    return box

def drawcnts_and_cut(original_img, box):
    # 因为这个函数有极强的破坏性,所有需要在img.copy()上画
    # draw a bounding box arounded the detected barcode and display the image
    draw_img = cv2.drawContours(original_img.copy(), [box], -1, (0, 0, 255), 3)
    
    Xs = [i[0] for i in box]
    Ys = [i[1] for i in box]
    x1 = min(Xs)
    x2 = max(Xs)
    y1 = min(Ys)
    y2 = max(Ys)
    hight = y2 - y1
    width = x2 - x1
    crop_img = original_img[y1:y1+hight, x1:x1+width]
    
    return draw_img, crop_img
    
def walk():
    
    img_path = r'C:\Users\aixin\Desktop\chongzi.png'
    save_path = r'C:\Users\aixin\Desktop\chongzi_save.png'
    original_img, gray = get_image(img_path)
    blurred = Gaussian_Blur(gray)
    gradX, gradY, gradient = Sobel_gradient(blurred)
    thresh = Thresh_and_blur(gradient)
    closed = image_morphology(thresh)
    box = findcnts_and_box_point(closed)
    draw_img, crop_img = drawcnts_and_cut(original_img,box)
    
    # 暴力一点,把它们都显示出来看看
    
    cv2.imshow('original_img', original_img)
    cv2.imshow('blurred', blurred)
    cv2.imshow('gradX', gradX)
    cv2.imshow('gradY', gradY)
    cv2.imshow('final', gradient)
    cv2.imshow('thresh', thresh)
    cv2.imshow('closed', closed)
    cv2.imshow('draw_img', draw_img)
    cv2.imshow('crop_img', crop_img)
    cv2.waitKey(20171219)
    cv2.imwrite(save_path, crop_img)

walk()

附录2.本篇文章精华函数说明


# 用来转化图像格式的
img = cv2.cvtColor(src, 
    COLOR_BGR2HSV # BGR---->HSV
    COLOR_HSV2BGR # HSV---->BGR
    ...)
# For HSV, Hue range is [0,179], Saturation range is [0,255] and Value range is [0,255]


# 返回一个阈值,和二值化图像,第一个阈值是用来otsu方法时候用的
# 不过现在不用了,因为可以通过mahotas直接实现
T = ret = mahotas.threshold(blurred)
ret, thresh_img = cv2.threshold(src, # 一般是灰度图像
    num1, # 图像阈值
    num2, # 如果大于或者num1, 像素值将会变成 num2
# 最后一个二值化参数
    cv2.THRESH_BINARY      # 将大于阈值的灰度值设为最大灰度值,小于阈值的值设为0
    cv2.THRESH_BINARY_INV  # 将大于阈值的灰度值设为0,大于阈值的值设为最大灰度值
    cv2.THRESH_TRUNC       # 将大于阈值的灰度值设为阈值,小于阈值的值保持不变
    cv2.THRESH_TOZERO      # 将小于阈值的灰度值设为0,大于阈值的值保持不变
    cv2.THRESH_TOZERO_INV  # 将大于阈值的灰度值设为0,小于阈值的值保持不变
)
thresh = cv2.AdaptiveThreshold(src, 
    dst, 
    maxValue, 
    # adaptive_method 
    ADAPTIVE_THRESH_MEAN_C,      
    ADAPTIVE_THRESH_GAUSSIAN_C,      
    # thresholdType
    THRESH_BINARY, 
    THRESH_BINARY_INV, 
    blockSize=3,
    param1=5
)


# 一般是在黑色背景中找白色物体,所以原始图像背景最好是黑色
# 在执行找边缘的时候,一般是threshold 或者是canny 边缘检测后进行的。
# warning:此函数会修改原始图像、
# 返回:坐标位置(x,y), 
(_, cnts, _) = cv2.findContours(mask.copy(), 
    # cv2.RETR_EXTERNAL,             #表示只检测外轮廓
    # cv2.RETR_CCOMP,                #建立两个等级的轮廓,上一层是边界
    cv2.RETR_LIST,                 #检测的轮廓不建立等级关系
    # cv2.RETR_TREE,                   #建立一个等级树结构的轮廓
    # cv2.CHAIN_APPROX_NONE,           #存储所有的轮廓点,相邻的两个点的像素位置差不超过1
    cv2.CHAIN_APPROX_SIMPLE,       #例如一个矩形轮廓只需4个点来保存轮廓信息
    # cv2.CHAIN_APPROX_TC89_L1,
    # cv2.CHAIN_APPROX_TC89_KCOS
   )
img = cv2.drawContours(src, cnts, whichToDraw(-1), color, line)


img = cv2.imwrite(filename, dst,  # 文件路径,和目标图像文件矩阵
    
    # 对于JPEG,其表示的是图像的质量,用0-100的整数表示,默认为95
    # 注意,cv2.IMWRITE_JPEG_QUALITY类型为Long,必须转换成int
    [int(cv2.IMWRITE_JPEG_QUALITY), 5] 
    [int(cv2.IMWRITE_JPEG_QUALITY), 95]
    # 从0到9,压缩级别越高,图像尺寸越小。默认级别为3
    [int(cv2.IMWRITE_PNG_COMPRESSION), 5])
    [int(cv2.IMWRITE_PNG_COMPRESSION), 9])

# 如果你不知道用哪个flags,毕竟太多了哪能全记住,直接找找。
寻找某个函数或者变量
events = [i for i in dir(cv2) if 'PNG' in i]
print( events )

寻找某个变量开头的flags
flags = [i for i in dir(cv2) if i.startswith('COLOR_')]
print flags

批量读取文件名字
import os
filename_rgb = r'C:\Users\aixin\Desktop\all_my_learning\colony\20170629'
for filename in os.listdir(filename_rgb):              #listdir的参数是文件夹的路径
    print (filename)

转载和疑问声明

如果你有什么疑问或者想要转载,没有允许是不能转载的哈
赞赏一下能不能转?哈哈,联系我啊,我告诉你呢 ~~
欢迎联系我哈,我会给大家慢慢解答啦~~~怎么联系我? 笨啊~ ~~ 你留言也行

你关注微信公众号1.机器学习算法工程师:2.或者扫那个二维码,后台发送 “我要找朕”,联系我也行啦!

(爱心.gif) 么么哒 ~么么哒 ~么么哒
码字不易啊啊啊,如果你觉得本文有帮助,三毛也是爱!

我祝各位帅哥,和美女,你们永远十八岁,嗨嘿嘿~~~

2017-06-16 11:39:29 llh_1178 阅读数 21990

我们在处理图像的时候,常常需要将图像的前景和背景做不同的处理,这时需要将前景和背景分割开。关于图像分割的方法我知道的有三种方法:K-means、分水岭和GrabCut算法进行物体分割。不能够肯定的比较出谁优谁劣,各种算法是分各种场合以及设定参数的优化。在此,只是简单介绍,学习之路任重而道远!

K-means方法进行分割:

它是一种最常用的聚类算法。因为,人们不需要手动的为数据集里的每个个体添加标签,能自动的发现集群结构,进行分类。是一种无监督的学习。那么是什么定义了集群的呢?答案是通过中心和形状定义的。然后,通过打分判断依据是:在这个集群中的分数高于在其他聚类中的分数和与本集群中心点比其他集群中心点更相似。具体的步骤是:首先,把观测分配给最近的中心点。然后,把集群中心点修改为被分配给原中心点观测的均值,反复这两步操作,直到全部收敛。

关于OpenCV下的kmean算法,函数为cv2.kmeans()
函数的格式为:kmeans(data, K, bestLabels, criteria, attempts, flags)

其中,K(分类数)和 attempts(Kmeans算法重复次数)是需要根据具体的图像进行优化的参数。像bestLabels预设分类标签可以不需要用None表示,criteria为迭代停止的模式选择,格式为(type,max_iter,epsilon),其中type又有两种选择:cv2.TERM_CRITERIA_EPS :精确度(误差)满足epsilon停止和cv2.TERM_CRITERIA_MAX_ITER:迭代次数超过max_iter停止,也可以两者结合,满意任意一个就结束。而flags(初始类中心选择),有两种方法:cv2.KMEANS_PP_CENTERS ; cv2.KMEANS_RANDOM_CENTERS

下面,就尝试一下修改K(分类数)和 attempts(Kmeans算法重复次数)参数进行测试。

先将K设为默认值,调attempts次数。

# 以灰色导入图像
img = cv2.imread('messi5.jpg',0)#image read be 'gray'
plt.subplot(221),plt.imshow(img,'gray'),plt.title('original')
plt.xticks([]),plt.yticks([])

# 改变图像的维度
img1 = img.reshape((img.shape[0]*img.shape[1],1))
img1 = np.float32(img1)

# 设定一个criteria,
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER,10,1.0)

# 设定一个初始类中心flags
flags = cv2.KMEANS_RANDOM_CENTERS
# 应用K-means
compactness,labels,centers = cv2.kmeans(img1,2,None,criteria,5,flags)
compactness_1,labels_1,centers_1 = cv2.kmeans(img1,2,None,criteria,10,flags)
compactness_2,labels_2,centers_2 = cv2.kmeans(img1,2,None,criteria,15,flags)
img2 = labels.reshape((img.shape[0],img.shape[1]))
img3 = labels_1.reshape((img.shape[0],img.shape[1]))
img4 = labels_2.reshape((img.shape[0],img.shape[1]))
plt.subplot(222),plt.imshow(img2,'gray'),plt.title('kmeans_attempts_5')
plt.xticks([]),plt.yticks([])
plt.subplot(223),plt.imshow(img3,'gray'),plt.title('kmeans_attempts_10')
plt.xticks([]),plt.yticks([])
plt.subplot(224),plt.imshow(img4,'gray'),plt.title('kmeans_attempts_15')
plt.xticks([]),plt.yticks([])
plt.savefig("kmeans_attempts.png")
plt.show()



可以看出attempts次数不同,是会造成图像分割差异的。

再来调K值,这里将attempts次数设为10.得到的图像为:


也可以看出K初始值不同,同样造成图像分割差异。所以,可以说这两个参数的优化是很重要的,但是,也不容易优化。看下一种方法:

分水岭算法

之所以叫分水岭算法,是因为它里面有“水”的概念。把图像中低密度的区域(变化很少)想象成山谷,图像中高密度的区域(变化很多)想象成山峰。开始向山谷中注入水直到不同的山谷中的水开始汇集。为了阻止不同山谷的水汇聚,可以设置一些栅栏,最后得到的栅栏就是图像分割。

img = cv2.imread("water_coins.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 将颜色转变为灰色之后,可为图像设一个阈值,将图像二值化。
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# 下面用morphologyEx变换来除去噪声数据,这是一种对图像进行膨胀之后再进行腐蚀的操作,它可以提取图像特征:
kernel = np.ones((3,3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations= 2)
# 通过对morphologyEx变换之后的图像进行膨胀操作,可以得到大部分都是背景的区域:
sure_bg = cv2.dilate(opening, kernel, iterations=3)
# 接着通过distanceTransform来获取确定前景区域,原理是应用一个阈值来决定哪些区域是前景,越是远离背景区域的边界的点越可能属于前景。
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.7 * dist_transform.max(), 255, 0)
# 考虑前景和背景中有重合的部分,通过sure_fg和sure_bg的集合相减得到。
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg, sure_fg)
# 现在有了这些区域,就可以设定“栅栏”来阻止水汇聚了,这通过connectedComponents函数来完成
ret, markers = cv2.connectedComponents(sure_fg)
# 在背景区域上加1, 这会将unknown区域设置为0:
markers = markers + 1
markers[unknown==255] = 0
# 最后打开门,让水漫起来并把栅栏绘成红色
markers = cv2.watershed(img, markers)
img[markers == -1] = [255, 0, 0]
plt.imshow(img), plt.xticks([]),plt.yticks([])
plt.show()



能够看出还是能大多数完整分割。

接下来介绍GrabCut算法进行对图像的分割处理。

使用GrabCut算法的实现步骤为:
1)在图片中定义含有(一个或多个)物体的矩形
2)矩形外的区域被自动认为是背景
3)对于用户定义的矩形区域,可用背景中的数据来区别它里面的前景和背景区域
4)用高斯混合模型(GMM)来对背景和前景建模,并将末定义的像素标记为可能的前景或背景
5)图像中的每一个像素都被看作通过虚拟边与周围像素相连接,而每一条边都有一个属于前景或背景的概率,这基于它与周围像素颜色上的相似性
6)每一个像素会与一个前景或背景节点连接。若节点之间不属于同一个终端(就是两个相邻的节点,一个节点属于前景,一个节点属于背景),则会切断它们之间的边,这就将图像各个部分分割出来了。

import numpy as np
import cv2
from matplotlib import pyplot as plt

# 首先加载图片,然后创建一个与所加载图片同形状的掩模,并用0填充。
img = cv2.imread("messi5.jpg")
mask = np.zeros(img.shape[:2], np.uint8)

# 然后创建以0填充的前景和背景模型:
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)
# 在实现GrabCut算法前,先用一个标识出想要隔离的对象的矩形来初始化它,这个矩形我们用下面的一行代码定义(x,y,w,h):
rect = (100, 50, 421, 378)
# 接下来用指定的空模型和掩摸来运行GrabCut算法
#mask, bgdModel, fgdModel = cv2.grabCut(img,mask,None,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_MASK)
cv2.grabCut(img, mask, rect, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT) # 5是指算法的迭代次数。
# 然后,我们再设定一个掩模,用来过滤之前掩模中的值(0-3)。值为0和2的将转为0,值为1和3的将转化为1,这样就可以过滤出所有的0值像素(背景)。
mask2 = np.where((mask==2)|(mask==0), 0, 1).astype("uint8")
img = img * mask2[:, :, np.newaxis]
# 最后可视化展现分割前后的图像
plt.subplot(1, 2, 1)
plt.imshow(img)
plt.title("grabcut"), plt.xticks([]), plt.yticks([])

plt.subplot(1, 2, 2)
plt.imshow(cv2.imread("messi5.jpg"))
plt.title("original"), plt.xticks([]), plt.yticks([])
plt.savefig("grabcut.png")



可以看出来分割的并不完整,而且头发和手都没有被区分到前景中来,这是因为,在设定矩形的时候需要不断优化的,且因每一张图像都有差异,所以矩形的范围也是有差异的。还好在github上找到了一个grabcut算法脚本,能完美的解决这个问题。并用他的代码进行测试。如下图:





参考:

《OpenCV3计算机视觉Python语言实现》

OpenCV帮助文档:

http://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_grabcut/py_grabcut.html

https://github.com/opencv/opencv/blob/master/samples/python/grabcut.py


2017-06-12 22:29:15 coming_is_winter 阅读数 10002

图像处理之图像分割(一)之活动轮廓模型:Snake算法简单梳理


  Snake算法,应该也可以翻译成蛇形算法,或者是包含曲折前进的意思。具体函数背景原理介绍参考:zouxy09,http://blog.csdn.net/zouxy09/article/details/8712287,图像分割之(五)活动轮廓模型之Snake模型简介,这里还是就几个自己思考的重点拓展一下。

  

 1. 这段中讲v(s)的表示,s∈[0,1],和s是以傅里叶变换形式描述边界的自变量。自己理解不是很明白,查了点资料,整合一下,说法有可能不严谨。首先:




   2.Snake模型到底是怎么变化的?



  弹性力量和弯曲力量合成内部力量,,保持固有的自身形状,外部力量改变其固有的自身形状,在合适的范围内受某种力而变化,这里举个例子:
 


  上面图中这种特大气泡(或想一下气球)的形状是有什么决定的呢?其中的弹性力量是曲线的斜率使其保持大概的圆形(可以理解为材料的张力,失重条件下水会变成水球),虽然现在上图的圆形变形严重,而弯曲力量是曲线斜率的斜率,这里理解为收缩的力量,可以想象一下气球紧绷的力量(这是气球本身材质决定,与外力无关),如果有漏气现象,紧绷的力量会使气泡或气球逐渐收缩,直至最小。外部力量这里的是空气的压力,这在上图中可以看到气泡的不规则凹凸,可以说是大气的压力的力量,形象的说是空气力量梯度的变化,在Snake中表现为像素灰度值梯度的变化。联系再丰富一点,Snake迭代的过程可以想一下真空包装:



  一开始先验知识人为划定Snake框架,相当于还未抽气时的包装袋,抽气开始在内力(包装袋的形状与材质决定)与外力(大气压力)的作用下,不断迭代,最终的轮廓收缩到鸡腿形状,能量最小达到平衡。

  3.泛函与变分
  函数的函数为泛函,我们求解的能量函数公式最终表现为函数的函数,怎样求公式最小值,一般求导找零点。函数的函数求导为变分。

  参考文献:
  [数字图像处理(第二版)].(美)冈萨雷斯.扫描版。

2019-01-07 10:21:04 u010608296 阅读数 10588

数字图像处理中常用图像分割算法有哪些?

1.多数的图像分割算法

2.图像边缘分割

3.图像阈值分割

4.基于区域的分割

5.形态学分水岭算法

多数的图像分割算法 均是基于灰度值的不连续相似的性质。在前者中,算法以灰度突变为基础分割一幅图像,如图像边缘分割。假设图像不同区域的边界彼此完全不同,且与背景不同,从而允许基于灰度的局部不连续性来进行边界检测。后者是根据一组预定义的准则将一幅图像分割为相似区域,如阈值处理、区域生长、区域分裂区域聚合都是基于这种方法形成的。下面将对每类算法进行详细说明。

 

图像边缘分割:边缘是图像中灰度突变像素的集合,一般用微分进行检测。基本的边缘检测算法有:Roberts算子、Prewitt算子、Sobel算子。稍高级的算法有:Marr-Hilderth边缘检测器、Canny边缘检测器。

图像阈值分割:由于阈值处理直观、实现简单且计算速度快,因此阈值处理在分割应用中处于核心地位。阈值处理可以分为单阈值处理与多阈值处理。在单阈值处理中最常用且分割效果也不错的算法是Otsu(最大类间方差算法)算法。多阈值处理:K类由K-1个阈值来分离,即计算图像的多个类间方差,多阈值处理的分割结果相较于单阈值的结果虽然会更好一些,但分类数量增加时它会开始失去意义,因为我们仅仅处理一个变量(灰度),此时可以通过增加变量如彩色来进行解决。

基于区域的分割:区域生长算法和区域分裂与聚合都是属于基于区域的分割算法。

区域生长算法是根据预先定义的生长准则将像素或子区域组合为更大的区域的过程。

基本方法是从一组“种子”点开始,将与种子预先定义的性质相似的那些邻域像素添加到每个种子上来形成这些生长区域(如特定范围的灰度或颜色)。区域分裂与聚合是首先将一幅图像细分为一组任意的不相交区域,然后按照一定规则聚合、分裂这些区域。

形态学分水岭算法:分水岭的概念是以三维形象化一幅图像为基础的。

在图中,我们主要考虑三种类型的点:

(1)属于一个区域最小值的点;

(2)把一点看成是一个水滴,如果把这些点放在任意位置上,水滴一定会下落到一个单一的最小值点;

(3)处在该点的水会等可能性地流向不止一个这样的最小值点。

对于一个特定的区域最小值,满足条件(2)的点的集合称为该最小值的汇水盆地分水岭

满足条件(3)的点形成地表面的峰线,称之为分割线分水线

为了达到更好的分割效果,常常将分水岭算法应用到梯度图像上,而不是图像本身。

 

(二)

个人认为图像分割的算法可以从分割目标入手:通常是要将图像分成目标区域和背景。

需要从图像的特征入手,以灰度图像为例(其余类型的图像处理均类似),图像图形很明显的特征有:

图像灰度值特征、目标边界特征、纹理特征、形态学特征等等;

还有一些基于这些特征所计算提取出的特征,比如信息熵、能量泛函等等。

最为简单的就是灰度值特征了,一幅图中有时候目标区域与背景区域有很明显的亮度区别,基于这个认识,只要试图找到某个亮度的值,我们假设低于该值的认为是背景,高于该值的认为是目标。关于找这个值的算法就是阈值分割算法了,像OTSU、迭代法、最大熵法等等都是属于这一范畴。

同时也可以注意到,在空域内,目标的边界是区分目标与背景的重要依据,因此区分边界也是一个重要的手段,通常边界点周围灰度值变化率很高,因此可以基于图像灰度梯度来识别。这就有一些sobel算子、canny算子等等方式,都是通过找到边界来确定目标区域背景的。

在有些图像中,目标区域具有一定的连续性,基于区域连续性的一些方法像区域生长法、分水岭算法等(本人对这一块不是很熟悉)。

另外,基于图像原始的特征进行提取获得“精炼”的二级特征,并据此分割也是一种好的方法。像SNAKE算法,该算法认为目标区域的边界是“外力”,内力共同作用的结果,因此当外力内力平衡时找到边界,基于这种平衡,提出了判断能量泛函最小的判断原则。此外,还有基于几何活动轮廓模型的水平集方法,该方法是借助于目标区域的几何度量参数,可以比较好的处理一些拓扑变化。

除去经典的阈值分水岭分割算法外,有主动轮廓及衍生的水平集,图割及相关算法(例如GrabCut),交互式分割,以及Cosegmentation。

所有分割算法的核心目的是解决目标区域语义合并难题。例如交互式分割,通过精准交互来解决该问题。Cosegmentation通过分割相同或相似目标来处理该问题。

(三)

(1)基于阈值的分割方法:可在各种颜色空间或不同通道中完成阈值、自适应阈值、

(2)基于边缘的分割方法:各种边缘检测算子

(3)基于区域的分割方法:分水岭、区域归并与分裂

(4)图割分割:最大流(最小割)算法

(5)基于深度信息的分割

(6)基于先验信息的分割

 

基于特定理论的分割方法等。

特定理论大概有:聚类分析、模糊集理论、基因编码、小波变换等。

2016-11-15 17:21:22 yangleo1987 阅读数 36329

图像分割的主要算法:

1.基于阈值的分割方法

2.基于边缘的分割方法

3.基于区域的分割方法

4.基于聚类分析的图像分割方法

5.基于小波变换的分割方法

6.基于数学形态学的分割方法

7.基于人工神经网络的分割方法


基于阈值的分割方法

阈值分割方法作为一种常见的区域并行技术,就是用一个或几个阈值将图像的灰度直方图分成几个类,认为图像中灰度值在同一类中的像素属于同一物体。由于是直接利用图像的灰度特性,因此计算方便简明、实用性强。显然,阈值分割方法的关键和难点是如何取得一个合适的阈值。而实际应用中,阈值设定易受噪声和光亮度影响。近年来的方法有:用最大相关性原则选择阈值的方法、基于图像拓扑稳定状态的方法、Yager测度极小化方法、灰度共生矩阵方法、方差法、熵法、峰值和谷值分析法等,其中,自适应阈值法、最大熵法、模糊阈值法、类间阈值法是对传统阈值法改进较成功的几种算法。更多的情况下,阈值的选择会综合运用2种或2种以上的方法,这也是图像分割发展的一个趋势。

特点

阈值分割的优点是计算简单、运算效率较高、速度快。全局阈值对于灰度相差很大的不同目标和背景能进行有效的分割。当图像的灰度差异不明显或不同目标的灰度值范围有重叠时,应采用局部阈值或动态阈值分割法。另一方面,这种方法只考虑像素本身的灰度值,一般不考虑空间特征,因而对噪声很敏感。在实际应用中,阈值法通常与其他方法结合使用。


基于边缘的分割方法

基于边缘检测的分割方法试图通过检测包含不同区域的边缘来解决分割问题,是最常用的方法之一。通常不同的区域之间的边缘上像素灰度值的变化往往比较剧烈,这是边缘检测得以实现的主要假设之一。常用灰度的一阶或者二阶微分算子进行边缘检测。常用的微分算子有一次微分(sobel算子,Robert算子等),二次微分(拉普拉斯算子等)和模板操作(Prewit算子,Kirsch算子等)

特点

基于边缘的分割方法其难点在于边缘检测时抗噪性和检测精度之间的矛盾。若提高检测精度,则噪声产生的伪边缘会导致不合理的轮廓;若提高抗噪性,则会产生轮廓漏检和位置偏差。为此,人们提出各种多尺度边缘检测方法,根据实际问题设计多尺度边缘信息的结合方案,以较好的兼顾抗噪性和检测精度。


基于区域的分割方法

区域分割的实质就是把具有某种相似性质的像索连通,从而构成最终的分割区域。它利用了图像的局部空间信息,可有效地克服其他方法存在的图像分割空间小连续的缺点。在此类方法中,如果从全图出发,按区域属性特征一致的准则决定每个像元的区域归属,形成区域图,常称之为区域生长的分割方法。如果从像元出发,按区域属性特征一致的准则,将属性接近的连通像元聚集为区域,则是区域增长的分割方法。若综合利用上述两种方法,就是分裂合并的方法。它是先将图像分割成很多的一致性较强的小区域,再按一定的规则将小区域融合成大区域,达到分割图像的目的。

特点

基于区域的分割方法往往会造成图像的过度分割,而单纯的基于边缘检测方法有时不能提供较好的区域结构,为此可将基于区域的方法和边缘检测的方法结合起来,发挥各自的优势以获得更好的分割效果。


基于聚类分析的图像分割方法

特征空间聚类法进行图像分割是将图像空间中的像素用对应的特征空间点表示,根据它们在特征空间的聚集对特征空间进行分割,然后将它们映射回原图像空间,得到分割结果。其中,K均值、模糊C均值聚类(FCM)算法是最常用的聚类算法。K均值算法先选K个初始类均值,然后将每个像素归入均值离它最近的类并计算新的类均值。迭代执行前面的步骤直到新旧类均值之差小于某一阈值。模糊C均值算法是在模糊数学基础上对K均值算法的推广,是通过最优化一个模糊目标函数实现聚类,它不像K均值聚类那样认为每个点只能属于某一类,而是赋予每个点一个对各类的隶属度,用隶属度更好地描述边缘像素亦此亦彼的特点,适合处理事物内在的不确定性。利用模糊C均值(FCM)非监督模糊聚类标定的特点进行图像分割,可以减少人为的干预,且较适合图像中存在不确定性和模糊性的特点。

聚类方法应注意几个问题:

(1)聚类的类数如何确定。

(2)怎样确定聚类的有效性准则。

(3)聚类中心的位置和特性事先不清楚时,如何设置初始值。

(4)运算的开销。

并且FCM算法对初始参数极为敏感,有时需要人工干预参数的初始化以接近全局最优解,提高分割速度。另外,传统FCM算法没有考虑空间信息,对噪声和灰度不均匀敏感。


基于小波变换的分割方法

基于小波变换的阈值图像分割方法的基本思想是,首先由二进小波变换将图像的直方图分解为不同层次的小波系数,然后依据给定的分割准则和小波系数选择阈值门限,最后利用阈值标出图像分割的区域。整个分割过程是从粗到细,有尺度变化来控制,即起始分割由粗略的L2(R)子空间上投影的直方图来实现,如果分割不理想,则利用直方图在精细的子空间上的小波系数逐步细化图像分割。分割算法的计算馈与图像尺寸大小呈线性变化。小波变换为信号在不同尺度上的分析和表征提供了一个精确和统一的框架。从图像分割的角度来看,小波分解提供了一个数学上完备的描述;小波变换通过选取合适的滤波器,可以极大地减少或去除所提取的不同特征之间的相关性,不仅具有“变焦”特性,而且在实现上有快速算法。

特点

小波变换是一种多尺度、多通道的分析工具它是空域和频域的局域变换,因而能有效地从信号中提取信息,通过伸缩和平移等运算功能对函数或信号进行多尺度分析,解决了傅立叶变换不能解决的许多问题。近年来多进制小波开始用于边缘检测。另外,利用正交小波基的小波变换也可提取多尺度边缘,并可通过对图像奇异度的计算和估计来区分一些边缘的类型。


基于数学形态学的分割方法

数学形态学是一种非线性滤波方法,可以用于抑制噪声、特性提取、边缘检测、图像分割等图像处理问题。数学形态学首先被用来处理二值图像,后来也被用来处理灰度图像,现在又有学者开始用软数学形态学和模糊形态学来解决计算机视觉方面的问题。数学形态学的特点是能将复杂的形状进行分解,并将有意义的形状分量从无用的信息中提取出来。它的基本思想是利用一个称为结构元素的探针来收集图像的信息,当探针在图像中不断的移动时,不仅可根据图像各个部分间的相互关系来了解图像的结构特征,而且利用数学形态学基本运算还可以构造出许多非常有效的图像处理与分析方法。其基本的形态运算是腐蚀与膨胀。腐蚀具有使目标缩小、目标内孔增大以及外部孤立噪声消除的效果;而膨胀是将图像中与目标物体接触的所有背景点合并到物体中的过程,结果是使目标增大、孔径缩小,可以增补目标中的空间,使其形成连通域。数学形态学中另一对基本运算方法是开运算和闭运算。开运算具有消除图像是细小物体,并在物体影响纤细处分离物体和平滑较大物体边界的作用;闭运算具有填充物体影像内细小空间, 接邻近物体和平滑边界的作用。

特点

数学形态学应用于图像分割,具有定位效果好、分割精度高、抗噪声性能好的特点。同时这种方法也有着自身的局限性:由于在图像处理的前期工作中,采用数学形态学的开()运算,进行图像处理后,依然存在大量与目标不符的短线和孤立点;由于预处理工作的不彻底,还需要进行一系列的基于点的开()运算,因此运算速度明显下降。如何将数学形态学与其它方法综合运用以克服这些缺陷,将是数学形态学以后的工作方向。连接邻近物体和平滑边界的作用。


基于人工神经网络的分割方法

近年来,人工神经网络识别技术已经引起了广泛的关注,并应用于图像分割。基于神经网络的分割方法的基本思想是通过训练多层感知机来得到线性决策函数,然后用决策函数对像素进行分类来达到分割的目的

特点

用人工神经网络的方法分割图像,需要大量的训练数据。神经网络存在巨量的连接,容易引入空间信息,能较好地解决图像中的噪声和不均匀问题。选择何种网络结构是这种方法要解决的主要问题。


基于遗传学算法的分割方法

遗传算法(GA),是一种模拟自然选择和遗传机制的搜索和优化过程,它具有很强的全局优化搜索能力,是一种具有广泛适用性的自适应搜索方法。它在搜索空间中是在种群中而不是在单点上进行寻优,它在求解过程中使用遗传操作规则而不是确定性规则来工作。这些特点使得遗传算法很适于应用在图像分割中,尤其是阈值分割法以及区域生长法中。利用GA的全局寻优能力及对初始位置的不敏感特性,可以改进图像分割的性能。

特点

遗传算法应用于图像分割,其难点在于适应度函数的选择以及交叉概率和变异概率的确定。GA还有可能收敛于局部最优。可考虑使用能够自适应设置交叉概率和变异概率自适应遗传算法以及和模拟退火法相结合的混合遗传算法。