当前位置:   article > 正文

Python 人工智能实战:图像分割_blurred = cv.medianblur(gray, 5) thresh = cv.adapt

blurred = cv.medianblur(gray, 5) thresh = cv.adaptivethreshold(blurred, 255,

作者:禅与计算机程序设计艺术

1.背景介绍

概述

图像处理(Image Processing)是指利用计算机对图像进行分析、识别和处理的一门技术领域。对于人类来说,拍照、剪贴、看手机相册都是很平常的事情,但对图像进行分析、处理、或者说“分割”则属于高级技术范畴。现如今,随着计算机视觉、机器学习等技术的成熟和普及,许多软件工程师、科研工作者、数据分析师、产品经理以及AI/ML从业者都越来越多地依赖图像处理工具进行项目开发、解决实际问题。因此,掌握图像处理技术有助于让你的技能更上一层楼,从而实现更多的商业价值。

在此处,我们将以深度学习(Deep Learning)方法中的卷积神经网络(Convolutional Neural Network,CNN)来讲解图像分割技术的基本原理与相关知识。CNN 是一种简单且有效的图像分类器。它能够自动提取图像的特征信息,并通过卷积层、池化层以及全连接层对这些特征进行组合,最后输出分类结果。因此,掌握 CNN 的图像分割技术至关重要,因为这是许多计算机视觉任务的关键组件。

应用场景

图像分割(Segmentation)是指将一张完整图片划分为多个独立的区域,每个区域代表一种不同的语义含义或对象。典型的应用场景包括:

  1. 游戏领域——通过对游戏角色的动作截图进行分析,可以识别出角色的攻击、移动路径和敌人的位置;
  2. 医疗领域——通过对肿瘤切片的像素级分类,可以分割出不同类型的组织,并进一步进行组织细胞和免疫细胞的分割;
  3. 图像修复、超分辨率——由于光学失真导致的图像模糊,无法清晰看到图像中的物体边界,图像分割技术可用于恢复图像的原始结构和边缘信息,提升图像的分辩性;
  4. 图像编辑——图像编辑软件通常需要用户标注出物体的各个轮廓,这时就需要用到图像分割技术。

除此之外,图像分割还具有其他的应用场景,例如:

  1. 城市规划——根据街道、建筑、景观等元素的颜色、纹理等特征进行区分和提取,可以帮助规划师快速识别环境中不同类型和层次的建筑物;
  2. 视频监控——自动物体检测可以帮助摄像头跟踪和识别特定目标,从而减少人力投入,提升工作效率;
  3. 地籁导航——不同地形特征的提取有利于判断岔路口的走向,也有助于导航软件准确定位目标。

数据集

常用的图像分割数据集有 Pascal VOC、Cityscapes、ADE20K、COCO、CamVid等。本文使用的两个数据集分别为 PASCAL VOC 2012 和 Cityscapes。

PASCAL VOC 2012 数据集简介

PASCAL VOC数据集(Pascal Visual Object Classes)是最流行的图像分割数据集。该数据集由加拿大牛津大学的研究人员自然标记制作,共包含了17125幅图像,其中20个类别(包括背景)、500个训练图像和15223张测试图像。训练集中包含115554张图像,验证集中包含5011张图像。测试集(VOC2012 Test)共有2913张图像。每张图像分辨率为400×300px,共有20个类别:人、猫、狗、汽车、植被、建筑、树、道路、农田、飞机、船只、鸟、水crafts等。

Cityscapes 数据集简介

Cityscapes数据集是一个新颖的数据集,其由来自不同城市街道、交叉口的街景图片组成。该数据集的优点在于:

  1. 大量的高质量图像数据:19,797张训练图像、5,000张验证图像、1,525张测试图像;
  2. 丰富的类别标签:7类的城市生活设施(buildings、skyscrapers、trees、gardens、road、vehicles、pedestrians)、19类的道路交通设施(wall、fence、guard rail、bridge、tunnel、pole、traffic light、traffic sign、vegetation、terrain、sky)。

2.核心概念与联系

1.什么是图像分割?

图像分割,是指将一张完整的图片分割成若干个子图像,每个子图像表示一种语义意思或特点,并对其中的每个子图像进行分类、检测、跟踪、理解。图像分割与图像分类有些类似,但是又不完全相同。

图像分类一般采用的是模式匹配的方法,即根据待分类对象的外观特征判断其所属类别。比如一张黑白图像中是否存在某个目标,就可以用一种分类器(如SVM)来判断其所属类别。然而,图像分割则是一种基于学习的计算机视觉技术,可以将图片中目标物体区域分割出来,然后再把它们按照目标的类别区分开来。

假定要分割的图像如下图所示:

那么,图像分割过程就是先确定一下哪些区域是目标物体的区域,然后再对这几个目标物体区域进行分类,比如汽车、鸟类、道路等等。

2.图像分割的常见技术

目前主流的图像分割技术主要有以下几种:

  • 基于形状的图像分割:首先通过图像的预处理(如灰度化、直方图均衡化、噪声去除),消除图像中的噪声。然后采用像素级别的运算方法,结合目标物体的形状,将图像分割成若干个子图像。常用的方法有轮廓分割、分水岭变换、形态学处理、基于树的分割算法等。
  • 基于空间位置的图像分割:首先通过前景背景模型(Foreground-Background Model)估计图像中的前景和背景,得到一个概率分布函数。然后采用聚类方法,对图片中的像素进行聚类,得到不同类别对应的像素集合。常用的方法有K-means、DBSCAN、Gaussian Mixture Model、层次聚类等。
  • 深度学习技术:深度学习技术最早起源于卷积神经网络(Convolutional Neural Network),后被证明能够有效地解决图像分割问题。深度学习方法能够直接学习到图像中的高阶特征,且无需手工设计特征。常用的方法有FCN、SegNet、U-net、DeepLabv3+等。

3.图像分割的步骤与流程

图像分割的基本步骤和流程如下图所示:

图像分割常见的算法有:

  1. 预处理:首先对图像进行预处理,比如归一化、裁剪、缩放等。
  2. 分割算法:选择适合于当前图像的分割算法。有轮廓分割、分水岭变换、形态学处理、基于树的分割算法等。
  3. 重构:基于分割结果,重新构造原始图像,用于模型的评估。
  4. 模型优化:优化模型参数,使得分割结果尽可能地接近原始图像的真实标签。

3.核心算法原理与操作步骤

1.轮廓分割法

轮廓分割法(Contour Slicing)是指通过图像的像素值变化与形状关系,将图像划分为许多小的连续区域,每个区域表示一个目标对象。它的基本思想是:首先找出图像中所有目标对象的边界线,然后根据边界线形成轮廓(Contours)。对于每个轮廓,可以定义出一系列的形状特征,如矩形、圆形、椭圆等等。之后,可以用这些特征来定义出目标对象的边界范围,从而将图片划分成许多大小相似的区域,每个区域表示一个目标对象。

轮廓分割法的基本步骤如下图所示:

轮廓分割法主要优点是简单、易于实现。缺点主要有两点:第一,要求目标对象必须是连通的;第二,无法考虑目标对象内部的相互作用。

2.分水岭变换法

分水岭变换法(Watershed Transform)是指根据图像的梯度方向,计算出图像上的所有地物边界线。它的基本思想是:将图像看做是高度场,将其分割成连通的体积,然后计算各体积的地物的流量,也就是地物从哪里流向哪里。由于流量的方向与梯度的方向一致,所以可以找到各体积之间的路线,从而找到图像上所有的边界线。

分水岭变换法的基本步骤如下图所示:

分水岭变换法适用于复杂的非凸区域分割,而且不要求目标对象必须是连通的。缺点主要是计算量大。

3.形态学处理法

形态学处理法(Morphological Processing)是指对图像的灰度值进行处理,形成目标对象的形状。它的基本思想是:首先对图像进行二值化处理,之后用一些形态学操作,比如腐蚀、膨胀、开、闭等,将图像转化成目标对象的形状。

形态学处理法的基本步骤如下图所示:

形态学处理法可以方便地实现对各种形状的目标对象的分割,但是缺点是只能处理简单形状的目标对象。

4.基于树的分割算法

基于树的分割算法(Tree-Based Segmentation Method)是指将图像分割成一棵树的形式。它的基本思想是:首先根据图像的色彩模式,生成一系列的颜色阈值,然后按照颜色相似度,将这些阈值组织成一棵树。树的根节点表示整个图像,中间的叶子结点表示颜色相近的区域,而每个内部结点都对应于一块颜色区域。

基于树的分割算法的基本步骤如下图所示:

基于树的分割算法可以通过颜色相似性来将图像中的不同颜色区域分割成不同的区域,但是缺点主要是不容易处理目标对象内部的相互作用。

5.FCN算法

FCN(Fully Convolutional Networks)是一种基于卷积神经网络的图像分割方法,其基本思想是:先对图像进行预处理(如归一化、裁剪),然后用卷积神经网络(CNN)提取图像特征,再利用反卷积网络(Deconvolutional Networks)重构图像,从而得到分割后的结果。

FCN算法的基本步骤如下图所示:

FCN算法有利于提取复杂的图像特征,能够有效地解决图像分割问题。缺点主要是计算量大。

6.SegNet算法

SegNet是另一种基于卷积神经网络的图像分割方法,其基本思想是:首先对图像进行预处理(如归一化、裁剪),然后分割图像分为两个阶段:编码阶段(Encoder Phase)和解码阶段(Decoder Phase)。编码阶段由卷积网络提取图像特征,解码阶段则由反卷积网络重构图像,从而得到分割后的结果。

SegNet算法的基本步骤如下图所示:

SegNet算法通过两个阶段的分割方式,能够有效地解决不同感受野下的目标对象的分割问题。

4.具体代码实例和详细解释说明

1.导入库

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
  • 1
  • 2

2.载入图片并显示

# 加载图像

# 显示图像
cv.imshow("Original Image", img)
cv.waitKey(0) # 等待按键输入
  • 1
  • 2
  • 3
  • 4

3.轮廓分割法

gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # 转换为灰度图
ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU) # 使用OSTU算法进行阈值处理
contours, hierarchy = cv.findContours(thresh, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) # 提取外部轮廓

