Java如何高效使用OpenCV图像处理库

 更新时间:2024年03月04日 10:37:35   作者:qq_41983414  
OpenCV是一个开源的计算机视觉库,它提供了一系列丰富的图像处理和计算机视觉算法,包括图像读取、显示、滤波、特征检测、目标跟踪等功能,这篇文章主要给大家介绍了关于Java如何高效使用OpenCV图像处理库的相关资料,需要的朋友可以参考下

前言

Java中使用OpenCV图像处理库,是通过JNI + 动态链接库的方式进行库函数调用的。因此会产生多次native函数调用,而JNI调用会产生额外的性能开销,这将导致图像处理的速度急剧减慢。下面我将演示几个常见的示例:

一、遍历获取图像所有像素的RGB值

错误示例:

 public static void processMat(Mat mat){
    byte[] rgb = new byte[3];
    for(int i = 0;i < mat.rows();i++){
        for(int j = 0;j < mat.cols();j++){
            mat.get(i,j,rgb);
            int r = (rgb[2] & 0xff);
            int g = (rgb[1] & 0xff);
            int b = (rgb[0] & 0xff);
            //处理像素RGB值
                    
        }
    }
}

上面的代码看似十分符合人类的逻辑思维,意图简洁明了,但实际运行的效率时十分低的。测试代码对电脑全屏截图(分辨率1500*1000)后,调用processMat()函数的运行时间如下:

遍历一张全屏截图居然要2秒钟! 这还玩个P OpenCV啊。

实际上,看似简单的循环遍历代码背后,隐藏着无数次native函数调用,而这些调用存在额外性能开销,例如:查找dll函数入口地址表,Java到C数据类型的内存存储格式转换,参数传递,返回等等。

代码优化

上面的代码中,光是调用Mat.rows()和Mat.cols()就有150万次。经过测试,光执行这样的空循环(1500 * 1000),就要耗时35ms

for(int i = 0;i < mat.rows();i++){
    for(int j = 0;j < mat.cols();j++){
    }
}

显然,rows()和cols()函数属于重复执行相同的功能了。那么就把它们挪到循环外面只执行一次。

int rows = mat.rows();
int cols = mat.cols();
for(int i = 0;i < rows;i++){
    for(int j = 0;j < cols;j++){
    }
}

优化后,执行速度降低到了2ms

还没完,现在只把代码的运行速度提升了几十毫秒,真正的耗时大头在获取每一个像素的RGB值Mat.get()函数上。

遍历图像时,如果我们每次循环都去调用dll库获取一个图像像素点,那么总共150万个像素点就要调用150万次native函数,每次却只获得一个像素的RGB值,这也太低效了吧。

解决方案已经显而易见了,那就是一次性获取所有像素的RGB值数组到Java中,这样只需要一次数据类型转换开销,效率大大提升。

public static void highSpeed(Mat mat){
    int rows = mat.rows();
    int cols = mat.cols();
    int channels = mat.channels();
    //像素数组大小: 行数 * 列数 * 颜色通道数
    byte[] pixels = new byte[rows * cols * channels];
    //通过一次native调用获取整个图片的像素数组
    mat.get(0,0,pixels);
    //遍历像素数组
    int inner = cols * channels;
    for(int i = 0; i < rows; i++){
        for(int j = 0; j < inner; j += channels){
            int index = i * inner + j;
            int r = (pixels[index + 2] & 0xff);
            int g = (pixels[index + 1] & 0xff);
            int b = (pixels[index] & 0xff);
            //处理RGB值
            
        }
    }
}

使用以下测试代码进行测试:

太好了,150万个像素RGB值获取只用了5毫秒

二、高效BufferedImage和Mat对象互相转换

BufferedImage时Java中提供的最常用的带缓冲图像处理对象,不仅可以直接对图像的像素数组进行操作,还能使用此类封装的函数对图像进行裁剪,复制,缩放,颜色类型转换和绘画等操作。

此外,Robot类提供的屏幕截图函数返回的也是BufferedImage对象。

提升效率的原理和上面一样,就是将BufferedImage的图像像素数组一次性赋值给OpenCV的Mat对象,千万不要循环一个个获取再赋值!

public static Mat toMat(BufferedImage bi) {
   Mat mat = new Mat(bi.getHeight(), bi.getWidth(), CvType.CV_8UC3);
   mat.put(0, 0, ((DataBufferByte) bi.getRaster().getDataBuffer()).getData());
   return mat;
}
 
public static BufferedImage toBufferedImage(Mat mat){
    int type = BufferedImage.TYPE_BYTE_GRAY;
    if (mat.channels() > 1) {
    	//注意OpenCV的颜色格式是BGR,所以BufferedImage格式也设为BGR
        type = BufferedImage.TYPE_3BYTE_BGR;
    }
    BufferedImage image = new BufferedImage(mat.cols(), mat.rows(), type);
    mat.get(0, 0, ((DataBufferByte)image.getRaster().getDataBuffer()).getData());
    return image;
}

