简述

在配置完OpenCV以及尝试过官方的例子之后,就开始学习一些需要用到的对图片的操作了.
因为对项目的功能实现有比较明确的需要使用的方法以及操作,所以就从这些需要的功能开始学习,在过程中再对其他的需要了解的知识进行补充吧.大致的学习范围是:

  1. 对Mat类的了解和操作(在之后会学习)
  2. 直方图以及直方图均衡化
  3. 图像的降噪处理(主要是高斯滤波器)
  4. Canny边缘检测函数
  5. Sobel算子检测方向性的边缘
  6. 霍夫变换(霍夫概率变换)提取直线
  7. 图像细化(Zhang快速并行算法)

以上是需要学习的主要的功能,在理解上由于数学功力欠缺,只有对函数的很浅的理解和认识,在写博客的过程中也不会有太多的数学出现,主要还是以能够使用为主.

官方帮助文档

OpenCV的官方也提供了一份Java的API的文档,但是由于版本问题,在网上能够找到的大部分资料都是OpenCV2.*版本的函数,在3.0之后函数名称和某些函数的参数也发生了变化 ,故官方的文档在实际使用的时候并不是很好用,但是用来查看函数的参数和操作上还是有很大的帮助.
OpenCV官方3.0.0文档

主要使用到的Package的简介

图片
这是文档中所包含的全部的包名,主要使用到了以下的几个包:

  • org.opencv.core:里面包含了Core,CvType,Mat三个使用到的类.
  • org.opencv.imgcodes:主要是对于图片的读写,均在此包中
  • org.opencv.imgproc:是使用到的频率最高的也是我最主要使用的包,下面将提到的大部分的操作的函数均在此包中
  • org.opencv.videoio:主要包含了对于视频的操作

开始-直方图均衡化

直方图:

  • 直方图是图像中像素强度分布的图形学表达式
  • 它统计了每一个强度值所具有的像素个数

直方图均衡化:

是通过拉伸像素强度分布范围来增强图像对比度的一种方法.

均衡化函数:equalizeHist()

public static void equalizeHist(Mat src,Mat dst)
参数src为图像源,dst为转换之后的图像.**注:**函数只接受灰度图像,在图像处理之前要进行转换.
例程:

1
2
3
4
5
6
7
8
9
//加载图片源
Mat src = Imgcodecs.imread("img/fengjing.jpg");
//创建一个用来接受的Mat
Mat mat = new Mat(src.rows(),src.cols(),src.type());
//由于equalizeHist方法只能接受8UC1类型的图片,所以需要将图片转换成GRAY格式,
Imgproc.cvtColor(mat,mat,Imgproc.COLOR_RGB2GRAY);
Imgproc.cvtColor(src,src,Imgproc.COLOR_RGB2GRAY);
//应用直方图均衡化
Imgproc.equalizeHist(src,mat);

参考教程

OpenCV 2.3.2 documentation » OpenCV 教程 » imgproc 模块. 图像处理 »直方图均衡化


图像的降噪处理-高斯滤波器

简介

高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。通俗的讲,高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。

高斯滤波器函数

public static void GaussianBlur(Mat src,Mat dst,Size ksize,double sigmaX,double sigmaY,int borderType)
参数说明:

  • Mat src:输入图像
  • Mat dst:输出图像
  • Size ksize:高斯内核的大小
  • double sigmaX:高斯核函数在X方向上的标准偏差
  • double sigmaY:高斯核函数在Y方向上的标准偏差,若不设置则与X方向上相同
  • int borderType:推断图像外部像素的某种便捷模式,一般不需要更改,默认值即可.
    测试例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //加载图片源
    Mat src = Imgcodecs.imread("img/test.jpg", Imgproc.COLOR_RGB2GRAY);
    //创建一个用来接受的Mat
    Mat mat = new Mat(src.rows(),src.cols(),src.type());
    //由于equalizeHist方法只能接受8UC1类型的图片,所以需要将图片转换成GRAY格式,
    Imgproc.cvtColor(mat,mat,Imgproc.COLOR_RGB2GRAY);Imgproc.cvtColor(src,src,
    Imgproc.COLOR_RGB2GRAY);
    /*
    * 首先进行高斯滤波处理
    * */
    Imgproc.GaussianBlur(src,mat,new Size(11,11),0);

参考教程:
opencv学习(二十)之高斯滤波GaussianBlur()
图像处理—高斯滤波


Mat类的操作

