第 9 章 图像内容分析
在这一章,我们将介绍以下主题:
用OpenCV-Pyhon操作图像
检测边
直方图均衡化
检测棱角
检测SIFT特征点
创建Star特征检测器
利用视觉单词码本和向量量化创建特征
用极端随机森林训练图像分类器
创建一个对象识别器
9.1 简介
计算机视觉是一个研究如何处理、分析和理解视觉数据内容的领域。在图像内容分析中,会用到很多计算机视觉算法来构建我们对图像对象的理解。计算机视觉包括很多方面的图像分析,例如目标识别、形状分析、姿态估计、3D建模、视觉搜索等。人类非常擅长鉴定和识别其周边的事物,而计算机视觉的终极目标就是用计算机准确地模拟人类的视觉系统。
计算机视觉包括多个级别的分析。在低级视觉分析领域,计算机视觉可以进行像素处理,例如边检测、形态处理和光流。在中级和高级视觉分析领域,计算机视觉可以处理事物,例如物体识别、3D建模、运动分析以及其他方面的视觉数据。随着分析层次的深入,我们会对视觉系统的各个概念钻研得更加深入,并基于活动和意图提取出对视觉数据的描述。值得注意的一点是,高层次的分析往往依赖低层次分析的输出结果。
关于计算机视觉最常见的一个问题是“计算机视觉与图像处理有什么不同”。图像处理是在像素级别对图像进行变换。图像处理系统的输入和输出都是图像,常用的图像处理有边检测、直方图均衡化或图像压缩。计算机视觉算法大量依赖了图像处理算法来执行其任务。在计算机视觉领域,我们还处理更复杂的事情,例如在概念层级理解视觉数据,期望借此帮助自己构建对图像对象更有意义的描述。计算机视觉系统的输出是给定图像的3D场景的描述,这样的描述可以是各种形式的,而这取决于你的需要。
这一章将用到OpenCV库来分析图像。OpenCV是世界上最受欢迎的计算机视觉库。由于OpenCV已经为各种不同的平台进行了高度优化,它已然成为业界的事实标准。在继续学习接下来的内容之前,请确保在Python的支持下安装了这个库。你可以在http://opencv.org下载并安装OpenCV。有关各种操作系统的详细安装说明,可以参考网站的文档部分。
9.2 用OpenCV-Pyhon操作图像
下面看看如何用OpenCV-Python操作图像。这一节将介绍如何加载并展示图像,并介绍如何裁剪、调整大小以及将图像保存到输出文件中。
详细步骤
(1) 创建一个Python文件,并导入以下程序包:
import sys
import cv2
import numpy as np
(2) 指定输入图像为文件的第一个参数,并使用图像读取函数来读取参数。这个例子中将用到forest.jpg:
# 加载并显示图像forest.jpg
input_file = sys.argv[1]
img = cv2.imread(input_file)
(3) 显示输入图像:
cv2.imshow('Original', img)
(4) 现在裁剪该图像。提取输入图像的高度和宽度,然后指定边界:
# 裁剪图像
h, w = img.shape[:2]
start_row, end_row = int(0.21*h), int(0.73*h)
start_col, end_col= int(0.37*w), int(0.92*w)
(5) 用NumPy式的切分方式裁剪图像,并将其展示出来:
img_cropped = img[start_row:end_row, start_col:end_col]
cv2.imshow('Cropped', img_cropped)
(6) 将图像大小调整为其原始大小的1.3
倍,并将其展示出来:
# 调整图像大小
scaling_factor = 1.3
img_scaled = cv2.resize(img, None, fx=scaling_factor, fy=scaling_ factor,
interpolation=cv2.INTER_LINEAR)
cv2.imshow('Uniform resizing', img_scaled)
(7) 之前的方法将均匀地在两个维度上扩展图像。假定我们希望仅在某一个维度进行调整,可以用以下代码实现:
img_scaled = cv2.resize(img, (250, 400), interpolation=cv2.INTER_ AREA)
cv2.imshow('Skewed resizing', img_scaled)
(8) 将图像保存到输出文件:
# 保存图像
output_file = input_file[:-4] + 'cropped.jpg'
cv2.imwrite(outputfile, img_cropped)
cv2.waitKey()
(9) waitKey
函数保持显示图像,直到按下键盘上的任一个按键。
(10) 全部代码已经在operating_on_images.py文件中给出。运行该代码,可以看到如图9-1所示的输入图像。
图 9-1
(11) 第二幅输出图像是裁剪后的图像,如图9-2所示。
图 9-2
(12) 第三幅图像是从两个维度均匀地调整大小后的图像,如图9-3所示。
图 9-3
(13) 第四幅图像是仅从一个维度调整大小后的图像,如图9-4所示。
图 9-4
9.3 检测边
边检测是计算机视觉中最常用到的技术之一,常用在很多应用的预处理过程中。接下来介绍如何用不同的边检测器检测输入图像的边。
详细步骤
(1) 创建一个Python文件,并导入以下程序包:
import sys
import cv2
import numpy as np
(2) 加载输入图像,本例用到chair.jpg:
# 加载输入图像chair.jpg,转换成灰度图
input_file = sys.argv[1]
img = cv2.imread(input_file, cv2.IMREAD_GRAYSCALE)
(3) 提取输入图像的高度和宽度:
h, w = img.shape
(4) 索贝尔滤波器(Sobel filter)是一种边检测器,它使用3×3内核来检测水平边和垂直边。你可以在http://www.tutorialspoint.com/dip/sobel_operator.htm查看到更多索贝尔滤波器的信息。先运行索贝尔水平检测器:
sobel_horizontal = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5)
(5) 运行索贝尔垂直检测器:
sobel_vertical = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5)
(6) 拉普拉斯边检测器(Laplacian edge detector)可以检测两个方向上的边。你可以在http://homepages.inf.ed.ac.uk/rbf/HIPR2/log.htm找到更多相关介绍。定义拉普拉斯边检测器如下:
laplacian = cv2.Laplacian(img, cv2.CV_64F)
(7) 尽管拉普拉斯边检测器弥补了索贝尔边检测器的不足,但是拉普拉斯边检测器的输出仍然带有很多噪声。Canny边检测器(Canny edge detector)在解决噪声问题方面优于拉普拉斯边检测器和索贝尔边检测器。Canny边检测器是一个分阶段的处理过程,它用到了迟滞性来做边数据清理。你可以在http://homepages.inf.ed.ac.uk/rbf/HIPR2/canny.htm了解更多相关细节:
canny = cv2.Canny(img, 50, 240)
(8) 显示所有的输出图像:
cv2.imshow('Original', img)
cv2.imshow('Sobel horizontal', sobel_horizontal)
cv2.imshow('Sobel vertical', sobel_vertical)
cv2.imshow('Laplacian', laplacian)
cv2.imshow('Canny', canny)
cv2.waitKey()
(9) 全部代码已经在edge_detector.py文件中给出。运行该代码,可以看到原始输入图像如图9-5所示。
图 9-5
(10) 如图9-6所示是索贝尔水平边检测器的输出。注意,它到检测到的边大致都是垂直的,这是因为它是一个水平边检测器,它能检测出在水平方向上的变化。
图 9-6
(11) 索贝尔垂直边检测器的输出如图9-7所示。
图 9-7
(12) 如图9-8所示是拉普拉斯边检测器的输出。
图 9-8
(13) Canny边检测器较好地检测出了所有的边,如图9-9所示。
图 9-9
9.4 直方图均衡化
直方图均衡化是指修改图像的像素以增强图像的对比强度的过程。人的眼睛喜欢对比,这也是为什么几乎所有的照相机系统都会用直方图均衡化来使图像更好看。有趣的是,直方图均衡化过程不同于彩色图像的灰度化过程。在处理彩色图像时有一个问题,在这一节的介绍中将会提到。接下来具体介绍如何实现直方图均衡化。
详细步骤
(1) 创建一个Python文件,并导入以下程序包:
import sys
import cv2
import numpy as np
(2) 加载输入图像。这个例子将用到sunrise.jpg:
# 加载输入图像sunrise.jpg
input_file = sys.argv[1]
img = cv2.imread(input_file)
(3) 将图像转为灰度并将其显示出来:
# 转化为灰度图
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('Input grayscale image', img_gray)
(4) 均衡灰度图像的直方图,并将其显示出来:
# 均衡直方图
img_gray_histeq = cv2.equalizeHist(img_gray)
cv2.imshow('Histogram equalized - grayscale', img_gray_histeq)
(5) 为了均衡彩色图像的直方图,需要用到不同于以上的步骤。直方图均衡化仅适用于亮度通道。一个RGB图像由3个颜色通道组成,因此不能对这些通道单独地做直方图均衡化。在做其他操作之前,需要将强度信息从颜色信息中分离出来。因此,首先将其转换到YUV色彩空间,均衡Y通道,然后将其转换回RGB并得到输出。更多关于YUV色彩空间的详细介绍可查看http://softpixel.com/~cwright/programming/colorspace/yuv。OpenCV默认用BGR格式加载图像,因此需要先将其从BGR转化为YUV:
# 均衡彩色图像的直方图
img_yuv = cv2.cvtColor(img, cv2.COLOR_BGR2YUV)
(6) 均衡Y通道:
img_yuv[:,:,0] = cv2.equalizeHist(img_yuv[:,:,0])
(7) 将其转换回BGR:
img_histeq = cv2.cvtColor(img_yuv, cv2.COLOR_YUV2BGR)
(8) 显示输入和输出图像:
cv2.imshow('Input color image', img)
cv2.imshow('Histogram equalized - color', img_histeq)
cv2.waitKey()
(9) 全部代码在已经histogram_equalizer.py文件中给出,运行该代码,可以看到原始输入图像如图9-10所示。
图 9-10
(10) 直方图均衡化处理后的图像如图9-11所示。
图 9-11
9.5 检测棱角
棱角检测是计算机视觉中的一个重要环节,它帮助我们识别图像中突出的点。这是用于开发图像分析系统中最早期的特征提取技术之一。
详细步骤
(1) 创建一个Python文件,并导入以下程序包:
import sys
import cv2
import numpy as np
(2) 加载输入图像。本例将用到box.png:
# 加载图像box.png
input_file = sys.argv[1]
img = cv2.imread(input_file)
cv2.imshow('Input image', img)
(3) 将图像转为灰度,并将其强制转换为浮点值。浮点值将用于棱角检测过程:
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_gray = np.float32(img_gray)
(4) 对灰度图像运行哈里斯角检测器(Harris corner detector)函数。你可以在http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_feature2d/py_features_harris/py_features_harris.html查看更多哈里斯角检测器的详细介绍:
# 哈里斯角检测器
img_harris = cv2.cornerHarris(img_gray, 7, 5, 0.04)
(5) 为了标记棱角,需要放大图像:
# 放大图像以标记棱角
img_harris = cv2.dilate(img_harris, None)
(6) 定义显示重要点个数的阈值:
# 用阈值显示棱角
img[img_harris > 0.01 * img_harris.max()] = [0, 0, 0]
(7) 显示输出图像:
cv2.imshow('Harris Corners', img)
cv2.waitKey()
(8) 全部代码已经在corner_detector.py文件中给出。运行该代码,可以看到原始输入图像如图9-12所示。
图 9-12
(9) 检测棱角处理后的图像如图9-13所示。
图 9-13
9.6 检测SIFT特征点
尺度不变特征变换(Scale Invariant Feature Transform,SIFT)是计算机视觉领域最常用的特征之一。David Lowe首次在其论文中提出该特征,具体可参考https://www.cs.ubc.ca/~lowe/papers/ijcv04.pdf。此后,SIFT成为图像识别和图像内容分析领域最有效的特征之一,它在大小、方向、对比度等方向都有较强的健壮性。SIFT也是目标识别系统的基础。接下来介绍如何检测这些特征点。
详细步骤
(1) 创建一个Python文件,并导入以下程序包:
import sys
import cv2
import numpy as np
(2) 加载输入图像。本例将用到table.jpg:
# 加载图像table.jpg
input_file = sys.argv[1]
img = cv2.imread(input_file)
(3) 将图像转为灰度:
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
(4) 初始化SIFT检测器对象并提取关键点:
sift = cv2.xfeatures2d.SIFT_create()
keypoints = sift.detect(img_gray, None)
(5) 上面所说的关键点是指突出的点,但它们并不是特征。这基本上指出了突出点的位置。SIFT还可以作为非常有效的特征提取器,这一点将在后面的某一节中介绍。
(6) 在输入图像上画出关键点:
img_sift = np.copy(img)
cv2.drawKeypoints(img, keypoints, img_sift, flags=cv2.DRAW_
MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
(7) 显示输入和输出图像:
cv2.imshow('Input image', img)
cv2.imshow('SIFT features', img_sift)
cv2.waitKey()
(8) 全部代码已经在feature_detector.py文件中给出。运行该代码,可以看到原始输入图像如图9-14所示。
图 9-14
(9) 输出图像如图9-15所示。
图 9-15
9.7 创建Star特征检测器
SIFT特征检测器在很多场景中都很好用,但是,当创建目标识别系统时,在用SIFT检测特征之前,可能需要用到一个不同的特征检测器,这使我们能够通过灵活地层叠不同的模块来获得最佳的性能。这一节将用到Star特征检测器(Star feature detector),查看其性能表现。
详细步骤
(1) 创建一个Python文件,并导入以下程序包:
import sys
import cv2
import numpy as np
(2) 定义一个类,用于处理与Star特征检测相关的函数:
class StarFeatureDetector(object):
def __init__(self):
self.detector = cv2.xfeatures2d.StarDetector_create()
(3) 定义一个对输入图像运行检测器的函数:
def detect(self, img):
return self.detector.detect(img)
(4) 在main
函数中加载输入图像。本例将用到table.jpg:
if __name__=='__main__':
# 加载图像table.jpg
input_file = sys.argv[1]
input_img = cv2.imread(input_file)
(5) 将图像转为灰度:
# 转为灰度图
img_gray = cv2.cvtColor(input_img, cv2.COLOR_BGR2GRAY)
(6) 用Star特征检测器检测出特征:
# 用Star特征检测器检测出特征
keypoints = StarFeatureDetector().detect(input_img)
(7) 画出输入图像的关键点:
cv2.drawKeypoints(input_img, keypoints, input_img,
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
(8) 显示输出图像:
cv2.imshow('Star features', input_img)
cv2.waitKey()
(9) 全部代码已经在star_detector.py文件中给出。运行该代码,可以看到原始输入图像如图9-16所示。
图 9-16
9.8 利用视觉码本和向量量化创建特征
为了创建一个目标识别系统,需要从每张图像中提取特征向量。每张图像需要有一个识别标志,以用于匹配。我们用一个叫视觉码本的概念来创建图像识别标志。在训练数据集中,这个码本基本上是一个字典,用于提出关于图像的描述。我们用向量量化方法将很多特征点进行聚类并得出中心点。这些中心点将作为视觉码本的元素,更详细的介绍可以参考http://mi.eng.cam.ac.uk/~cipolla/lectures/PartIB/old/IB-visualcodebook.pdf。
在开始接下来的学习之前,请确保你已经有一些训练图像。本例提供了包含3个类的示例训练数据集,每一类包含20幅图像,这些图像可以在http://www.vision.caltech.edu/html-files/archive.html下载。
为了创建一个健壮的目标识别系统,你需要数万幅图像。该领域有一个非常著名的数据集叫Caltech256,它包括256类图像,每一类都包含上千幅示例图像。你可以在http://www.vision.caltech.edu/Image_Datasets/Caltech256下载该数据集。
详细步骤
(1) 这是一个比较长的例子,因此这里仅介绍一些重点函数。全部代码已经在build_features.py文件中给出。下面先定义一个提取特征的类:
class FeatureBuilder(object):
(2) 定义一个从输入图像提取特征的方法。下面将用Star检测器获得关键点,然后用SIFT提取这些位置的描述信息:
def extract_features(self, img):
keypoints = StarFeatureDetector().detect(img)
keypoints, feature_vectors = compute_sift_features(img, keypoints)
return feature_vectors
(3) 从描述信息中提取出中心点:
def get_codewords(self, input_map, scaling_size, max_samples=12):
keypoints_all = []
count = 0
cur_class = ''
(4) 每幅图像都会生成大量的描述信息。这里将仅用一小部分图像,因为这些中心点并不会发生很大的改变:
for item in input_map:
if count >= max_samples:
if cur_class != item['object_class']:
count = 0
else:
continue
count += 1
(5) 将进程打印出来:
if count == max_samples:
print "Built centroids for", item['object_class']
(6) 提取当前标签:
cur_class = item['object_class']
(7) 读取图像并调整其大小:
img = cv2.imread(item['image_path'])
img = resize_image(img, scaling_size)
(8) 设置维度数为128并提取特征:
num_dims = 128
feature_vectors = self.extract_features(img)
keypoints_all.extend(feature_vectors)
(9) 用向量量化来量化特征点。向量量化是一个N维的“四舍五入”,更多详细介绍可参考http://www.data-compression.com/vq.shtml:
kmeans, centroids = BagOfWords().cluster(keypoints_all)
return kmeans, centroids
(10) 定义一个类来处理词袋模型和向量量化:
class BagOfWords(object):
def __init__(self, num_clusters=32):
self.num_dims = 128
self.num_clusters = num_clusters
self.num_retries = 10
(11) 定义一个方法来量化数据点。下面将用k-means聚类来实现:
def cluster(self, datapoints):
kmeans = KMeans(self.num_clusters,
n_init=max(self.num_retries, 1),
max_iter=10, tol=1.0)
(12) 提取中心点:
res = kmeans.fit(datapoints)
centroids = res.cluster_centers_
return kmeans, centroids
(13) 定义一个方法来归一化数据:
def normalize(self, input_data):
sum_input = np.sum(input_data)
if sum_input > 0:
return input_data / sum_input
else:
return input_data
(14) 定义一个方法来获得特征向量:
def construct_feature(self, img, kmeans, centroids):
keypoints = StarFeatureDetector().detect(img)
keypoints, feature_vectors = compute_sift_features(img, keypoints)
labels = kmeans.predict(feature_vectors)
feature_vector = np.zeros(self.num_clusters)
(15) 创建一个直方图并将其归一化:
for i, item in enumerate(feature_vectors):
feature_vector[labels[i]] += 1
feature_vector_img = np.reshape(feature_vector,
((1, feature_vector.shape[0])))
return self.normalize(feature_vector_img)
(16) 定义一个方法来提取SIFT特征:
# 提取SIFT特征
def compute_sift_features(img, keypoints):
if img is None:
raise TypeError('Invalid input image')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
keypoints, descriptors = cv2.xfeatures2d.SIFT_create().compute(img_gray, keypoints)
return keypoints, descriptors
正如之前提到的,全部代码可参考build_features.py文件。可以用以下方式来运行代码:
$ python build_features.py –-data-folder /path/to/training_images/ --codebook-file codebook.pkl --feature-map-file feature_map.pkl
结果将产生两个文件,分别为codebook.pkl和feature_map.pkl。下一节中将用到这两个文件。
9.9 用极端随机森林训练图像分类器
本节将用极端随机森林(Extremely Random Forests,ERF)来训练图像分类器。一个目标识别系统就是利用图像分类器将图像分到已知的类别中。ERF在机器学习领域非常流行,因为ERF具有较快的速度和比较精确的准确度。我们基于图像的特征构建一组决策树,并通过训练这个森林实现正确决策。更多随机森林的详细内容可参考https://www.stat.berkeley.edu/~breiman/RandomForests/cc_home.htm,更多ERF的内容可参考http://www.montefiore.ulg.ac.be/~ernst/uploads/news/id63/extremely-randomized-trees.pdf。
详细步骤
(1) 创建一个Python文件,并且导入以下程序包:
import argparse
import cPickle as pickle
import numpy as np
from sklearn.ensemble import ExtraTreesClassifier
from sklearn import preprocessing
(2) 定义一个参数解析器:
def build_arg_parser():
parser = argparse.ArgumentParser(description='Trains the classifier')
parser.add_argument("--feature-map-file", dest="feature_map_file", required=True,
help="Input pickle file containing the feature map")
parser.add_argument("--model-file", dest="model_file", required=False,
help="Output file where the trained model will be stored")
return parser
(3) 定义一个类来处理ERF训练。这里将用到一个标签编码器来对训练标签进行编码:
class ERFTrainer(object):
def __init__(self, X, label_words):
self.le = preprocessing.LabelEncoder()
self.clf = ExtraTreesClassifier(n_estimators=100, max_depth=16, random_state=0)
(4) 对标签编码并训练分类器:
y = self.encode_labels(label_words)
self.clf.fit(np.asarray(X), y)
(5) 定义一个函数,用于对标签进行编码:
def encode_labels(self, label_words):
self.le.fit(label_words)
return np.array(self.le.transform(label_words), dtype=np.float32)
(6) 定义一个函数,用于将未知数据点进行分类:
def classify(self, X):
label_nums = self.clf.predict(np.asarray(X))
label_words = self.le.inverse_transform([int(x) for x in label_nums])
return label_words
(7) 定义main
函数并解析输入参数:
if __name__=='__main__':
args = build_arg_parser().parse_args()
feature_map_file = args.feature_map_file
model_file = args.model_file
(8) 加载上一节中生成的特征地图:
# 加载特征地图
with open(feature_map_file, 'r') as f:
feature_map = pickle.load(f)
(9) 提取特征向量:
# 提取特征向量和标记
label_words = [x['object_class'] for x in feature_map]
dim_size = feature_map[0]['feature_vector'].shape[1]
X = [np.reshape(x['feature_vector'], (dim_size,)) for x in feature_map]
(10) 基于训练数据训练ERF:
# 训练ERF分类器
erf = ERFTrainer(X, label_words)
(11) 保存训练的ERF模型:
if args.model_file:
with open(args.model_file, 'w') as f:
pickle.dump(erf, f)
(12) 全部代码已经在trainer.py文件中给出。可以用以下方式运行代码:
$ python trainer.py --feature-map-file feature_map.pkl --model-file erf.pkl
结果将产生一个erf.pkl文件。下一节中将用到该文件。
9.10 创建一个对象识别器
训练好一个ERF模型后,接下来创建一个目标识别器,该识别器可以识别未知图像的内容。
详细步骤
(1) 创建一个Python文件,并且导入以下程序包:
import argparse
import cPickle as pickle
import cv2
import numpy as np
import build_features as bf
from trainer import ERFTrainer
(2) 定义一个参数解析器:
def build_arg_parser():
parser = argparse.ArgumentParser(description='Extracts features \
from each line and classifies the data')
parser.add_argument("--input-image", dest="input_image", required=True,
help="Input image to be classified")
parser.add_argument("--model-file", dest="model_file", required=True,
help="Input file containing the trained model")
parser.add_argument("--codebook-file", dest="codebook_file",
required=True, help="Input file containing the codebook")
return parser
(3) 定义一个类来处理图像标签提取函数:
class ImageTagExtractor(object):
def __init__(self, model_file, codebook_file):
with open(model_file, 'r') as f:
self.erf = pickle.load(f)
with open(codebook_file, 'r') as f:
self.kmeans, self.centroids = pickle.load(f)
(4) 定义一个函数,用于使用训练好的ERF模型来预测输出:
def predict(self, img, scaling_size):
img = bf.resize_image(img, scaling_size)
feature_vector = bf.BagOfWords().construct_feature(
img, self.kmeans, self.centroids)
image_tag = self.erf.classify(feature_vector)[0]
return image_tag
(5) 定义main
函数,加载输入图像:
if __name__=='__main__':
args = build_arg_parser().parse_args()
model_file = args.model_file
codebook_file = args.codebook_file
input_image = cv2.imread(args.input_image)
(6) 合理地调整图像大小:
scaling_size = 200
(7) 在命令行打印输出结果:
print "\nOutput:", ImageTagExtractor(model_file,
codebook_file).predict(input_image, scaling_size)
(8) 全部代码已经在object_recognizer.py文件中给出。可以用以下方式运行代码:
$ python object_recognizer.py --input-image imagefile.jpg --model-file erf.pkl --codebook-file codebook.pkl
可以看到命令行工具中输出的类。