Java將圖像數(shù)據(jù)傳到C++的兩種高效實現(xiàn)方式對比
在圖像處理時,Java 的圖像數(shù)據(jù)換到 c++中是無法直接使用的,需要轉為 BGR 格式,要么在 java 層處理,要么在 jni 層處理,算法工程師的提供的動態(tài)庫一般不會處理圖片格式,直接拿到圖像數(shù)據(jù)就使用了,這里寫的是我自己用過的兩種實現(xiàn)方式。
背景
調試時遇到的問題:
- 圖像的顏色通道順序(Java 是 RGB,OpenCV 是 BGR),需要轉換
- 圖像類型多樣(ARGB、灰度、RGB、BGR 等)
- 是否需要中間轉換?
- 數(shù)據(jù)如何傳輸最優(yōu)?
兩種實現(xiàn)方式
環(huán)境:
JDK:8
OPENCV:4.5.3
對比:
| 實現(xiàn)方式 | Java 處理通道順序 | JNI 層構造 cv::Mat | 性能 | 靈活性 |
|---|---|---|---|---|
| 方式一:Java 轉為 BGR | Java 層預處理為 BGR | 直接生成 Mat | 中 | 高 |
| 方式二:Java 原始 byte[] | 不處理 | C++ 層判斷格式并轉換 | 高 | 高 |
沒有做過實際的數(shù)據(jù)對比,但處理速度上方式二是比方式一快的,至于快多少,不估了,啥時候有空再上數(shù)據(jù)吧。
以下是代碼示例:
假設算法頭文件其中一個方法是這樣的:
//進行MRZ碼識別,只識別,不進行任何MRZ碼內容校驗。 /** * @brief 對輸入的圖像,進行字符OCR,并選擇最長的兩行字符輸出 * @param pData: 圖像數(shù)據(jù),BGR格式或Gray * @param nw: 圖像寬度 * @param nh: 圖像高度 * @param channels: 圖像通道數(shù) * @param ocr: 字符識別對象句柄,來自InitModel * @return emp_MRZ*: 字符識別結果,需要調用Release_empMRZ接口釋放內存。 */ PassportMRZ_API emp_MRZ* detectMRZ(unsigned char * pData, int nw, int nh,int channels,void * ocr);
方式一:Java 層轉為 BGR 格式,再傳給 JNI
Java 實現(xiàn)
方法映射:
public native static EmpMRZ detectMRZ(byte[] imageData, int width, int height, int channels, long ocrPtr);
圖像處理工具類:
public class CustomImgUtils {
/**
* @param image
* @param bandOffset 用于判斷通道順序
* @return
*/
private static boolean equalBandOffsetWith3Byte(BufferedImage image,int[] bandOffset){
if(image.getType()==BufferedImage.TYPE_3BYTE_BGR){
if(image.getData().getSampleModel() instanceof ComponentSampleModel){
ComponentSampleModel sampleModel = (ComponentSampleModel)image.getData().getSampleModel();
if(Arrays.equals(sampleModel.getBandOffsets(), bandOffset)){
return true;
}
}
}
return false;
}
/**
* 判斷圖像是否為BGR格式
* @return
*/
public static boolean isBGR3Byte(BufferedImage image){
return equalBandOffsetWith3Byte(image,new int[]{0, 1, 2});
}
/**
* 判斷圖像是否為RGB格式
* @return
*/
public static boolean isRGB3Byte(BufferedImage image){
return equalBandOffsetWith3Byte(image,new int[]{2, 1, 0});
}
/**
* 判斷圖像是否為ARGB格式
* @return
*/
public static boolean isARGB(BufferedImage image){
return image.getType() == BufferedImage.TYPE_INT_ARGB ||
image.getType() == BufferedImage.TYPE_INT_ARGB_PRE ||
image.getType() == BufferedImage.TYPE_4BYTE_ABGR ||
image.getType() == BufferedImage.TYPE_4BYTE_ABGR_PRE;
}
/**
* 判斷圖像是否為灰度圖
* @return
*/
public boolean isGray(BufferedImage image){
return image.getType() == BufferedImage.TYPE_BYTE_GRAY;
}
/**
* 對圖像解碼返回RGB格式矩陣數(shù)據(jù)
* @param image
* @return
*/
public static byte[] getMatrixRGB(BufferedImage image) {
if(null==image)
throw new NullPointerException();
byte[] matrixRGB;
if(isRGB3Byte(image)){
matrixRGB= (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
}else{
// 轉RGB格式
BufferedImage rgbImage = new BufferedImage(image.getWidth(), image.getHeight(),
BufferedImage.TYPE_3BYTE_BGR);
new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null).filter(image, rgbImage);
matrixRGB= (byte[]) rgbImage.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
}
return matrixRGB;
}
/**
* 對圖像解碼返回BGR格式矩陣數(shù)據(jù)
* @param image
* @param channels
* @return
*/
public static byte[] getMatrixBGR(BufferedImage image){
if(null==image)
throw new NullPointerException();
byte[] matrixBGR;
if(isBGR3Byte(image)){
matrixBGR= (byte[]) image.getData().getDataElements(0, 0, image.getWidth(), image.getHeight(), null);
} else if (isGray(image)) {
byte[] gray = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
// 創(chuàng)建BGR矩陣
matrixBGR = new byte[gray.length * 3];
for (int i = 0, j = 0; i < gray.length; ++i, j += 3) {
// Blue
matrixBGR[j] = gray[i];
// Green
matrixBGR[j + 1] = gray[i];
// Red
matrixBGR[j + 2] = gray[i];
}
return matrixBGR;
} else {
// ARGB格式圖像數(shù)據(jù)
int[] intrgb = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());
matrixBGR = new byte[image.getWidth() * image.getHeight() * 3];
// ARGB轉BGR格式
for (int i = 0, j = 0; i < intrgb.length; ++i, j += 3) {
// Blue
matrixBGR[j] = (byte) (intrgb[i] & 0xff);
// Green
matrixBGR[j + 1] = (byte) ((intrgb[i] >> 8) & 0xff);
// Red
matrixBGR[j + 2] = (byte) ((intrgb[i] >> 16) & 0xff);
}
}
return matrixBGR;
}
// 判斷是否為單通道圖像(灰度圖或單通道類型)
public static boolean isSingleChannel(BufferedImage image) {
// 通過顏色模型判斷:顏色分量數(shù)量為1時表示單通道
return image.getColorModel().getNumColorComponents() == 1;
}
// 單通道轉三通道的具體實現(xiàn)
public static BufferedImage convertSingleToThreeChannels(BufferedImage srcImage) {
int width = srcImage.getWidth();
int height = srcImage.getHeight();
BufferedImage destImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int grayValue = srcImage.getRaster().getSample(x, y, 0);
int rgb = (grayValue << 16) | (grayValue << 8) | grayValue;
destImage.setRGB(x, y, rgb);
}
}
return destImage;
}
}
邏輯層:
public R<MrzInfo> delectJni(MultipartFile file) throws IOException {
log.info("begin detect");
try {
long start = System.currentTimeMillis();
if (null ==file || file.isEmpty()) {
return R.failed("傳入文件為空");
}
BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
// int channels = bufferedImage.getColorModel().getNumComponents();
int channels = 3;
if (CustomImgUtils.isSingleChannel(bufferedImage)) {
// 通道轉換
bufferedImage = CustomImgUtils.convertSingleToThreeChannels(bufferedImage);
}
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
byte[] matrixBGR = CustomImgUtils.getMatrixBGR(bufferedImage);
MrzInfo mrzInfo = new MrzInfo();
long mrzStart = System.currentTimeMillis();
EmpMRZ result = PassportMRZ.detectMrz(matrixBGR, width, height, channels);
// ……
} catch (Exception e) {
log.error("error:",e);
return R.failed(new MrzInfo(),"detect exception");
}
}
JNI實現(xiàn)
jbyte* imageDataBytes = env->GetByteArrayElements(imageData, nullptr); // 獲取后直接傳到對應方法里即可,有時可能需要轉換:reinterpret_cast<unsigned char*>(imageDataBytes)
這樣做的好處是:
- 圖像格式一致,C++ 層無需關心圖像類型
- 支持灰度、ARGB、RGB 等復雜格式
方式二:Java 層不處理,傳原始 byte[] 到 JNI
Java 實現(xiàn)
public native static EmpMRZ detectMRZ(byte[] imageData, long ocrPtr);
圖像處理工具類:
public class ImageProcessor {
// 公共方法:將 InputStream 轉換為 byte[]
public static byte[] inputStreamToByteArray(InputStream inputStream) throws IOException {
try (ByteArrayOutputStream buffer = new ByteArrayOutputStream();
InputStream is = inputStream) {
byte[] data = new byte[8192];
int nRead;
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
return buffer.toByteArray();
}
}
邏輯調用層:
byte[] imageBytes = ImageProcessor.inputStreamToByteArray(file.getInputStream()); detectMRZ(imageBytes, ocrPtr);
JNI實現(xiàn)
JNIEXPORT jobject JNICALL Java_com_emp_empxmrz_util_MrzDetect_detectMRZ
(JNIEnv* env, jclass, jbyteArray imageData, jlong modelPtr) {
// ......
// 獲取傳入的圖片字節(jié)數(shù)組
jsize len = env->GetArrayLength(imageData);
if (len <= 0) {
fprintf(stderr, "[JNI] Invalid image data length: %d\n", len);
jclass exceptionCls = env->FindClass("java/lang/RuntimeException");
env->ThrowNew(exceptionCls, "Invalid image data length");
return nullptr;
}
jbyte* dataPtr = env->GetByteArrayElements(imageData, nullptr);
if (dataPtr == nullptr) {
fprintf(stderr, "[JNI] Failed to get image data elements\n");
return nullptr;
}
// 復制數(shù)據(jù)到 vector
std::vector<uchar> buffer(dataPtr, dataPtr + len);
env->ReleaseByteArrayElements(imageData, dataPtr, JNI_ABORT);
// 通過 OpenCV imdecode 解碼圖片數(shù)據(jù)
cv::Mat image = cv::imdecode(buffer, cv::IMREAD_UNCHANGED);
if (image.empty()) {
fprintf(stderr, "[JNI] Failed to decode image\n");
jclass exceptionCls = env->FindClass("java/lang/RuntimeException");
env->ThrowNew(exceptionCls, "Failed to decode image");
return nullptr;
}
int width = image.cols;
int height = image.rows;
int channels = image.channels();
emp_MRZ* pRes = nullptr;
try {
// 調用 detectMRZ 接口進行識別
pRes = detectMRZ(image.data, width, height, channels, ocr);
if (pRes == nullptr) {
fprintf(stderr, "[JNI] detectMRZ returned NULL\n");
return nullptr;
}
// ......
}
catch (const std::exception& e) {
fprintf(stderr, "[JNI] Exception in detectMRZ: %s\n", e.what());
jclass exceptionCls = env->FindClass("java/lang/RuntimeException");
env->ThrowNew(exceptionCls, e.what());
if (pRes) {
Release_empMRZ(pRes);
}
return nullptr;
}
}
這樣做的好處是:
- Java 層代碼干凈、簡單(只讀流,不處理圖像)
- 支持所有格式:JPEG、PNG、BMP、WebP 等
- C++ 自動識別圖像通道:灰度、BGR、BGRA 等
- 適合網(wǎng)絡圖像、文件流、Base64 解碼后圖像等各種來源
- 易于跨平臺、跨語言傳輸(例如 HTTP 上傳)
Java 和 C++ 的圖像數(shù)據(jù)交互不算復雜,關鍵是理解圖像格式的要求。
到此這篇關于Java將圖像數(shù)據(jù)傳到C++的兩種高效實現(xiàn)方式對比的文章就介紹到這了,更多相關Java圖像數(shù)據(jù)傳到C++內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java中InputSteam轉String的實現(xiàn)方法
本文主要介紹了InputStream與String互轉的多種方法,包括使用JDK原生、Apache Commons、Google Guava等,具有一定的參考價值,感興趣的可以了解一下2025-07-07

