0%

机器学习--spark基于opencv做图片分类

spark擅长分布式处理海量数据,opencv又是进行图像处理的利器,在面对海量图片的时候,我们是否可以考虑结合二者的优势? 本文将展示如何在spark下使用opencv,并训练一个简单的二分类模型。

环境准备

  1. 我们首先在单机上测试,先下载opencv源码,安装opencv可以参考这篇
  2. 在编译好的文件夹下 有命名为opencv_xxx.jar的java接口, 这个需要添加到项目依赖,同时要添加jvm运行时的option。以IDEA为例,Run->EditConfiguration, vm option 添加 -Djava.library.path="/Users/mluo/Documents/opencv/build/lib"
  3. 在开始运行时添加 System.loadLibrary(Core.NATIVE_LIBRARY_NAME),确保能够Load the native library
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def main(args: Array[String]): Unit = {

    val rootLogger = Logger.getRootLogger()
    rootLogger.setLevel(Level.WARN)

    // Load the native library.
    System.loadLibrary(Core.NATIVE_LIBRARY_NAME)
    .....
    }
    完成以上的步骤,就可以保证在spark中正常调用opencv

图片的特征处理

常用的图像特征有颜色特征、纹理特征、形状特征、空间关系特征

  • 什么是颜色通道
  1. 单通道图 俗称灰度图,每个像素点只能有有一个值表示颜色,它的像素值在0到255之间,0是黑色,255是白色,中间值是一些不同等级的灰色。(也有3通道的灰度图,3通道灰度图只有一个通道有值,其他两个通道的值都是零)。

  2. 三通道图 每个像素点都有3个值表示 ,所以就是3通道。也有4通道的图。例如RGB图片即为三通道图片,RGB色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。总之,每一个点由三个值表示。还会有其他的描述通道 比如 CMYK(四个分量)、Lab(三个)、HSV(三个)等等

  • 图片为什么要灰度化 识别物体,最关键的因素是梯度(SIFT/HOG),梯度意味着边缘,这是最本质的部分,而计算梯度,自然就用到灰度图像了,可以把灰度理解为图像的强度。颜色,易受光照影响,难以提供关键信息,故将图像进行灰度化,同时也可以加快特征提取的速度。

  • 什么是仿射不变形 平面上任意两条线,经过仿射变换后,仍保持原来的状态(比如平行的线还是平行,相交的线夹角不变等)

  • 什么是局部特征 局部特征有哪些特点 局部特征从总体上说是图像或在视觉领域中一些有别于其周围的地方;局部特征通常是描述一块区域,使其能具有高可区分度;局部特征的好坏直接会决定着后面分类、识别是否会得到一个好的结果。局部特征应该具有的特点: 可重复性、可区分性、准确性、有效性(特征的数量、特征提取的效率)、鲁棒性(稳定性、不变性)

SIFT(尺度不变特征变换)特征提取

实质是在不同的尺度空间上查找关键点(特征点),并计算出关键点的方向。SIFT所查找到的关键点是一些十分突出、不会因光照、仿射变换和噪音等因素而变化的点,如角点、边缘点、暗区的亮点及亮区的暗点等。主要步骤如下:

  1. 构建DOG尺度空间: 模拟图像数据的多尺度特征,大尺度抓住概貌特征,小尺度注重细节特征。通过构建高斯金字塔(每一层用不同的参数σ做高斯模糊(加权)),保证图像在任何尺度都能有对应的特征点,即保证尺度不变性。

  2. 关键点搜索和定位: 确定是否为关键点,需要将该点与同尺度空间不同σ值的图像中的相邻点比较,如果该点为max或min,则为一个特征点。找到所有特征点后,要去除低对比度和不稳定的边缘效应的点,留下具有代表性的关键点(比如,正方形旋转后变为菱形,如果用边缘做识别,4条边就完全不一样,就会错误;如果用角点识别,则稳定一些)。去除这些点的好处是增强匹配的抗噪能力和稳定性。最后,对离散的点做曲线拟合,得到精确的关键点的位置和尺度信息。

  3. 方向赋值: 为了实现旋转不变性,需要根据检测到的关键点的局部图像结构为特征点赋值。具体做法是用梯度方向直方图。在计算直方图时,每个加入直方图的采样点都使用圆形高斯函数进行加权处理,也就是进行高斯平滑。这主要是因为SIFT算法只考虑了尺度和旋转不变形,没有考虑仿射不变性。通过高斯平滑,可以使关键点附近的梯度幅值有较大权重,从而部分弥补没考虑仿射不变形产生的特征点不稳定。注意,一个关键点可能具有多个关键方向,这有利于增强图像匹配的鲁棒性。

  4. 关键点描述子的生成: 关键点描述子不但包括关键点,还包括关键点周围对其有贡献的像素点。这样可使关键点有更多的不变特性,提高目标匹配效率。在描述子采样区域时,需要考虑旋转后进行双线性插值,防止因旋转图像出现白点。同时,为了保证旋转不变性,要以特征点为中心,在附近领域内旋转θ角,然后计算采样区域的梯度直方图,形成n维SIFT特征矢量(如128-SIFT)。最后,为了去除光照变化的影响,需要对特征矢量进行归一化处理。

  • SIFT特征提取的优点
    1. SIFT特征是图像的局部特征,其对旋转、尺度缩放、亮度变化保持不变性,对视角变化、仿射变换、噪声也保持一定程度的稳定性;
    2. 独特性(Distinctiveness)好,信息量丰富,适用于在海量特征数据库中进行快速、准确的匹配;
    3. 多量性,即使少数的几个物体也可以产生大量的SIFT特征向量;
    4. 高速性,经优化的SIFT匹配算法甚至可以达到实时的要求;
    5. 可扩展性,可以很方便的与其他形式的特征向量进行联合;需要较少的经验主义知识,易于开发。
  • SIFT特征提取的缺点
    1. 实时性不高,因为要不断地要进行下采样和插值等操作;
    2. 有时特征点较少(比如模糊图像);
    3. 对边缘光滑的目标无法准确提取特征(比如边缘平滑的图像,检测出的特征点过少,对圆更是无能为力)。
  • SIFT特征提取可以解决的问题 目标的自身状态、场景所处的环境和成像器材的成像特性等因素影响图像配准/目标识别跟踪的性能。而SIFT算法在一定程度上可解决:
    • 目标的旋转、缩放、平移(RST)
    • 图像仿射/投影变换(视点viewpoint)
    • 光照影响(illumination)
    • 目标遮挡(occlusion)
    • 杂物场景(clutter)
    • 噪声

