1. 背景
实验室的项目中有一个需求是:用户在前端界面的一张图片上勾勒出自己感兴趣的区域,后台根据该区域的顶点坐标生成一个mask。实际场景如下图所示。
2. 方法
目前已找到两种方式实现上述需求:
- 使用OpenCV提供的API;
- 利用OpenCV填充的思想(漫水填充法)自己写代码实现。
2.1 使用OpenCV提供的API实现
2.1.1 依赖文件下载
在使用OpenCV提供的API之前,需要事先配置好环境。先从opencv官网上下载所需的版本,我是在windows平台下进行开发的,所以直接下载的.exe文件(下载速度很慢且不稳地,可能需要科学上网),如果是在Linux上进行开发,可能需要下载源码进行编译。下载完成后运行,将文件解压到合适的目录下即可。对于Java开发,只需要找到解压目录下的这些文件即可。
2.1.1 IDE环境配置
我本人使用的是eclipse进行的开发,以下是eclipse中的配置方式。
首先在工程目录下新建一个“lib”文件夹,将之前解压的“opencv-412.jar”复制到该文件夹下。添加完成后,选中该jar包并点击右键,选择“Build path” —> “Add to Build Path”,将该jar包添加到项目中。
然后右键刚添加的jar包,选择"Build Path" --> “Cofigure Build Path”。
在弹出的对话框中双击“Native library location”,在“location path”输入之前解压的目录下的x64文件的路径,因为该路径下含有opencv的动态库文件opencv-xxx.dll,Linux下编译完成后是opencv-xxx.so文件,java程序将通过JNI去调用opencv C++的动态库中的方法去实现相关功能。
点击"ok"和apply即完成了以上配置的过程。
2.1.2 代码
opencv中完成区域填充的关键函数是fillpoly函数,其实现填充的原理是“漫水填充法”。废话不多说,直接上代码。
/**
* 通过 OpenCV 创建一张图的mask
* @author tong
*/
public class CreateMaskByOpenCV {
/**
* 创建一个掩膜
* @param width: 图片的宽度
* @param height: 图片的高度
* @param filePath: 文件保存的路径
* @param type: 文件的类型
* @param points: 轮廓的顶点
* @throws IOException
*/
public static void create(int width, int height, String filePath, String type, List<Point> points) throws IOException {
// 对输入的点进行预处理
List<org.opencv.core.Point> list = new ArrayList<>();
for (Point p : points) {
list.add(new org.opencv.core.Point(p.getxAxis(), p.getyAxis()));
}
// 创建掩膜区域
List<MatOfPoint> maskArea = new ArrayList<>();
MatOfPoint maskPoints = new MatOfPoint();
maskPoints.fromList(list);
maskArea.add(maskPoints);
// 构建掩膜
Mat mask = new Mat(new Size(width, height), CvType.CV_8UC3, new Scalar(0, 0, 0));
Imgproc.fillPoly(mask, maskArea, new Scalar(255, 255, 255));
// 保存图片
imWrite(filePath, mask, type);
}
/**
* 保存图片
* @param filePath
* @param mat
* @return
*/
private static boolean imWrite(String filePath, Mat mat, String type) {
FileOutputStream fos = null;
try {
// 预处理
File file = new File(filePath);
if (file.exists()) file.delete();
file.createNewFile();
MatOfByte matOfByte = new MatOfByte();
Imgcodecs.imencode("." + type, mat, matOfByte);
matOfByte.toArray();
// 将字节列表转为字节数组
byte[] bytes = matOfByte.toArray();
// 写文件
fos = new FileOutputStream(new File(filePath));
fos.write(bytes);
// 返回结果
return true;
} catch (IOException e) {
System.out.println("图片保存失败: " + e.getMessage());
} finally {
try {
fos.close();
} catch (IOException e) {
System.out.println("图片保存失败: " + e.getMessage());
}
}
return false;
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
String filePath = "E:/projects/Java/json/智能配置文件/海康设备1_IPCamera1/mask1.bmp";
List<Point> points = new ArrayList<>();
points.add(new Point(334, 362));
points.add(new Point(906, 300));
points.add(new Point(934, 309));
points.add(new Point(875, 1075));
points.add(new Point(696, 1075));
points.add(new Point(686, 921)