# 创建空白图像
blank = np.zeros(img.shape[:2], dtype='uint8')

# 对每个轮廓绘制矩形
for cnt in contours:
    x,y,w,h = cv.boundingRect(cnt) # 获得矩形坐标
    if w > 10 and h > 10:
        cv.rectangle(blank,(x,y),(x+w,y+h),255,-1) # 在空白图像画出矩形

# 显示分割结果
plt.subplot(121),plt.imshow(cv.cvtColor(blank,cv.COLOR_GRAY2RGB))
plt.title('Contour Slicing'), plt.xticks([]), plt.yticks([])
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

4.分水岭变换法

gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # 转换为灰度图
ret, thresh = cv.threshold(gray, 0, 255, cv.THRESH_BINARY + cv.THRESH_OTSU) # 使用OSTU算法进行阈值处理

kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, (3,3)) # 定义核
opening = cv.morphologyEx(thresh, cv.MORPH_OPEN, kernel, iterations=2) # 开运算
sure_bg = cv.dilate(opening, kernel, iterations=3) # 扩张
dist_transform = cv.distanceTransform(opening, cv.DIST_L2, 5) # L2距离变换
_, sure_fg = cv.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0) # 确定前景
sure_fg = np.uint8(sure_fg)
unknown = cv.subtract(sure_bg, sure_fg) # 确定未知区域

