Fork me on GitHub

OpenCV简介及使用(一)

前言

OpenCV (Open Source Computer Vision Library) 是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android和Mac OS操作系统上。它主要由 C++ 语言编写,同时提供了Python、Ruby、MATLAB、Java等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法,同时一个使用CUDA的GPU接口也于2010年9月开始实现。

该库拥有 2500 多个优化算法,其中包括一套全面的经典和最先进的计算机视觉和机器学习算法。这些算法可用于检测和识别人脸、识别对象、对视频中的人类行为进行分类、跟踪摄像机移动、跟踪移动对象、提取对象的 3D 模型、从立体摄像机生成 3D 点云、将图像拼接在一起生成整个场景的高分辨率图像,从图像数据库中查找相似图像,从使用闪光灯拍摄的图像中删除红眼,跟踪眼睛运动,识别风景,并建立标记以覆盖其增强现实等。

除了谷歌、微软、英特尔、IBM、索尼、等老牌公司,还有许多初创公司,它们广泛使用OpenCV。

我们可以通过以下网址获取各个版本的 OpenCV ,OpenCV最新版本已经进入 4.x.x 了。

https://opencv.org/

OpenCV环境搭建

下面分别展示了Windows和Mac环境下的OpenCV搭建,在Linux环境下搭建和Mac环境下类似,如有机会,我会在进行补充。

本文基于 Java + IDEA + OpenCV 环境的搭建和使用来进行说明。

Windows环境下的OpenCV环境搭建

首先下载 OpenCV for Windows 版本,我这儿下载了4.1.1 版本的OpenCV。

我们将这个exe运行,将OpenCV安装在一个方便寻找的目录下即可。

至此,OpenCV算是在Windows环境上安装了,下面我们来进行开发环境搭建。

PS:如果想获取OpenCV最新版进行安装,可以通过OpenCV的 GitHub 获取最新版源码,在通过CMake工具进行编译生成OpenCV Lib,这块大家可以查阅相关资料,不在过多叙述。

我们打开OpenCV的安装目录,在 build/java 可以看到 opencv-411.jarx64、x86 两个文件夹,这两个文件夹里有两个dll文件,都叫opencv_java411.dll,这分别是64位系统和32系统需要使用的动态链接库文件,我们如果想在Java环境下使用OpenCV,在JVM启动时,必须将dll文件加载进去,然后才能使用opencv-411.jar里提供的Java接口方法。

upload successful

我们来看简单测试下,我们用IDEA新建一个Java项目test,同时需要引入上面的dll文件和jar文件。

新建一个Java项目:

upload successful

引入必要文件:

upload successful

这儿要注意两个文件同时选中,引入后如下图效果,Native Library 可以被正确加载到。否则在启动后会出现 java.lang.UnsatisfiedLinkError错误。

upload successful

然后我们测试一下,代码如下:

1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
Mat mat = new Mat();
System.out.println(mat.toString());
}
}

可以看到正确输出内容。

upload successful

这儿要注意System.loadLibrary(Core.NATIVE_LIBRARY_NAME);这句话,在使用OpenCV时,需要调用此语句以加载原生Library。

PS:上面的文件引入过程,dll和jar也是可以分别引入的,也是相当于引入到了JVM运行环境中。本质上是没有区别的,但一起引入方便理解,即opencv_java411.dll是为opencv-411.jar ”服务“ 的。

upload successful

显然我们实际的项目是没有这么简单的,而且很多是Web (SpringBoot)项目,使用Maven管理,这种直接引入对我们的管理十分不方便。

对于jar包部分,我们可以使用如下配置引入:

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.bytedeco/opencv -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.1.0-1.5.1</version>
</dependency>

对于dll文件,我们有三种引入方式:

  1. 按照上面直接添加lib到项目里

    这种虽然可以正常使用,但是在Maven打包项目部署到服务器运行后仍会遇到连接不到的问题。

  2. 添加VM Options

    我们可以将dll所在路径添加到VM Options里,-Djava.library.path=D:/opencv/build/java/x64

    upload successful

  3. 使用程序在项目启动时加入

    理论上JVM启动后就不能在向其中加入参数了,但我们可以利用反射机制,在JVM启动时将java.library.path参数添加进去,代码如下:

    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
    /**
    * 追加VM Options参数
    * java.library.path
    * 需要在启动后直接调用
    * @param libraryPath
    * @throws Exception
    */
    public static void addLibraryDir(String libraryPath) throws Exception {
    Field userPathsField = ClassLoader.class.getDeclaredField("usr_paths");
    userPathsField.setAccessible(true);
    String[] paths = (String[]) userPathsField.get(null);
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < paths.length; i++) {
    if (libraryPath.equals(paths[i])) {
    continue;
    }
    sb.append(paths[i]).append(';');
    }
    sb.append(libraryPath);
    System.setProperty("java.library.path", sb.toString());
    //系统变量设置为空,JVM会重新加载 sys_paths 和 usr_paths
    final Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths");
    sysPathsField.setAccessible(true);
    sysPathsField.set(null, null);
    }

    这个方法在项目启动时调用即可。

    这个方法在JDK9 及以上版本会出现警告,因为我们这样操作是不安全的,也是不建议的,未来JDK所有非法访问操作将被拒绝。

    upload successful

