Python 计算机视觉(十二)—— OpenCV 进行图像分割

参考的一些文章以及论文我都会给大家分享出来 —— 链接就贴在原文,论文我上传到资源中去,大家可以免费下载学习,如果当天资源区找不到论文,那就等等,可能正在审核,审核完后就可以下载了。大家一起学习,一起进步!加油!!  

目录

前言

(1)图像分割

(2)读取图像信息

1. 基于阈值的图像分割

(1)基本概念

(2)二值化操作

a. 函数

b. 代码实现

2. 基于边缘检测的图像分割

3. 基于 K-Means 聚类的区域分割

(1)基本概念

(2)代码实现

4. 基于分水岭算法的图像分割

(1)基本概念

(2)代码实现

5.整体代码

结束语


前言

(1)图像分割

        图像分割是 AI 领域中一个重要的分支,是机器视觉技术中的关于图像理解的重要一环。近几年兴起的自动驾驶技术中,也需要用到这种技术。车载摄像头探查到图像,后台计算机可以自动将图像分割归类,以避让行人和车辆等障碍。

(2)读取图像信息

无需多言,直接读取图像信息:

"""
Author:XiaoMa
date:2021/11/2
"""
import cv2
import numpy as np
import matplotlib.pyplot as plt

img0 = cv2.imread("E:\From Zhihu\For the desk\cvtwelve3.jpg")
img1 = cv2.resize(img0, dsize = None, fx = 0.5, fy = 0.5)
img2 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
h, w = img1.shape[:2]
print(h, w)
cv2.namedWindow("W0")
cv2.imshow("W0", img2)
cv2.waitKey(delay = 0)

得到图像信息如下:

540 960

1. 基于阈值的图像分割

(1)基本概念

       根据图像的整体或部分信息选择阈值,把图像依据灰度级别划分,前面说过的图像二值化就是一种基于阈值的图像分割,当像素点的灰度值高于阈值时将其设置为1,低于阈值时将其设置为0,通过这种方法达到将感兴趣的图像和背景进行分离的操作,所以说如何选取合适的阈值对于这种方法来说比较重要,如果背景和图像亮度区别较大我们可以使用全局阈值分割,但是背景和图像亮度区别不大时得使用局部阈值分割。

(2)二值化操作

a. 函数

ret,dst = cv2.threshold(src,thresh,maxval,type)

res:分割阈值

dst:分割后图像

scr:输入的原图

thresh:分割时的像素分界点值(和阈值等值)

maxval:给大于阈值的像素点安排的灰度值(如定为240,那么大于阈值的点都置为240)

type:阈值的类型,包括四种不同的阈值类型

OpenCV 提供的几种阈值类型:

cv2.THRESH_BINARY           #小于阈值的像素点置0,大于阈值的像素点置maxval; 
cv2.THRESH_BINARY_INV   #小于阈值的像素点置maxval,大于阈值的像素点置0;
cv2.THRESH_TRUNC            # 小于阈值的像素点保持原数值,大于阈值的像素点置阈值; 
cv2.THRESH_TOZERO          # 小于阈值的像素点置0,大于阈值的像素点保持原数值; 
cv2.THRESH_TOZERO_INV  #小于阈值的像素点保持原数值,大于阈值的像素点置0。  

b. 代码实现

本文中将阈值都设置为 127 ,对不同的阈值类型都进行尝试:

将下面的代码复制过去改一下图像的读取路径就可以直接运行了,重要的地方都添加了注释,应该可以看懂。

"""
Author:XiaoMa
date:2021/11/2
"""
import cv2
import numpy as np
import matplotlib.pyplot as plt

img0 = cv2.imread("E:\From Zhihu\For the desk\cvtwelve0.jpg")
img1 = cv2.resize(img0, dsize = None, fx = 0.5, fy = 0.5)
img2 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
h, w = img1.shape[:2]
print(h, w)
cv2.namedWindow("W0")
cv2.imshow("W0", img2)
cv2.waitKey(delay = 0)
#图像进行二值化
##第一种阈值类型
ret0, img3 = cv2.threshold(img2, 127, 255, cv2.THRESH_BINARY)
print(ret0)
##第二种阈值类型
ret1, img4 = cv2.threshold(img2, 127, 255, cv2.THRESH_BINARY_INV)
print(ret1)
##第三种阈值类型
ret2, img5 = cv2.threshold(img2, 127, 255, cv2.THRESH_TRUNC)
print(ret2)
##第四种阈值类型
ret3, img6 = cv2.threshold(img2, 127, 255, cv2.THRESH_TOZERO)
print(ret3)
##第五种阈值类型
ret4, img7 = cv2.threshold(img2, 127, 255, cv2.THRESH_TOZERO)
print(ret4)
#将所有阈值类型得到的图像绘制到同一张图中
plt.rcParams['font.family'] = 'SimHei'       #将全局中文字体改为黑体
figure = [img2, img3, img4, img5, img6, img7]
title = ["原图", "第一种阈值类型", "第二种阈值类型", "第三种阈值类型", "第四种阈值类型", "第五种阈值类型"]
for i in range(6):
    figure[i] = cv2.cvtColor(figure[i], cv2.COLOR_BGR2RGB)   #转化图像通道顺序,这一个步骤要记得
    plt.subplot(3, 2, i+1)
    plt.imshow(figure[i])
    plt.title(title[i])   #添加标题
