Java将图像数据传到C++的两种高效实现方式对比
作者:颇有几分姿色
在图像处理时,Java 的图像数据换到 c++中是无法直接使用的,本文为大家整理了两种高效实现方式并进行了对比,感兴趣的小伙伴可以了解一下
在图像处理时,Java 的图像数据换到 c++中是无法直接使用的,需要转为 BGR 格式,要么在 java 层处理,要么在 jni 层处理,算法工程师的提供的动态库一般不会处理图片格式,直接拿到图像数据就使用了,这里写的是我自己用过的两种实现方式。
背景
调试时遇到的问题:
- 图像的颜色通道顺序(Java 是 RGB,OpenCV 是 BGR),需要转换
- 图像类型多样(ARGB、灰度、RGB、BGR 等)
- 是否需要中间转换?
- 数据如何传输最优?
两种实现方式
环境:
JDK:8
OPENCV:4.5.3
对比:
实现方式 | Java 处理通道顺序 | JNI 层构造 cv::Mat | 性能 | 灵活性 |
---|---|---|---|---|
方式一:Java 转为 BGR | Java 层预处理为 BGR | 直接生成 Mat | 中 | 高 |
方式二:Java 原始 byte[] | 不处理 | C++ 层判断格式并转换 | 高 | 高 |
没有做过实际的数据对比,但处理速度上方式二是比方式一快的,至于快多少,不估了,啥时候有空再上数据吧。
以下是代码示例:
假设算法头文件其中一个方法是这样的:
//进行MRZ码识别,只识别,不进行任何MRZ码内容校验。 /** * @brief 对输入的图像,进行字符OCR,并选择最长的两行字符输出 * @param pData: 图像数据,BGR格式或Gray * @param nw: 图像宽度 * @param nh: 图像高度 * @param channels: 图像通道数 * @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 实现
方法映射:
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格式矩阵数据 * @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格式矩阵数据 * @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(); // 创建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格式图像数据 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) { // 通过颜色模型判断:颜色分量数量为1时表示单通道 return image.getColorModel().getNumColorComponents() == 1; } // 单通道转三通道的具体实现 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实现
jbyte* imageDataBytes = env->GetByteArrayElements(imageData, nullptr); // 获取后直接传到对应方法里即可,有时可能需要转换:reinterpret_cast<unsigned char*>(imageDataBytes)
这样做的好处是:
- 图像格式一致,C++ 层无需关心图像类型
- 支持灰度、ARGB、RGB 等复杂格式
方式二:Java 层不处理,传原始 byte[] 到 JNI
Java 实现
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实现
JNIEXPORT jobject JNICALL Java_com_emp_empxmrz_util_MrzDetect_detectMRZ (JNIEnv* env, jclass, jbyteArray imageData, jlong modelPtr) { // ...... // 获取传入的图片字节数组 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; } // 复制数据到 vector std::vector<uchar> buffer(dataPtr, dataPtr + len); env->ReleaseByteArrayElements(imageData, dataPtr, JNI_ABORT); // 通过 OpenCV imdecode 解码图片数据 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 等
- 适合网络图像、文件流、Base64 解码后图像等各种来源
- 易于跨平台、跨语言传输(例如 HTTP 上传)
Java 和 C++ 的图像数据交互不算复杂,关键是理解图像格式的要求。
到此这篇关于Java将图像数据传到C++的两种高效实现方式对比的文章就介绍到这了,更多相关Java图像数据传到C++内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!