Mat类的理解

对于Mat的理解,主要是对于该类内部是如何存储图片信息的理解.详细可以参照博客:OpenCV学习之路(二)——Mat对象

遍历Mat元素

对于Mat来说最主要的就是遍历其中的每个像素点然后进行操作了.Mat也提供了相应的方法来获取每个位置的元素.
public double[] get(int row,int col)
可以看到取出来的值是double[]数组的形式,我的理解是根据图片的类型的不同,数组中的信息也会不同,所以在操作的时候还是要根据当前操作的图片类型来进行区分.
遍历的方法就十分简单了,在下面贴出来:

1
2
3
4
5
6
for (int i=0; i<size.height;i++){    
for(int j=0;j<size.width;j++){
double[] data = mat.get(i,j);
//在这里对data数组进行操作
}
}

参考文章

Using get() and put() to access pixel values in JAVA

Canny边缘检测

简介

Canny 边缘检测算子是John F. Canny于 1986 年开发出来的一个多级边缘检测算法。更为重要的是 Canny 创立了边缘检测计(Computational theory of edge detection)解释这项技术如何工作。
Canny 的目标是找到一个最优的边缘检测算法,最优边缘检测的含义是:

  • 好的检测 - 算法能够尽可能多地标识出图像中的实际边缘
  • 好的定位 - 标识出的边缘要尽可能与实际图像中的实际边缘尽可能接近
  • 最小响应 - 图像中的边缘只能标识一次,并且可能存在的图像噪声不应标识为边缘.

Canny检测函数:

public static void Canny(Mat image,Mat edges,double threshold1,double threshold2)
参数说明:

  • Mat image:输入的图像,为单通道的灰度图像
  • Mat edges:输出的图像,为单通道黑白图像
  • double threshold1,double threshold2:这两个参数表示阈值,这二个阈值中当中的小阈值用来控制边缘连接,大的阈值用来控制强边缘的初始分割.
    使用方法还是十分简单的,在Java中函数的使用如下:
    Imgproc.Canny(src,mat,100,130);

参考博客

【OpenCV入门指南】第三篇Canny边缘检测

Sobel算子

关于sobel算子的详细介绍可以参照博客: 【opencv2】Sobel算子原理与实现以及Sobel算子及C++实现.具体的细节就不再赘述.
**注:**sobel算子对于垂直和水平方向的的边缘检测效果较好,而对于45°以及135°这样梯度的边缘检测来说,推荐先将图片旋转,使得要检测的边缘在竖直或者水平方向上效果会更好.

Sobel算子函数

public static void Sobel(Mat src,Mat dst,int ddepth,int dx,int dy,int ksize,double scale,double delta)
参数说明:

  • Mat src:输入的图像
  • Mat dst:输出的图像,需要和输入图像有相同的尺寸类型
  • int ddepth:输出图像的深度,默认可填-1
  • int dx,int dy:x,y方向上的差分阶数.
  • int ksize:Sobel核的大小,必须取1,3,5,7.
  • double scale:计算导数时的缩放因子,默认为1
  • double delta:可选的delta值.默认为0

使用方法

Imgproc.Sobel(src,mat,-1,1,0);

参考博客

【OpenCV入门教程之十二】OpenCV边缘检测:Canny算子,Sobel算子,Laplace算子,Scharr滤波器合辑
【opencv2】Sobel算子原理与实现
Sobel算子及C++实现

霍夫变换提取直线

简介

霍夫变换(Hough Transform)是图像处理中的一种特征提取技术,该过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换结果。

霍夫变换于1962年由PaulHough首次提出,最初的Hough变换是设计用来检测直线和曲线,起初的方法要求知道物体边界线的解析方程,但不需要有关区域位置的先验知识。这种方法的一个突出优点是分割结果的Robustness,即对数据的不完全或噪声不是非常敏感。然而,要获得描述边界的解析表达常常是不可能的。 后于1972年由Richard Duda & Peter Hart推广使用,经典霍夫变换用来检测图像中的直线,后来霍夫变换扩展到任意形状物体的识别,多为圆和椭圆。霍夫变换运用两个坐标空间之间的变换将在一个空间中具有相同形状的曲线或直线映射到另一个坐标空间的一个点上形成峰值,从而把检测任意形状的问题转化为统计峰值问题.