我们通常也建议使用第二种方法引入OpenCV动态链接库dll文件。

PS:有时候我们环境搭建好了,可能仍无法使用,出现动态库dll找不到的情况,如下:

upload successful

这时候需要注意Core.NATIVE_LIBRARY_NAME这段代码,我们点进去,可以看到它使用的 LIBRARY_NAME 为 opencv_java410,加载不到的原因是我们安装的是 OpenCV 4.1.1 版本,这时候我们使用System.loadLibrary(“opencv_java411”);即可加载成功,出现这种情况的原因是我们上面Maven下载的jar包是 4.1.0 版本的(这也是目前Maven上的最高版本)。所以为防止出现问题,需要知道自己安装了什么版本的OpenCV,并建议设置为常量,方便引入。

Mac环境下的OpenCV环境搭建

OpenCV未提供Mac版本的安装程序,我们通常有以下几种安装方式:

  1. 使用Homebrew进行安装

    这是获取OpenCV最快的方式,安装Homebrew后,要检查Mac系统是否安装了 XCode Command Line Tools。

    在Terminal下执行如下命令 xcode-select –install,如果系统要求安装此工具,则进行安装即可。

    上述步骤完成后,我们可以直接使用brew install opencv安装OpenCV,安装好后的文件位于/usr/local/Cellar/opencv4文件夹下。

    我们使用此种方式安装,可以发现得到的OpenCV安装文件里是不包含支持Java接口部分的编译文件的。

    这对我们来说十分不友好。

  2. 使用Homebrew下载源码自动编译安装

    对于OpenCV,Homebrew也是可以在线自动下载源码到本地并自动进行编译安装的。

    在安装Homebrew和 XCode Command Line Tools 后,使用Homebrew安装cmake工具brew install cmake,这是OpenCV编译需要用到的工具。

    同时我们需要安装Apache Ant工具,brew install ant,因为OpenCV编译生成Java接口文件需要用到。

    然后我们使用指令brew edit opencv,可以打开查看opencv的编译项。如下图:

    upload successful

    然后找到编译项里的 -DBUILD_opencv_java=OFF,将其改为 -DBUILD_opencv_java=ON,然后保存配置。

    由上面的操作可以看到 Homebrew 的 OpenCV编译默认是不包含Java相关支持的。

    我们使用brew install –build-from-source opencv下载源码到本地自动进行编译安装。

    PS:我按照上述操作后确实会从GitHub上自动下载最新版源码并进行编译,但是在编译过程中遇到了一个”has no symbol”的错误。导致始终无法make成功,也未找到相关解决办法,于是放弃了该种安装办法。

    错误详情如下:

    1
    Linking CXX static library ../../lib/libopencv_core.a 		/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: ../../lib/libopencv_core.a(hal_internal.cpp.o) has no symbols 		/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: ../../lib/libopencv_core.a(opencl_clamdblas.cpp.o) has no symbols 		/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: ../../lib/libopencv_core.a(opencl_clamdfft.cpp.o) has no symbols 		/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: ../../lib/libopencv_core.a(opencl_core.cpp.o) has no symbols 		/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: ../../lib/libopencv_core.a(hal_internal.cpp.o) has no symbols 		/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: ../../lib/libopencv_core.a(opencl_clamdblas.cpp.o) has no symbols 		/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: ../../lib/libopencv_core.a(opencl_clamdfft.cpp.o) has no symbols 		/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: ../../lib/libopencv_core.a(opencl_core.cpp.o) has no symbols

    我在OpenCV官网上也看到别人遇到此种情况,尚未有回复。

    https://answers.opencv.org/question/104758/has-no-symbol-error-on-mac/

  3. 手动编译OpenCV

    我们使用OpenCV的源码来编译OpenCV,同样我们需要cmake工具,使用brew install cmake进行安装。

    安装Apache Ant工具,brew install ant

    从OpenCV官网上下载 OpenCV的源码 Sources,我下载的是opencv-4.1.1.zip。

    我们将它解压,得到源码文件。

    进入到opencv源码目录 cd /Users/xxx/Desktop/opencv-4.1.1

    我们在该目录下创建一个build文件夹用于存放编译后的文件mkdir build

    进入到build目录cd build

    在此目录下,我们配置cmake的编译参数cmake -DBUILD_SHARED_LIBS=OFF -D CMAKE_INSTALL_PREFIX=/Users/xxx/Applications/opencv-4.1.1 ..

    PS:

    CMAKE_INSTALL_PREFIX 指的是编译完成后安装的路径前缀,我们会把OpenCV安装到此目录下。

    -DBUILD_SHARED_LIBS=OFF 指的是OpenCV作为一组静态库构建,不去动态依赖其它库,而是包含全部代码的编译。

    我们可以使用brew info opencv查看OpenCV的依赖库,如果-DBUILD_SHARED_LIBS=ON 则OpenCV会依赖已经存在的库来进行编译,可能会产生问题。

    构建完成后我们开始进行安装,使用make -j6来进行任务。

    PS:-j6 指的是并行6个任务来进行构建,当然也可以 -j5 五个并行任务等等。

    检查cmake的输出并确保java是“待构建”模块之一。如果不是的话,很可能你缺少了一个依赖关系您应该通过查看cmake输出中未找到的与java相关的工具并安装它们来进行故障排除。

    完成后,我们使用sudo make install完成最后的安装任务。

    这样,在我们上面提到的安装目录里,就会找到OpenCV的安装文件,当然,也能找到Java接口部分的文件。

    位于 ${CMAKE_INSTALL_PREFIX} /share/java/opencv4文件夹下,如图:

    upload successful