plt.savefig("E:\From Zhihu\For the desk\cvtwelven.jpg")  #保存图像,如果不想保存也可删去这一行
plt.show()

  

2. 基于边缘检测的图像分割

这一部分在我前面的文章种已经介绍过了:Python 计算机视觉(十)—— OpenCV 图像锐化及边缘检测

这里我们就拿其中的一个算子简单试一下:

#边缘检测之Sobel 算子
img8 = cv2.Sobel (img2, cv2.CV_64F, 0, 1, ksize=5)
cv2.namedWindow("W1")
cv2.imshow("W1", img8)
cv2.waitKey(delay = 0)

得到的结果如下:

3. 基于 K-Means 聚类的区域分割

(1)基本概念

此处参考:《K-Means聚类算法研究综述_杨俊闯》

        K-Means算法是一种无监督学习,同时也是基于划分的聚类算法,一般用欧式距离(两点间的直线距离)作为衡量数据对象间相似度的指标,相似度与数据对象间的距离成反比,相似度越大,距离越小。算法需要预先指定初始聚类数目k (需要分割的份数)以及 k 个初始聚类中心,根据数据对象与聚类中心之间的相似度,不断更新聚类中心的位置,不断降低类簇的误差平方和(Sum of Squared Error,SSE),当SSE不再变化或目标函数收敛时,聚类结束,得到最终结果。

对于该算法的理解,也可以参考 OpenCV 官网给出的解释:K-means聚类

(2)代码实现

此处参考:OpenCV 官网

#K-means均值聚类
Z = img1.reshape((-1, 3))
Z = np.float32(Z)      #转化数据类型
c = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
k = 4   #聚类中心个数,一般来说也代表聚类后的图像中的颜色的种类
ret, label, center = cv2.kmeans(Z, k, None, c, 10, cv2.KMEANS_RANDOM_CENTERS)
center = np.uint8(center)
res = center[label.flatten()]
img9 = res.reshape((img1.shape))
cv2.namedWindow("W2")
cv2.imshow("W2", img9)
cv2.waitKey(delay = 0)

4. 基于分水岭算法的图像分割

(1)基本概念

此处参考:IMAGE SEGMENTATION AND MATHEMATICAL MORPHOLOGY

       任何灰度图像都可以视为地形表面,其中高强度表示山峰和丘陵,而低强度表示山谷。你开始用不同颜色的水(标签)填充每个孤立的山谷(局部最小值)。随着水位上升,以附近的山峰(梯度)作为基础,来自不同山谷的水,明显不同颜色的水会开始融合。为了避免这种情况,你可以在水汇合的位置建立障碍。你继续填水和建造屏障,直到所有的山峰都在水下。然后你创建的障碍为你提供了分割结果。这就是分水岭背后的“哲学”。

        但如果图像中噪声比较多,那么就会出现很多的“山谷”,这样就分割出太多的区域,所以我们在进行分水岭操作时,一般也会对图像进行一下平滑处理或者形态学操作,来使得图像上的噪声点减少,使得分割效果更加明显。图像平滑和形态学的部分我在前面的文章中提到过:Python 计算机视觉(九)—— OpenCV进行图像平滑

Python 计算机视觉(十一)—— OpenCV 图像形态学处理

(2)代码实现

此处参考:分水岭算法的图像分割(官网)

#分水岭算法
ret1, img10 = cv2.threshold(img2, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)#(图像阈值分割,将背景设为黑色)
cv2.namedWindow("W3")
cv2.imshow("W3", img10)
cv2.waitKey(delay = 0)
##noise removal(去除噪声,使用图像形态学的开操作,先腐蚀后膨胀)
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(img10, cv2.MORPH_OPEN, kernel, iterations = 2)
# sure background area(确定背景图像,使用膨胀操作)
sure_bg = cv2.dilate(opening, kernel, iterations=3)
# Finding sure foreground area(确定前景图像,也就是目标)
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret2, 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
ret3, markers = cv2.connectedComponents(sure_fg)  #用0标记所有背景像素点
# Add one to all labels so that sure background is not 0, but 1(将背景设为1)
markers = markers+1
##Now, mark the region of unknown with zero(将未知区域设为0)
markers[unknown == 255] = 0
markers = cv2.watershed(img1, markers)   #进行分水岭操作
img1[markers == -1] = [0, 0, 255]    #边界区域设为-1,颜色设置为红色
cv2.namedWindow("W4")
cv2.imshow("W4", img1)
cv2.waitKey(delay = 0)

      效果并不是很理想,建议大家找一些亮度相差较大而且梯度明显但种类不多的图像进行试验操作。