Java中的霍夫变换函数

  • static void HoughLines(Mat image, Mat lines, double rho, double theta, int threshold)
  • static void HoughLinesP(Mat image, Mat lines, double rho, double theta, int threshold)
  • static void HoughCircles(Mat image, Mat circles, int method, double dp, double minDist)
    一共有三种h函数,分别是标准霍夫变换,累计概率霍夫变换以及霍夫圆变换.
    这里只介绍一下第二个累计概率霍夫变换的函数.
    参数说明:
  • Mat image:表示输入的图像
  • Mat lines:表示经过检测之后得到的直线的矢量,存储的信息为x1,y1,x2,y2四个点的坐标.
  • double rho:直线搜索时的进步单位半径
  • double theta:直线搜索时进步单位角度
  • int threshold:累加平面的阈值参数,只有大于该阈值的结果才能被检测通过.

Java使用

Imgproc.HoughLinesP(src,mat,1,Math.acos(-1)/45,230);

在图片上将所有的直线都画出来:

1
2
3
4
5
6
for (int i=0; i<size.height;i++){            
for(int j=0;j<size.width;j++){
double[] data = mat.get(i,j);
Imgproc.line(dstImage, new Point(data[0],data[1]),new Point(data[2],data[3]),new Scalar(255,0,0),1);
}
}

参考博客

【OpenCV入门教程之十四】OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑

图像细化(Zhang快速并行算法)

图像细化也称为图像的骨架化,简单的说就是把一条直线的轮廓变细,只剩下一条单位像素的直线.
算法的核心解释如下:

  • 首先定义一个矩阵:
  • 然后对于P1这个点进行讨论:
  1. 2 <= N(P1) <= 6,N(x)为x的8领域中黑点(背景点)的个数
  2. A(P1) = 1,A(x)为P2-P8之间按序前后分别为0、1的对数
  3. P2*P4*P6 = 0
  4. P4*P6*P8 = 0
    满足上述条件的P1点即可删去.
  • 重复上述步骤直到没有可删除的点为止.

算法的Java实现:

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
public void thinImage(Mat src, Mat thin, Integer maxIterations){        
maxIterations = -1;
int width = src.cols();
int height = src.rows();
thin = src.clone();
int count = 0;
while (true){
count++;
if(maxIterations != -1 && count > maxIterations)//限制迭代次数
break;
Stack<Point> flag = new Stack<Point>();//存放要删除的点的信息
for(int i=0; i < height; i++){
Mat p = thin.row(i);
for(int j=0; j < width; j++){
double[] zero = {0};
double[] p1 = p.get(i,j);//p1里面只有一个元素 应该就是无符号整形的灰度值
double[] p2 = (i==0)?zero:thin.get(i-1,j);
double[] p3 = (i==0 || j==width-1)?zero:thin.get(i-1,j+1);
double[] p4 = (j == width - 1) ? zero : thin.get(i, j + 1);
double[] p5 = (i==height-1 || j==width-1)?zero:thin.get(i+1,j+1);
double[] p6 = (i == height - 1) ? zero : thin.get(i + 1, j);
double[] p7 = (i == height - 1 || j == 0) ? zero : thin.get(i + 1, j - 1);
double[] p8 = (j==0)?zero:thin.get(i,j-1);
double[] p9 = (i==0 || j==0)?zero:thin.get(i-1,j-1);
if((p2[0]+p3[0]+p4[0]+p5[0]+p6[0]+p7[0]+p8[0]+p9[0])>=2 && (p2[0]+p3[0]+p4[0]+p5[0]+p6[0]+p7[0]+p8[0]+p9[0])<=6){
int ap = 0;
if(p2[0] == 0 && p3[0]== 1) ++ap;
if(p3[0] == 0 && p4[0]== 1) ++ap;
if(p4[0] == 0 && p5[0]== 1) ++ap;
if(p5[0] == 0 && p6[0]== 1) ++ap;
if(p6[0] == 0 && p7[0]== 1) ++ap;
if(p7[0] == 0 && p8[0]== 1) ++ap;
if(p8[0] == 0 && p9[0]== 1) ++ap;
if(p9[0] == 0 && p2[0]== 1) ++ap;
if(ap == 1){
if(p2[0]*p4[0]*p6[0] == 0){
if(p4[0]*p6[0]*p8[0] == 0){
//标记
flag.push(new Point(i,j));
}
}
}
}
}
}
//删除标记的点
Iterator iterator = flag.iterator();
while (iterator.hasNext()){
byte[] zero = {0};
Point p = (Point) iterator.next();
thin.put((int)p.x,(int)p.y,zero);
}
//判断是否为空,若否则清空继续
if(flag.empty()) break;
else flag.clear();
/*
* 重复一遍
* */
for(int i=0; i < height; i++){
Mat p = thin.row(i);
for(int j=0; j < width; j++){
double[] zero = {0};
double[] p1 = p.get(i,j);//p1里面只有一个元素 应该就是无符号整形的灰度值
double[] p2 = (i==0)?zero:thin.get(i-1,j);
double[] p3 = (i==0 || j==width-1)?zero:thin.get(i-1,j+1);
double[] p4 = (j == width - 1) ? zero : thin.get(i, j + 1);
double[] p5 = (i==height-1 || j==width-1)?zero:thin.get(i+1,j+1);
double[] p6 = (i == height - 1) ? zero : thin.get(i + 1, j);
double[] p7 = (i == height - 1 || j == 0) ? zero : thin.get(i + 1, j - 1);
double[] p8 = (j==0)?zero:thin.get(i,j-1);
double[] p9 = (i==0 || j==0)?zero:thin.get(i-1,j-1);
if((p2[0]+p3[0]+p4[0]+p5[0]+p6[0]+p7[0]+p8[0]+p9[0])>=2 && (p2[0]+p3[0]+p4[0]+p5[0]+p6[0]+p7[0]+p8[0]+p9[0])<=6){
int ap = 0;
if(p2[0] == 0 && p3[0]== 1) ++ap;
if(p3[0] == 0 && p4[0]== 1) ++ap;
if(p4[0] == 0 && p5[0]== 1) ++ap;
if(p5[0] == 0 && p6[0]== 1) ++ap;
if(p6[0] == 0 && p7[0]== 1) ++ap;
if(p7[0] == 0 && p8[0]== 1) ++ap;
if(p8[0] == 0 && p9[0]== 1) ++ap;
if(p9[0] == 0 && p2[0]== 1) ++ap;
if(ap == 1){
if(p2[0]*p4[0]*p6[0] == 0){
if(p4[0]*p6[0]*p8[0] == 0){
//标记
flag.push(new Point(i,j));
}
}
}
}
}
}
//删除标记的点
while (iterator.hasNext()){
byte[] zero = {0};
Point p = (Point) iterator.next();
thin.put((int)p.x,(int)p.y,zero);
}
//判断是否为空,若否则清空继续
if(flag.empty()) break;
else flag.clear();
}
}