这里有个小细节,OpenCV中处理的彩色图像默认格式是BGR格式,但我们用Java从屏幕截图或文件中获取图片时得到的图片格式都是RGB格式,为了保证转换后颜色的正确性,BufferedImage必须先转为BGR格式。

//原始BufferedImage图像
BufferedImage image = 。。。;
//转换成RGB格式
BufferedImage rgb = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null).filter(image, rgb);

三、高效从屏幕截图和图片文件中获取Mat对象

//配合上面的 toMat() 使用: OpenCV.toMat(screenShot(0,0,100,100));
public static BufferedImage screenshot(int x,int y,int width,int height){
    BufferedImage image = robot.createScreenCapture(new Rectangle(x,y,width,height));
    //转换成RGB格式
    BufferedImage rgb = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
    new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null).filter(image, rgb);
    return rgb;
}
//配合上面的 toMat() 使用: OpenCV.toMat(readImageFile(""));
public static BufferedImage readImageFile(String path){
    try {
        BufferedImage image =  ImageIO.read(new BufferedInputStream(new FileInputStream(path)));
        //转换成RGB格式
        BufferedImage rgb = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
        new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null).filter(image, rgb);
        return rgb;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

总结

过于抽象的编程让我们的开发效率提高了,但同时使得函数失去了透明性,开发者根本不知道,也根本不用去关心底层函数的实现原理是什么,能跑就完了。例如Python语言,原生循环的运行效率非常低,因为每次循环语句都要被虚拟机动态编译再运行,为了高效处理大批量数据,py提供了Numpy库。在Numpy中进行向量化操作时,实际上是在进行一些底层的操作,这些操作是由C语言实现的,因此它们的执行效率非常高。而我们要做的,就是在底层调用和上层开发效率之间取得一个平衡点,兼顾代码的可维护性和运行效率。

到此这篇关于Java如何高效使用OpenCV图像处理库的文章就介绍到这了,更多相关Java高效使用OpenCV内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java关键字static学习心得

    java关键字static学习心得

    本篇文章给大家分享一篇关于java关键字static的学习心得,有这方面需要的朋友学习下吧。
    2018-01-01
  • Java之如何截取视频第一帧

    Java之如何截取视频第一帧

    这篇文章主要介绍了Java之如何截取视频第一帧问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • MyEclipse8.6首次运行maven项目图标上没有小M的标识怎么解决

    MyEclipse8.6首次运行maven项目图标上没有小M的标识怎么解决

    myeclipse8.6导入maven项目后识别为普通java项目,即项目图标上没有小M的标识。这时是无法直接运行的,怎么解决这一问题呢?下面小编给大家带来了解决方案,需要的朋友参考下吧
    2016-11-11
  • LeetCode 动态规划之矩阵区域和详情

    LeetCode 动态规划之矩阵区域和详情

    这篇文章主要介绍了LeetCode 动态规划之矩阵区域和详情,文章基于Java的相关资料展开对LeetCode 动态规划的详细介绍,需要的小伙伴可以参考一下
    2022-04-04
  • SpringBoot整合Redis使用@Cacheable和RedisTemplate

    SpringBoot整合Redis使用@Cacheable和RedisTemplate

    本文主要介绍了SpringBoot整合Redis使用@Cacheable和RedisTemplate,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • 数据定位在java购物车系统中的应用

    数据定位在java购物车系统中的应用

    实现"加入购物车"功能,数据定位至关重要,它通过用户ID和商品ID等标识符实现快速查询和数据一致性,主键、外键和联合索引等数据库技术,以及Redis缓存和并发控制策略如乐观锁或分布式锁,共同保障了购物车系统的查询效率和数据安全,这些机制对高并发和大数据量的场景尤为重要
    2024-10-10
  • 深入解读 Spring Boot 生态之功能、组件与优势

    深入解读 Spring Boot 生态之功能、组件与优势

    本文将深入剖析 Spring Boot 的生态体系,包括其核心功能、生态组件以及在不同场景中的应用,并附上一张 Spring Boot 生态系统图,帮助开发者更直观地理解 Spring Boot 的强大之处,感兴趣的朋友一起看看吧
    2024-11-11
  • IDEA工程运行时总是报xx程序包不存在实际上包已导入(问题分析及解决方案)

    IDEA工程运行时总是报xx程序包不存在实际上包已导入(问题分析及解决方案)

    这篇文章主要介绍了IDEA工程运行时,总是报xx程序包不存在,实际上包已导入,本文给大家分享问题分析及解决方案,通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2020-08-08
  • java多线程读取多个文件的方法

    java多线程读取多个文件的方法

    这篇文章主要为大家详细介绍了java多线程读取多个文件的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-08-08
  • JedisPool资源池优化方法

    JedisPool资源池优化方法

    这篇文章主要介绍了JedisPool资源池优化方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03

最新评论