Java如何高效使用OpenCV圖像處理庫(kù)
前言
Java中使用OpenCV圖像處理庫(kù),是通過JNI + 動(dòng)態(tài)鏈接庫(kù)的方式進(jìn)行庫(kù)函數(shù)調(diào)用的。因此會(huì)產(chǎn)生多次native函數(shù)調(diào)用,而JNI調(diào)用會(huì)產(chǎn)生額外的性能開銷,這將導(dǎo)致圖像處理的速度急劇減慢。下面我將演示幾個(gè)常見的示例:
一、遍歷獲取圖像所有像素的RGB值
錯(cuò)誤示例:
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值 } } }
上面的代碼看似十分符合人類的邏輯思維,意圖簡(jiǎn)潔明了,但實(shí)際運(yùn)行的效率時(shí)十分低的。測(cè)試代碼對(duì)電腦全屏截圖(分辨率1500*1000)后,調(diào)用processMat()函數(shù)的運(yùn)行時(shí)間如下:
遍歷一張全屏截圖居然要2秒鐘! 這還玩?zhèn)€P OpenCV啊。
實(shí)際上,看似簡(jiǎn)單的循環(huán)遍歷代碼背后,隱藏著無數(shù)次native函數(shù)調(diào)用,而這些調(diào)用存在額外性能開銷,例如:查找dll函數(shù)入口地址表,Java到C數(shù)據(jù)類型的內(nèi)存存儲(chǔ)格式轉(zhuǎn)換,參數(shù)傳遞,返回等等。
代碼優(yōu)化
上面的代碼中,光是調(diào)用Mat.rows()和Mat.cols()就有150萬次。經(jīng)過測(cè)試,光執(zhí)行這樣的空循環(huán)(1500 * 1000),就要耗時(shí)35ms
for(int i = 0;i < mat.rows();i++){ for(int j = 0;j < mat.cols();j++){ } }
顯然,rows()和cols()函數(shù)屬于重復(fù)執(zhí)行相同的功能了。那么就把它們挪到循環(huán)外面只執(zhí)行一次。
int rows = mat.rows(); int cols = mat.cols(); for(int i = 0;i < rows;i++){ for(int j = 0;j < cols;j++){ } }
優(yōu)化后,執(zhí)行速度降低到了2ms。
還沒完,現(xiàn)在只把代碼的運(yùn)行速度提升了幾十毫秒,真正的耗時(shí)大頭在獲取每一個(gè)像素的RGB值Mat.get()函數(shù)上。
遍歷圖像時(shí),如果我們每次循環(huán)都去調(diào)用dll庫(kù)獲取一個(gè)圖像像素點(diǎn),那么總共150萬個(gè)像素點(diǎn)就要調(diào)用150萬次native函數(shù),每次卻只獲得一個(gè)像素的RGB值,這也太低效了吧。
解決方案已經(jīng)顯而易見了,那就是一次性獲取所有像素的RGB值數(shù)組到Java中,這樣只需要一次數(shù)據(jù)類型轉(zhuǎn)換開銷,效率大大提升。
public static void highSpeed(Mat mat){ int rows = mat.rows(); int cols = mat.cols(); int channels = mat.channels(); //像素?cái)?shù)組大小: 行數(shù) * 列數(shù) * 顏色通道數(shù) byte[] pixels = new byte[rows * cols * channels]; //通過一次native調(diào)用獲取整個(gè)圖片的像素?cái)?shù)組 mat.get(0,0,pixels); //遍歷像素?cái)?shù)組 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值 } } }
使用以下測(cè)試代碼進(jìn)行測(cè)試:
太好了,150萬個(gè)像素RGB值獲取只用了5毫秒!
二、高效BufferedImage和Mat對(duì)象互相轉(zhuǎn)換
BufferedImage時(shí)Java中提供的最常用的帶緩沖圖像處理對(duì)象,不僅可以直接對(duì)圖像的像素?cái)?shù)組進(jìn)行操作,還能使用此類封裝的函數(shù)對(duì)圖像進(jìn)行裁剪,復(fù)制,縮放,顏色類型轉(zhuǎn)換和繪畫等操作。
此外,Robot類提供的屏幕截圖函數(shù)返回的也是BufferedImage對(duì)象。
提升效率的原理和上面一樣,就是將BufferedImage的圖像像素?cái)?shù)組一次性賦值給OpenCV的Mat對(duì)象,千萬不要循環(huán)一個(gè)個(gè)獲取再賦值!
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格式也設(shè)為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; }
這里有個(gè)小細(xì)節(jié),OpenCV中處理的彩色圖像默認(rèn)格式是BGR格式,但我們用Java從屏幕截圖或文件中獲取圖片時(shí)得到的圖片格式都是RGB格式,為了保證轉(zhuǎn)換后顏色的正確性,BufferedImage必須先轉(zhuǎn)為BGR格式。
//原始BufferedImage圖像 BufferedImage image = 。。。; //轉(zhuǎn)換成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對(duì)象
//配合上面的 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)); //轉(zhuǎn)換成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))); //轉(zhuǎn)換成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; }
總結(jié)
過于抽象的編程讓我們的開發(fā)效率提高了,但同時(shí)使得函數(shù)失去了透明性,開發(fā)者根本不知道,也根本不用去關(guān)心底層函數(shù)的實(shí)現(xiàn)原理是什么,能跑就完了。例如Python語(yǔ)言,原生循環(huán)的運(yùn)行效率非常低,因?yàn)槊看窝h(huán)語(yǔ)句都要被虛擬機(jī)動(dòng)態(tài)編譯再運(yùn)行,為了高效處理大批量數(shù)據(jù),py提供了Numpy庫(kù)。在Numpy中進(jìn)行向量化操作時(shí),實(shí)際上是在進(jìn)行一些底層的操作,這些操作是由C語(yǔ)言實(shí)現(xiàn)的,因此它們的執(zhí)行效率非常高。而我們要做的,就是在底層調(diào)用和上層開發(fā)效率之間取得一個(gè)平衡點(diǎn),兼顧代碼的可維護(hù)性和運(yùn)行效率。
到此這篇關(guān)于Java如何高效使用OpenCV圖像處理庫(kù)的文章就介紹到這了,更多相關(guān)Java高效使用OpenCV內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java關(guān)鍵字static學(xué)習(xí)心得
本篇文章給大家分享一篇關(guān)于java關(guān)鍵字static的學(xué)習(xí)心得,有這方面需要的朋友學(xué)習(xí)下吧。2018-01-01MyEclipse8.6首次運(yùn)行maven項(xiàng)目圖標(biāo)上沒有小M的標(biāo)識(shí)怎么解決
myeclipse8.6導(dǎo)入maven項(xiàng)目后識(shí)別為普通java項(xiàng)目,即項(xiàng)目圖標(biāo)上沒有小M的標(biāo)識(shí)。這時(shí)是無法直接運(yùn)行的,怎么解決這一問題呢?下面小編給大家?guī)砹私鉀Q方案,需要的朋友參考下吧2016-11-11LeetCode?動(dòng)態(tài)規(guī)劃之矩陣區(qū)域和詳情
這篇文章主要介紹了LeetCode?動(dòng)態(tài)規(guī)劃之矩陣區(qū)域和詳情,文章基于Java的相關(guān)資料展開對(duì)LeetCode?動(dòng)態(tài)規(guī)劃的詳細(xì)介紹,需要的小伙伴可以參考一下2022-04-04SpringBoot整合Redis使用@Cacheable和RedisTemplate
本文主要介紹了SpringBoot整合Redis使用@Cacheable和RedisTemplate,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07數(shù)據(jù)定位在java購(gòu)物車系統(tǒng)中的應(yīng)用
實(shí)現(xiàn)"加入購(gòu)物車"功能,數(shù)據(jù)定位至關(guān)重要,它通過用戶ID和商品ID等標(biāo)識(shí)符實(shí)現(xiàn)快速查詢和數(shù)據(jù)一致性,主鍵、外鍵和聯(lián)合索引等數(shù)據(jù)庫(kù)技術(shù),以及Redis緩存和并發(fā)控制策略如樂觀鎖或分布式鎖,共同保障了購(gòu)物車系統(tǒng)的查詢效率和數(shù)據(jù)安全,這些機(jī)制對(duì)高并發(fā)和大數(shù)據(jù)量的場(chǎng)景尤為重要2024-10-10深入解讀 Spring Boot 生態(tài)之功能、組件與優(yōu)勢(shì)
本文將深入剖析 Spring Boot 的生態(tài)體系,包括其核心功能、生態(tài)組件以及在不同場(chǎng)景中的應(yīng)用,并附上一張 Spring Boot 生態(tài)系統(tǒng)圖,幫助開發(fā)者更直觀地理解 Spring Boot 的強(qiáng)大之處,感興趣的朋友一起看看吧2024-11-11IDEA工程運(yùn)行時(shí)總是報(bào)xx程序包不存在實(shí)際上包已導(dǎo)入(問題分析及解決方案)
這篇文章主要介紹了IDEA工程運(yùn)行時(shí),總是報(bào)xx程序包不存在,實(shí)際上包已導(dǎo)入,本文給大家分享問題分析及解決方案,通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2020-08-08