赞
踩
作者:禅与计算机程序设计艺术
图像处理(Image Processing)是指利用计算机对图像进行分析、识别和处理的一门技术领域。对于人类来说,拍照、剪贴、看手机相册都是很平常的事情,但对图像进行分析、处理、或者说“分割”则属于高级技术范畴。现如今,随着计算机视觉、机器学习等技术的成熟和普及,许多软件工程师、科研工作者、数据分析师、产品经理以及AI/ML从业者都越来越多地依赖图像处理工具进行项目开发、解决实际问题。因此,掌握图像处理技术有助于让你的技能更上一层楼,从而实现更多的商业价值。
在此处,我们将以深度学习(Deep Learning)方法中的卷积神经网络(Convolutional Neural Network,CNN)来讲解图像分割技术的基本原理与相关知识。CNN 是一种简单且有效的图像分类器。它能够自动提取图像的特征信息,并通过卷积层、池化层以及全连接层对这些特征进行组合,最后输出分类结果。因此,掌握 CNN 的图像分割技术至关重要,因为这是许多计算机视觉任务的关键组件。
图像分割(Segmentation)是指将一张完整图片划分为多个独立的区域,每个区域代表一种不同的语义含义或对象。典型的应用场景包括:
除此之外,图像分割还具有其他的应用场景,例如:
常用的图像分割数据集有 Pascal VOC、Cityscapes、ADE20K、COCO、CamVid等。本文使用的两个数据集分别为 PASCAL VOC 2012 和 Cityscapes。
PASCAL VOC数据集(Pascal Visual Object Classes)是最流行的图像分割数据集。该数据集由加拿大牛津大学的研究人员自然标记制作,共包含了17125幅图像,其中20个类别(包括背景)、500个训练图像和15223张测试图像。训练集中包含115554张图像,验证集中包含5011张图像。测试集(VOC2012 Test)共有2913张图像。每张图像分辨率为400×300px,共有20个类别:人、猫、狗、汽车、植被、建筑、树、道路、农田、飞机、船只、鸟、水crafts等。
Cityscapes数据集是一个新颖的数据集,其由来自不同城市街道、交叉口的街景图片组成。该数据集的优点在于:
图像分割,是指将一张完整的图片分割成若干个子图像,每个子图像表示一种语义意思或特点,并对其中的每个子图像进行分类、检测、跟踪、理解。图像分割与图像分类有些类似,但是又不完全相同。
图像分类一般采用的是模式匹配的方法,即根据待分类对象的外观特征判断其所属类别。比如一张黑白图像中是否存在某个目标,就可以用一种分类器(如SVM)来判断其所属类别。然而,图像分割则是一种基于学习的计算机视觉技术,可以将图片中目标物体区域分割出来,然后再把它们按照目标的类别区分开来。
假定要分割的图像如下图所示:
那么,图像分割过程就是先确定一下哪些区域是目标物体的区域,然后再对这几个目标物体区域进行分类,比如汽车、鸟类、道路等等。
目前主流的图像分割技术主要有以下几种:
图像分割的基本步骤和流程如下图所示:
图像分割常见的算法有:
轮廓分割法(Contour Slicing)是指通过图像的像素值变化与形状关系,将图像划分为许多小的连续区域,每个区域表示一个目标对象。它的基本思想是:首先找出图像中所有目标对象的边界线,然后根据边界线形成轮廓(Contours)。对于每个轮廓,可以定义出一系列的形状特征,如矩形、圆形、椭圆等等。之后,可以用这些特征来定义出目标对象的边界范围,从而将图片划分成许多大小相似的区域,每个区域表示一个目标对象。
轮廓分割法的基本步骤如下图所示:
轮廓分割法主要优点是简单、易于实现。缺点主要有两点:第一,要求目标对象必须是连通的;第二,无法考虑目标对象内部的相互作用。
分水岭变换法(Watershed Transform)是指根据图像的梯度方向,计算出图像上的所有地物边界线。它的基本思想是:将图像看做是高度场,将其分割成连通的体积,然后计算各体积的地物的流量,也就是地物从哪里流向哪里。由于流量的方向与梯度的方向一致,所以可以找到各体积之间的路线,从而找到图像上所有的边界线。
分水岭变换法的基本步骤如下图所示:
分水岭变换法适用于复杂的非凸区域分割,而且不要求目标对象必须是连通的。缺点主要是计算量大。
形态学处理法(Morphological Processing)是指对图像的灰度值进行处理,形成目标对象的形状。它的基本思想是:首先对图像进行二值化处理,之后用一些形态学操作,比如腐蚀、膨胀、开、闭等,将图像转化成目标对象的形状。
形态学处理法的基本步骤如下图所示:
形态学处理法可以方便地实现对各种形状的目标对象的分割,但是缺点是只能处理简单形状的目标对象。
基于树的分割算法(Tree-Based Segmentation Method)是指将图像分割成一棵树的形式。它的基本思想是:首先根据图像的色彩模式,生成一系列的颜色阈值,然后按照颜色相似度,将这些阈值组织成一棵树。树的根节点表示整个图像,中间的叶子结点表示颜色相近的区域,而每个内部结点都对应于一块颜色区域。
基于树的分割算法的基本步骤如下图所示:
基于树的分割算法可以通过颜色相似性来将图像中的不同颜色区域分割成不同的区域,但是缺点主要是不容易处理目标对象内部的相互作用。
FCN(Fully Convolutional Networks)是一种基于卷积神经网络的图像分割方法,其基本思想是:先对图像进行预处理(如归一化、裁剪),然后用卷积神经网络(CNN)提取图像特征,再利用反卷积网络(Deconvolutional Networks)重构图像,从而得到分割后的结果。
FCN算法的基本步骤如下图所示:
FCN算法有利于提取复杂的图像特征,能够有效地解决图像分割问题。缺点主要是计算量大。
SegNet是另一种基于卷积神经网络的图像分割方法,其基本思想是:首先对图像进行预处理(如归一化、裁剪),然后分割图像分为两个阶段:编码阶段(Encoder Phase)和解码阶段(Decoder Phase)。编码阶段由卷积网络提取图像特征,解码阶段则由反卷积网络重构图像,从而得到分割后的结果。
SegNet算法的基本步骤如下图所示:
SegNet算法通过两个阶段的分割方式,能够有效地解决不同感受野下的目标对象的分割问题。
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# 加载图像
# 显示图像
cv.imshow("Original Image", img)
cv.waitKey(0) # 等待按键输入
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()
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()
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()
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()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。