颜色特征

颜色直方图

颜色直方图用以反映图像颜色的组成分布,即各种颜色出现的概率。Swain和Ballard最先提出了应用颜色直方图进行图像特征提取的方法,首先利用颜色空间三个分量的剥离得到颜色直方图,之后通过观察实验数据发现将图像进行旋转变换、缩放变换、模糊变换后图像的颜色直方图改变不大,即图像直方图对图像的物理变换是不敏感的。因此常提取颜色特征并用颜色直方图应用于衡量和比较两幅图像的全局差。另外,如果图像可以分为多个区域,并且前景与背景颜色分布具有明显差异,则颜色直方图呈现双峰形。

颜色直方图也有其缺点:由于颜色直方图是全局颜色统计的结果,因此丢失了像素点间的位置特征。可能有几幅图像具有相同或相近的颜色直方图,但其图像像素位置分布完全不同。因此,图像与颜色直方图得多对一关系使得颜色直方图在识别前景物体上不能获得很好的效果。

考虑到颜色直方图的以上问题,主色调直方图便产生了。所谓主色调直方图基于假设少数几个像素的值能够表示图像中的绝大部分像素,即出现频率最高的几个像素被选为主色,仅用主色构成的主色调直方图描述一幅图像。这样的描述子并不会降低通过颜色特征进行匹配的效果,因为从某种角度将,频度出现很小的像素点可以被视为噪声。

颜色矩

颜色矩是一种有效的颜色特征,由Stricker和Orengo提出.该方法利用线性代数中矩的概念,将图像中的颜色分布用其矩表示。利用颜色一阶矩(平均值Average)、颜色二阶矩(方差Variance)和颜色三阶矩(偏斜度Skewness)来描述颜色分布。与颜色直方图不同,利用颜色矩进行图像描述无需量化图像特征。由于每个像素具有颜色空间的三个颜色通道,因此图像的颜色矩有9个分量来描述。由于颜色矩的维度较少,因此常将颜色矩与其他图像特征综合使用。

利用图像特征训练SVM分类模型

上面介绍了提取图像特征的主要方法,这里我们利用opencv计算每一张图片的颜色矩,

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
//计算颜色矩 总共9个分量 返回tuple
def calColorMoment(channel: Mat*) = {

val bch = channel(0) //blue channel
val gch = channel(1) //green channel
val rch = channel(2) //red channel

bch.convertTo(bch, CvType.CV_64FC1)
gch.convertTo(gch, CvType.CV_64FC1)
rch.convertTo(rch, CvType.CV_64FC1)

val mean = new MatOfDouble()
val std = new MatOfDouble()


Core.meanStdDev(bch, mean, std)
val bchArray: Array[Double] = Array(mean.toList.asScala.head, std.toList.asScala.head, cal3rdmoment(bch))

Core.meanStdDev(gch, mean, std)
val gchArray: Array[Double] = Array(mean.toList.asScala.head, std.toList.asScala.head, cal3rdmoment(gch))

Core.meanStdDev(rch, mean, std)
val rchArray: Array[Double] = Array(mean.toList.asScala.head, std.toList.asScala.head, cal3rdmoment(rch))

mean.release()
std.release()
bchArray ++ gchArray ++ rchArray
}

随后分别给训练图片打标,然后使用SVM训练并测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Run training algorithm to build the model
val numIterations = 100
val model = SVMWithSGD.train(training, numIterations)
// Clear the default threshold.
model.clearThreshold()

// this is a binary classification
// Compute raw scores on the test set.
val scoreAndLabels = test.map { point =>
val score = model.predict(point.features)
(score, point.label)
}


// Get evaluation metrics.
val metrics = new BinaryClassificationMetrics(scoreAndLabels)
val auROC = metrics.areaUnderROC()
println("Area under ROC = " + auROC)
最终结果如下图 2W张图片训练, 5k张图片测试总共耗时12min,作为一个单机spark运行来说,速度是ok的

小结

  1. opencv功能强大,提供了多种提取图像特征的方法,如果在spark集群环境下使用opencv,需要在每个节点上都安装opencv
  2. 在scala或者java中使用opencv要注意主动调用release函数释放资源,否则会有内存泄露的问题