参考博客

openCV综合运用 ———- 图像细化、线段长度、平行线距离检测

总结

到这里OpenCV的学习也告一段落了,对于OpenCV的学习还是十分的友好的,csdn上面的博客也非常的多以及详细,本人也只是在Java中再次尝试了一遍过程而已.对于算法的细节没有做太多的考究,因为现阶段只是能够使用即可.但是感觉上OpenCV对于Java的支持并不是特别的好,还是尝试使用C++来做算法的开发更加的好,而且3.0+版本的资料仍旧以英文为主,可能在一开始会在函数的使用上遇到一些困难,但是熟悉之后,和之前版本也没有太大的区别.就写到这里吧.

概述

最近有一个项目需要用到OpenCV的库来做图像处理的工作,然后就自学了一些基础的OpenCV知识,由于项目用Java语言开发,所以为了方便在OpenCV的调用上也使用了Java版的API来操作.

环境配置

JDK

JDK去官方网站下载JDK并安装即可.

IDE

IDE使用的是IDEA开发

OpenCV的安装以及导入

安装

OpenCV官网下载OpenCV的安装包,我安装了最新的OpenCV版本3.2.0,平台是Windows64位,安装完成后会在指定安装的目录下有opencv的文件夹.

IDEA配置

在IDEA的Project Structure窗口中选择Libraries选项,添加opencv目录下的jar包即可.对应opencv目录下jar包的路径为:\opencv\build\java.
opencv文件路径
IDEA配置路径
成功之后会在Libraries中出现OpenCV的Jar包
成功

运行环境配置

在IDEA右上角的Run/Debug Configurations选项中的VM options中添加如下代码:-Djava.library.path=D:\opencv\opencv\build\java\x64;D:\opencv\opencv\build\x64\vc14\bin
图示

测试代码

