起因
在2013年1月份发行的OpenCV 2.4.4中,对Java的支持也正式发布。同时也有支持Python。
环境配置
这里的环境配置十分简单,在这里,操作系统64位,所以选这个,将里面的opencv_java342.dll
负责到你本地下载的JDK的bin目录下,和JDK下的JRE目录下的bin目录下。
我使用的是Eclipse,在Eclipse新建Java工程后将opencv-342.jar
加入到路径中,就OK了。
这里可以验证下是否能成功运行OpenCV,代码如下:
package com.monhitul;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
public class SimpleSample {
//静态代码块定义,会在程序开始运行时先被调用初始化
static {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME); //得保证先执行该语句,用于加载库,才能调用其他操作库的语句,
} //否则会报 java.lang.UnsatisfiedLinkError: org.opencv.core.Mat.n_Mat(IIIDDDD)J 错误
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());
}
}
控制台输出是这样的:
这样就表示没问题。
基本矩阵操作
大家知道,在计算机视觉中,一幅图像其实就是一个由数字组成的矩阵,这些数字表示的是图像的像素。比如说gray-image,灰度图像,将这些数字按从0(black)到255(white)排列,数字越接近0就是越接近黑,越接近255就是越接近白,其实就是不同程度上的灰,这就是普遍的8-bit images;甚至还可以采用浮点数的方法,黑到白就是0.0到1.0。而color image,彩色图像,它的每个像素点则是由三原色(红绿蓝)结合而成的。
所以,每一个数字图像都是一个像素点矩阵,这个矩阵包含所有像素点的强度值,矩阵的列数对应图像的宽度,矩阵的行数对应图像的高度。
在OpenCV中,Mat类(Matrix的缩写,矩阵)是OpenCV用于处理图像而引入的一个封装类。Mat的type属性表示了矩阵中元素的类型以及矩阵的通道个数,它是一系列的预定义的常量,命名规则如下:
CV_<bit_depth>{U|S|F}C(<number_of_channels>)
在这里呢,bit_depth
表示图像深度,即一个像素点所占的总位数(bit),像是前面提到的灰度图像,就是8位。
U|S|F
分别代表unsigned
无符号整数,signed
有符号整数和float
浮点数。
number_of_channels
代表的是通道,单通道就是一个像素点只需一个数值表示,只能表示灰度,0为黑色;三通道就是RGB模式,把图像分为红绿蓝三个通道,可以表示彩色,全0表示黑色;四通道就是在RGB基础上加上alpha通道,表示透明度,alpha=0表示全透明。如果number_of_channels
未指明,则默认为单通道。
这样的话,我们举个例子,比如说前面的8位灰度图,那就是CV_8UC1
,这个还挺好理解的,就不多说了。
熟悉操作
我们可以回到最开始我们验证OpenCV是否正常使用的那个代码。
package com.monhitul;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Scalar;
public class SimpleSample {
//静态代码块定义,会在程序开始运行时先被调用初始化
static {
System.loadLibrary(Core.NATIVE_LIBRARY_NAME); //得保证先执行该语句,用于加载库,才能调用其他操作库的语句,
} //否则会报 java.lang.UnsatisfiedLinkError: org.opencv.core.Mat.n_Mat(IIIDDDD)J 错误
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());
}
}
猜也可以猜出这个代码大致的意思了。我们看看输出结果中Mat的结果,isCont
属性告诉我们在表示图像时,次矩阵是否使用了额外的填充,以便在某些平台上可以硬件加速。isSubmat
属性是指该矩阵是否是从另一个矩阵创建而来的,以及它是否引用了另一个矩阵的数据。
关于数据初始化的,我们还可以看看下面这个例子:
Mat image = new Mat(new Size(3,3),CvType.CV_8UC3,new Scalar(new double[] {3,6,9}));
System.out.println("OpenCV Mat image data:\n"+image.dump()+"\n");
输出结果为:
像素操作
对像素的操作可以有个很直接的方法,就是使用put(row, col, data)
方法,代码如下:
for(int i=0;i<image.rows();i++) {
for(int j=0;j<image.cols();j++) {
image.put(i, j, new byte[] {2,4,8});
}
}
在这里byte[]
数值中,1
代表通道蓝,2
代表通道绿,3
代表通道红,即顺序是{蓝,绿,黄}。
这样存在的问题也很明显,就算是一幅640×480px的图像,也有307200个像素点,如果是彩色图像则是921600个数值存在于矩阵中,这样必然造成运行时间过久。
我们这个可以看看下面这个代码,实现了过滤通道的功能:
//过滤器,将通道蓝的数值滤去,置为0
public static Mat filter(Mat image) {
Mat newImage=image;
//得到图像中的字节数
int totalBytes = (int)(newImage.total() * newImage.elemSize());
//image.total()得到像素总数,image.elemSize()得到每个元素的大小,单位byte
System.out.println("totalBytes:"+totalBytes);
byte buffer[] = new byte[totalBytes];
//get,put方法分别是将byte转换成Mat,将Mat转换成byte
for(int i=0;i<totalBytes;i++) {
if(i%3==0) { //通道蓝为3的倍数
buffer[i]=0; //置为0
}
}
newImage.put(0, 0, buffer);
System.out.println("put:"+newImage.dump());
return newImage;
}
然后在主函数中调用:
Mat src = Imgcodecs.imread("D:\\favcion.jpg"); //读入一幅图像
if(src.empty()) {
System.out.println("empty");
return;
} else { //判断确实读入
System.out.println("Notempty");
Mat newImage = filter(src); //调用过滤器
Imgcodecs.imwrite("D:\\newfavcion.jpg", newImage); //将得到新的图像写入文件中
}
我们来看看前后的对比。
明显偏黄,我们可以选择将红的滤去,是这样的:
将绿的滤去,这样的:
再看看这么一张图:
从文件中加载图像
前面代码里刚刚出现的,借助Imgcodecs.imread(name_of_file)
可以加载图像文件,而dataAddr()
方法可以判断加载图像是否正确。
就像这样:
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.core;
public Mat openFile(String fileName) throws Exeception{
Mat newImage = Imgcodecs.imread(fileName);
if(newImage.dataAddr()==0){
throw new Exception("Couldn't open file: "+fileName);
}
return newImage;
}
而将图像存为文件,前面的代码也有出现,就是使用Imgcodecs.imwrite(name_of_file)
。
结束语
也欢迎大家关注微信公众号:Monhitul
一个东学西学,学东学西的公众号,大家一起学习,一同进步。