# 区域生长
ret, markers = cv.connectedComponents(sure_fg)
markers += 1
markers[unknown==255] = 0

markers = cv.watershed(img, markers)
img[markers == -1] = [255,0,0] # 设置标记

# 显示分割结果
plt.subplot(121),plt.imshow(cv.cvtColor(sure_bg,cv.COLOR_GRAY2RGB)),plt.title('Opening')
plt.subplot(122),plt.imshow(cv.cvtColor(img,cv.COLOR_BGR2RGB)),plt.title('Segmented Image')
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

5.形态学处理法

gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) # 转换为灰度图
blur = cv.medianBlur(gray, 5) # 中值滤波
thresh = cv.adaptiveThreshold(blur, 255, cv.ADAPTIVE_THRESH_MEAN_C, cv.THRESH_BINARY, 11, 2) # 自适应阈值处理

kernel = cv.getStructuringElement(cv.MORPH_RECT, (3,3)) # 定义核
closing = cv.morphologyEx(thresh, cv.MORPH_CLOSE, kernel, iterations=2) # 闭运算
opening = cv.morphologyEx(closing, cv.MORPH_OPEN, kernel, iterations=2) # 开运算

kernel = cv.getStructuringElement(cv.MORPH_CROSS, (3,3)) # 定义核
dilation = cv.dilate(opening, kernel, iterations=1) # 膨胀
erosion = cv.erode(dilation, kernel, iterations=1) # 腐蚀