在IDEA中创建_Test.java_文件,测试下述代码.

    import org.opencv.core.Core;
    import org.opencv.core.CvType;
    import org.opencv.core.Mat;
    import org.opencv.core.Scalar;
    
    public class Test {
    
        static{ System.loadLibrary(Core.NATIVE_LIBRARY_NAME); }
    
        public static void main(String[] args) {
            System.out.println("Welcome to OpenCV " + Core.VERSION);
            Mat m = new Mat(5, 10, CvType.CV_8UC1, new Scalar(0));
            System.out.println("OpenCV Mat: " + m);
            Mat mr1 = m.row(1);
            mr1.setTo(new Scalar(1));
            Mat mc5 = m.col(5);
            mc5.setTo(new Scalar(5));
            System.out.println("OpenCV Mat data:\n" + m.dump());
        }
    }

显示运行结果为:
运行结果
至此,OpenCV在Java环境下的配置就结束了.

参考文章

在IntelliJ IDEA 13中配置OpenCV的Java开发环境

简述

在OpenCV配置安装完成之后,先去看了一下官方的教程和例子,Introduction to Java Development,这是官方提供的一个面对Java开发者的快速的介绍,但是其中只有两个Demo,第一个就是配置时的测试代码.这里主要来看一下第二个例子.

开始

在官方的这个介绍中还介绍了Scala以及SBT的知识,这里我并没有按照他的方式来进行第二个人脸检测例子的测试.

  • 首先我们需要一个人脸识别的配置文件,官方是在构建时自动下载的,没有使用SBT的方式的话,可以直接在Github上来下载这个配置文件lbpcascade_frontalface.xml.
  • 然后我们需要一张图片,官方的识别图片是_lena.png_,在下方贴出来:
    准备好之后就是示例代码了.
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
/** 
* Created by MisakaTang on 2017/7/5. 
* 官方的HelloOpenCV案例,做了一个人脸边缘检测的例子 
*/
public class DetectFaceDemo {    
    static{System.loadLibrary(Core.NATIVE_LIBRARY_NAME);}    
    public static void main(String[] args) {        
        new DetectFaceDemo().run();    
    }    
    public void run(){        
        System.out.println("\nRunning DetectFaceDemo");        //从图片中创建一个面部检测        
        /*        
        * 官方文档使用的opencv为2.4版本,所以没有采用示例中读取图片时的方法        
        * 文件放在工程目录下无法正常读取 会报空指针异常        
        * 解决办法是将文件移出工程文件(放在了E:/根目录下)        
        * 在3.0以后的版本取消了Highgui类,对于图片的读取都使用了Imgcodes类来进行处理        
        * 
        */        
        CascadeClassifier faceDetector = new CascadeClassifier("E:/lbpcascade_frontalface.xml");        
        Mat image = Imgcodecs.imread("E:/lena.png");        
        /*        
        * 下方为官方教程中的代码        
        * 
        */
        //Create a face detector from the cascade file in the resources
        //directory.
        //CascadeClassifier faceDetector = new CascadeClassifier(getClass().getResource("/lbpcascade_frontalface.xml").getPath());
        //Mat image = Highgui.imread(getClass().getResource("/lena.png").getPath());        
        MatOfRect faceDetetions = new MatOfRect();        
        faceDetector.detectMultiScale(image,faceDetetions);        
        System.out.println(faceDetetions.toArray().length);        
        //画图        
        for (Rect rect : faceDetetions.toArray()){            
            Imgproc.rectangle(image,new Point(rect.x,rect.y),new Point(rect.x+rect.width,rect.y+rect.height),new Scalar(0,255,0));        
        }        
        String filenanme = "faceDetection.png";        
        System.out.println(String.format("Writing %s", filenanme));        
        Imgcodecs.imwrite(filenanme,image);     //这里也采用了Imgcodes类来处理    
    }
}

由于我下载的OpenCV版本是3.0以上的版本,而这个Demo的版本是2.4的,所以对于图像读取的类也发生了变化,要注意开发环境的OpenCV版本.

注意:

将xml配置文件和图片放在工程目录下会报错,StackOverFlow上的解决办法是不要将这两个文件放在工程目录下,我将其放在的E:\的根目录下问题就解决了.

运行结果

在人脸的图片上显示了识别到的面部信息并用矩形标识了出来.

总结

You’re done! Now you have a sample Java application working with OpenCV, so you can start the work on your own. We wish you good luck and many years of joyful life!


这是官方示例的结尾,拿来借用一下.主要的问题出在Opencv版本导致的类的使用方式变化以及文件不能放在工程目录下这两个问题.

0%