Linux环境下的OpenCV环境搭建

Linux环境下不再过多叙述,有需要的可以查看如下相关文章。

installing-opencv-for-java

文章内也包含Windows和Mac的安装教程。

OpenCV的使用

这儿我们先来了解下OpenCV的简单使用,后面在深入了解。

基础使用

先来了解下OpenCV的基本类Mat,它是一个图像的数据格式矩阵。用来存储图像的数据结构。

我们知道图像是由R (Red)、 G (Green) 、 B (Blue) 三原色构成,那Mat存储这三原色数据,会是一个三维数组?

其实不是的,Mat中是使用二维数组存储图像数据的,如何存储呢?如下图:

upload successful

可以看到Mat彩色图像的存储形式是三列当做一列,由 BGR 三个通道,存储在一个平面内,这儿彩色图的一个像素会占用3个字节。

对于灰度图,由于没有颜色要求,因此Mat的存储灰度图的格式和彩色图略有些不同,如下图:

upload successful

可以看到灰度图的一个像素在Mat中会占用1个字节。

我们使用OpenCV里的 Imgcodecs读取一张红色图片转为Mat,然后进行输出,如下:

1
2
3
4
5
6
7
public static void main(String[] args) throws Exception{
addLibraryDir(vmOptions);
System.loadLibrary(opencvLibName);
Mat mat = Imgcodecs.imread("C:\\Users\\DELL-3020\\Desktop\\test12.jpg");
System.out.println(mat.dump());
Color color = new Color(250,0, 0);
}

upload successful

upload successful

我们dump后可以看到Mat的数据内容。

Scalar是OpenCV里的可认为是颜色的向量类,它最多可以有四个入参。

1
Scalar scalar = new Scalar(255,0,0,0);

它的参数分别表示该颜色向量的 B G R 和 透明度,上面我们就创建了一个纯蓝色透明度为0(不透明)的颜色向量。

我们使用setTo方法,可以将原来Mat图的红色全部替换为蓝色,如下:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws Exception{
addLibraryDir(vmOptions);
System.loadLibrary(opencvLibName);
Mat mat = Imgcodecs.imread("C:\\Users\\DELL-3020\\Desktop\\test13.jpg");
Scalar scalar = new Scalar(255,0,0,0);
mat.setTo(scalar);
System.out.println(mat.dump());
Imgcodecs.imwrite("C:\\Users\\DELL-3020\\Desktop\\test15.jpg",mat);
Color color = new Color(0,0, 255 );
}

我们会生成一张蓝色图片。

人脸检测

CascadeClassifier级联分类器是OpenCV里进行图片对象识别的检测器。