# 获取面积最大的矩形
cnts, _ = cv.findContours(erosion, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
max_area = max(cnts, key=lambda x: cv.contourArea(x)).astype(int)
x,y,w,h = cv.boundingRect(max_area)
cv.rectangle(img,(x,y),(x+w,y+h),255,-1) # 在原图画出矩形

# 显示分割结果
plt.subplot(121),plt.imshow(cv.cvtColor(thresh,cv.COLOR_GRAY2RGB))
plt.title('Adaptive Thresholding'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(cv.cvtColor(img,cv.COLOR_BGR2RGB))
plt.title('Dilated Rectangle'), plt.xticks([]), plt.yticks([])
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

6.基于树的分割算法

def create_cluster(data):
    '''
    生成聚类中心
    :param data: 样本数据
    :return: 聚类中心
    '''
    n_samples = len(data)
    cluster_center = []

    while True:
        centroid = random.sample(data, k=1)[0]
        is_change = False

        for i in range(n_samples):
            dis = euclidean_distances([centroid], [data[i]])

            if dis < min([euclidean_distances([c], [data[i]]) for c in cluster_center]):
                cluster_center.append(data[i])
                is_change = True

        if not is_change or len(cluster_center) >= K:
            break

    return cluster_center

def clustering(train_set, train_labels):
    '''
    训练聚类器
    :param train_set: 训练集
    :param train_labels: 训练集标签
    :return: 聚类中心
    '''
    center = None

    for i in range(MAX_ITERATION):
        new_center = {}

        for j in range(len(classifiers)):
            sub_train_set = list(filter(lambda x: train_labels[x][j]!= '?', [k for k, v in enumerate(train_labels[:, j])]))
            sub_train_label = [train_labels[idx][j] for idx in sub_train_set]

            clf = RandomForestClassifier(n_estimators=NUM_ESTIMATORS, random_state=RANDOM_STATE)
            clf.fit([[train_set[idx][0], train_set[idx][1]] for idx in sub_train_set], sub_train_label)

            pred_labels = ['?' if np.isnan(clf.predict([[pixel[0], pixel[1]]])[0]) else str(int(clf.predict([[pixel[0], pixel[1]]])[0]))
                            for pixel in [[row, col] for row in range(IMG_SIZE) for col in range(IMG_SIZE)]]

            labels = ''.join(pred_labels).replace('0', '.')
            centers = set(re.findall('\d+\.\d+', labels))

            if len(centers) <= 1:
                continue

            mean_points = [(float(p.split(',')[0]), float(p.split(',')[1]))
                           for p in re.findall('\((\d+\.\d+), (\d+\.\d+)\)', labels)]

            new_center[j] = np.mean(mean_points, axis=0)

        num_change = sum([(abs(np.linalg.norm(new_center[j] - center.get(j, np.array([0., 0.])))) > EPSILON)
                          for j in range(len(classifiers))])
        center = new_center

        print('Iteration:', i+1, '; Number of changes:', num_change)

        if num_change == 0:
            break

    return center

def get_colors():
    '''
    生成随机颜色列表
    :return: 随机颜色列表
    '''
    colors = []

    for color in range(K):
        r = lambda: random.randint(0, 255)
        colors.append('#{:02X}{:02X}{:02X}'.format(r(), r(), r()))

    return colors

# 加载图像

# 调整大小
IMG_SIZE = int(max(img.shape[:-1]))
img = cv.resize(img, (IMG_SIZE, IMG_SIZE), interpolation=cv.INTER_AREA)

# 将图片转换为灰度图像
gray_img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

# 二值化处理
_, binary_img = cv.threshold(gray_img, 127, 255, cv.THRESH_BINARY)

# 存储分割结果
clusters = {}

# 循环遍历每个区域
for i, component in enumerate(measure.regionprops(binary_img)):
    region = tuple(component.coords.flatten())
    mask = np.zeros(binary_img.shape[:2], dtype="uint8")
    cv.drawContours(mask, [region], contourIdx=-1, color=1, thickness=-1)
    masked_img = cv.bitwise_and(gray_img, gray_img, mask=mask)

    # 计算每个区域的平均灰度值
    avg_intensity = round(sum(masked_img.reshape(-1))/len(masked_img.reshape(-1)))

    clusters[(min(region), min(region)+width, min(region)+height, max(region)-width, max(region)-height)] \
             = avg_intensity

    if i % 1000 == 0:
        print('[INFO]: Processed', i,'regions.')

# 根据平均灰度值创建集群
K = 5
MAX_ITERATION = 5
NUM_ESTIMATORS = 100
EPSILON = 0.1
RANDOM_STATE = 42

classifiers = {j: Clustering(n_clusters=1) for j in range(len(clusters))}
center = clustering({(c[0]+c[-1])/2:(c[2]-c[1], c[3]-c[0]) for c in clusters},
                    {(c[0], c[1], c[2], c[3], c[4]):str(round(clusters[(c[0], c[1], c[2], c[3], c[4])]//16)*16)
                     for c in clusters})

# 将图片划分为K个区域
result = np.zeros((IMG_SIZE, IMG_SIZE, 3))
colors = get_colors()

for ((x1, y1, x2, y2), intensity) in clusters.items():
    result[x1:x2, y1:y2] = colors[round(intensity)//16]

# 显示分割结果
fig, axarr = plt.subplots(1, 2, figsize=(12, 6))
axarr[0].imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
axarr[0].set_title('Input Image')
axarr[1].imshow(result)
axarr[1].set_title('Clustered Image')
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/148704
推荐阅读
相关标签
  

闽ICP备14008679号