5.整体代码

我将本篇文章中的代码贴在下面,大家可以直接复制进行试验,改一下图像的读取路径就可以使用了:

"""
Author:XiaoMa
date:2021/11/2
"""
import cv2
import numpy as np
import matplotlib.pyplot as plt

img0 = cv2.imread("E:\From Zhihu\For the desk\cvtwelve0.jpg")
img1 = cv2.resize(img0, dsize = None, fx = 0.5, fy = 0.5)
img2 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
h, w = img1.shape[:2]
print(h, w)
cv2.namedWindow("W0")
cv2.imshow("W0", img1)
cv2.waitKey(delay = 0)
#图像进行二值化
##第一种阈值类型
ret0, img3 = cv2.threshold(img2, 127, 255, cv2.THRESH_BINARY)
print(ret0)
##第二种阈值类型
ret1, img4 = cv2.threshold(img2, 127, 255, cv2.THRESH_BINARY_INV)
print(ret1)
##第三种阈值类型
ret2, img5 = cv2.threshold(img2, 127, 255, cv2.THRESH_TRUNC)
print(ret2)
##第四种阈值类型
ret3, img6 = cv2.threshold(img2, 127, 255, cv2.THRESH_TOZERO)
print(ret3)
##第五种阈值类型
ret4, img7 = cv2.threshold(img2, 127, 255, cv2.THRESH_TOZERO)
print(ret4)
#将所有阈值类型得到的图像绘制到同一张图中
plt.rcParams['font.family'] = 'SimHei'       #将全局中文字体改为黑体
figure = [img2, img3, img4, img5, img6, img7]
title = ["原图", "第一种阈值类型", "第二种阈值类型", "第三种阈值类型", "第四种阈值类型", "第五种阈值类型"]
for i in range(6):
    figure[i] = cv2.cvtColor(figure[i], cv2.COLOR_BGR2RGB)   #转化图像通道顺序,这一个步骤要记得
    plt.subplot(3, 2, i+1)
    plt.imshow(figure[i])
    plt.title(title[i])   #添加标题
plt.savefig("E:\From Zhihu\For the desk\cvtwelven.jpg")  #保存图像,如果不想保存也可删去这一行
plt.show()
#边缘检测之Sobel 算子
img8 = cv2.Sobel(img2, cv2.CV_64F, 0, 1, ksize = 5)
cv2.namedWindow("W1")
cv2.imshow("W1", img8)
cv2.waitKey(delay = 0)
#K-means均值聚类
Z = img1.reshape((-1, 3))
Z = np.float32(Z)      #转化数据类型
c = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
k = 4
ret, label, center = cv2.kmeans(Z, k, None, c, 10, cv2.KMEANS_RANDOM_CENTERS)
center = np.uint8(center)
res = center[label.flatten()]
img9 = res.reshape((img1.shape))
cv2.namedWindow("W2")
cv2.imshow("W2", img9)
cv2.waitKey(delay = 0)

#分水岭算法
ret1, img10 = cv2.threshold(img2, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)#(图像阈值分割,将背景设为黑色)
cv2.namedWindow("W3")
cv2.imshow("W3", img10)
cv2.waitKey(delay = 0)
##noise removal(去除噪声,使用图像形态学的开操作,先腐蚀后膨胀)
kernel = np.ones((3, 3), np.uint8)
opening = cv2.morphologyEx(img10, cv2.MORPH_OPEN, kernel, iterations = 2)
# sure background area(确定背景图像,使用膨胀操作)
sure_bg = cv2.dilate(opening, kernel, iterations=3)
# Finding sure foreground area(确定前景图像,也就是目标)
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret2, 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
ret3, markers = cv2.connectedComponents(sure_fg)  #用0标记所有背景像素点
# Add one to all labels so that sure background is not 0, but 1(将背景设为1)
markers = markers+1
##Now, mark the region of unknown with zero(将未知区域设为0)
markers[unknown == 255] = 0
markers = cv2.watershed(img1, markers)   #进行分水岭操作
img1[markers == -1] = [0, 0, 255]    #边界区域设为-1,颜色设置为红色
cv2.namedWindow("W4")
cv2.imshow("W4", img1)
cv2.waitKey(delay = 0)

结束语

       本文介绍了使用 OpenCV 进行图像分割的几种常用手段,包括阈值分割、边缘分割、K均值聚类分割以及分水岭分割。当然还有一些其他的比如均值漂移、基于纹理分割、文本分割、水漫分割等手段并没有在本文中提到,小伙伴们感兴趣可以去进行了解学习。

物联沃分享整理
物联沃-IOTWORD物联网 » Python 计算机视觉(十二)—— OpenCV 进行图像分割

发表评论