一个分类器的生成: 用一个对象的几百个样本(或者更多)作为正面例子,需要将它们缩放到相同大小;负面例子样本可用任意多张(非正面例子)和正面例子相同大小的图片即可。用它们完成分类器的训练。

而对于级联分类器,则是由若干个分类器组成,它们占用不同的权重组成,比如人脸级联分类器,要检测人脸,则需要有眼睛部分,则眼睛分类器可以作为人脸级联分类器的一部分。

我们训练特定样本后,可以得到一个分类器文件(xml),加载这个xml文件后,我们可以对一些测试样本进行检测,看它是否属于目标样本。

关于训练的内容我们后面在聊,现在OpenCV内置了一些分类器文件例子,我们来看下,如下图:

upload successful

upload successful

可以看到 opencv\build\etc文件夹下有haarcascadeslbpcascades两种模式的分类器,我们以Haar分类器来看。

可以看到它下面的一些分类器文件demo,根据英文名称还是比较好确认它们是对于哪种类型图片进行识别的。

根据以上,我们来看一个寻找图片人脸并进行裁剪的例子。

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
@Slf4j
public class FaceDetector {
/**
* 人脸识别xml文件地址
*/
public static final String CASCADE_FACE_FILENAME = "D:\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt.xml";
/**
* opencv 安装的版本号
*/
public static final String opencvLibName = "opencv_java411";
/**
* VM Options
*/
public static final String vmOptions = "D:/opencv/build/java/x64";
/**
* 人脸裁剪
* @param sourceImage
* @param targetFilePath
* @return
*/
public static List<String> getFaceImages(String sourceImage,String targetFilePath){
File file = new File(sourceImage);
if(!file.exists()){
throw new RuntimeException("文件不存在!!");
}
CascadeClassifier faceDetector = new CascadeClassifier(CASCADE_FACE_FILENAME);
if(faceDetector.empty()){
throw new RuntimeException("处理文件时发生异常!");
}
Mat image = Imgcodecs.imread(sourceImage);
MatOfRect faceDetections = new MatOfRect();
// 进行人脸检测
faceDetector.detectMultiScale(image, faceDetections);

Rect[] rects = faceDetections.toArray();
if(rects.length <=0 ){
throw new RuntimeException("原图片上未检测到人脸!!");
}
log.info("检测到人脸数量:{}",rects.length);
List<String> result = new ArrayList<>();
int i = 0;
for (Rect rect : rects) {
// 进行图片裁剪
Mat mat = new Mat(image,new Rect(rect.x, rect.y,rect.width,rect.height));
// 输出人脸图片
String outFilePath = targetFilePath +"/"+ i+".jpg";
Imgcodecs.imwrite(outFilePath, mat);
i++;
}
return result;
}

public static void main(String[] args) throws Exception{
addLibraryDir(vmOptions);
System.loadLibrary(opencvLibName);
getFaceImages("C:/Users/DELL-3020/Desktop/test.jpg","C:\\Users\\DELL-3020\\Desktop");
}
}

我们寻找一张照片(包含人脸),可以看到成功生成人脸图。

upload successful

其检测裁剪逻辑主要如下:

  • 级联分类器加载样本分类器文件haarcascade_frontalface_alt.xml,检测该文件的正确性!faceDetector.empty()
  • 将待检测图片读为MatImgcodecs.imread
  • 进行人脸检测faceDetector.detectMultiScale,该方法除了传入Mat外,还要传入一个MatOfRect,它是一个Rect数组,用来存放识别返回的”感兴趣”的区域(人脸区域);
  • “感兴趣”的区域可以有多个,如果一个也没有,我们就可以认为这张待检测图片上不存在人脸;
  • 如果有的话,我们可以根据矩形区域裁剪得到人脸。

关于这部分我们先介绍到这儿,有兴趣的可以了解下例子文件里的其他级联分类器。

总结

上面我们主要说了OpenCV的安装、开发环境的配置等内容。

对于OpenCV的安装,我们根据一些文档手册,安装还是比较简单的;开发环境的配置方面,由于OpenCV主要以C++编写、开发和使用,因此应用在Java上还是不那么方便的(需要加载原生Lib库)。

对于OpenCV的使用,这儿说的比较少,只是简单的介绍了它的一些使用,其原理也未作深入分析。

后面我们继续来看下OpenCV的一些有意思的地方(图片训练、图片识别等)。




-------------文章结束啦 ~\(≧▽≦)/~ 感谢您的阅读-------------

SakuraTears wechat
扫一扫关注我的公众号
您的支持就是我创作的动力!
0%