Java opencv把mat还原成图片

背景:代码的原理是opencv连接摄像头,捕获到帧数据mat(矩阵),将其转成byte数组然后进行base64编码,把数据与帧的其他信息(宽、高、通道数、时间戳)写入到json对象中,Kafka把json对象以字符串是方式发送出去;消费者(接收方)是ceph集群,拿到json对象后从中提取出帧的编码数据,作为对象内容存进去。要播放视频,就要把数据读出来,然后还原。


1.遇到的问题及解决思路

从ceph读取数据的方法已经掌握了,现在问题是还原。为了运行方便,我在生产者获取数据之后做了一些实验,尝试把帧的数据还原成图片。获取的数据主要是下面两行,mat是opencv的一种类,是帧的矩阵形式,可以转化成byte[],可以再对数组编码。

//创建一个与该帧一样大的byte数组
byte[] data = new byte[(int) (mat.total() * mat.channels())];
//把数组进行base64编码,然后添加到一个json对象obj中
obj.addProperty("data", Base64.getEncoder().encodeToString(data));

而普通的Java读取和还原图片,是以IO流的方式进行的,如我们的小文件存储部分,对于图片可以用Java的FileInput(Output)Stream或者ByteArrayInputStream+ImageIO.read/write方法完成。
但是经过一系列测试,发现上述两种方法不能还原从摄像头来的帧数据。
如果用FileOutputStream,确实可以得到一个图片文件,但是打不开。
在这里插入图片描述
在这里插入图片描述


如果用ByteArrayInputStream+ImageIO.read/write,会报错image= null,在调试过程中发现bufferedImage 没有收到数据,image的内容是空的,所以后面ImageIO.write()无法生成图片。其根本原因是ImageIO.read()可读取的图片类型是有限制的,可以读取图片的格式为:[BMP, bmp, jpg, JPG, wbmp, jpeg, png, PNG, JPEG, WBMP, GIF, gif],也就是它要读的in这个数据,来源得是上述格式图片转化而来的,这样才能再还原回去。


而视频流数据和普通的图片类型文件转化得到的数据是有差别的,所以Java自带的ImageIO不能还原。还原方法应该是用opencv的函数。我曾用python opencv把视频解析成帧,用到的一些类和函数和现在的比较接近,核心是

ret,frame=videocapture.read(),
cv2.imwrite(jpg,frame)

搞清楚这里 frame和Java代码里mat的关系以及videocapture.read()的返回值,应该可以解决问题。我以前还用Java opencv连接到了笔记本摄像头并直播画面,主要用的是opencv的jframe类,用它应该也能还原。应该第一种方法更好。
在这里插入图片描述

2.mat还原成图片

opencv里的imwrite()方法用于将图像保存到指定的文件,可以为各种格式的图像,有三个参数:

  • 第一个参数const String& filename表示需要写入的文件名,必须要加上后缀,比如“123.jpg”;
  • 第二个参数InputArray img(C++)表示Mat类型的图像数据,经过实验,JAVA API里用mat;
  • 第三个参数const std::vector& params表示为特定格式保存的参数编码,它有一个默认值std::vector< int >(),所以一般情况下不用写,如果更改的话,对于不同的图片格式,其对应的值不同功能不同。

我们只需要使用前两个参数。使用方法为:imwrite("*.jpg",frame);//将摄像头获取的图像帧frame保存到一个jpg图片,这里的frame是帧的原始数据mat。java源码如下:

public static boolean imwrite(String filename, Mat img) {
        return imwrite_1(filename, img.nativeObj);
    }

由mat还原图片demo:

import org.opencv.core.*;
import org.opencv.imgcodecs.*;
import org.opencv.imgproc.Imgproc;

public class ReadAndWrite {
	public static void main(String[] args) {
		//加载opencv
		System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
		
		String src = "d:/Java/opencv/example.jpg";
		//读取图像
		Mat srcImage = Imgcodecs.imread(src);
		//读取灰度图像
		srcImage = Imgcodecs.imread(src, Imgcodecs.CV_LOAD_IMAGE_GRAYSCALE);
		
		Mat dstImage = srcImage.clone();
		//保存图像
		Imgcodecs.imwrite("d:/Java/opencv/example_write.jpg", dstImage);
	}
}

在kafka生产者代码里,收取到一帧的mat后,用上面的方法进行还原,成功生成了图片,摄像头的画面。
在这里插入图片描述
上面的mat是生产者发送前的mat,而消费者接收到的是base64的string数组,所以在ceph里应该读出编码的字符串,解码,转成byte[],然后用这个数组创建一个mat对象,在用这个mat还原成图片。
在操作过程中,最后两行有问题,推测原因是新建的mat容量不够或者说与图片大小不一致,要用Imgproc.resize()方法设置它的大小,只要设置正确,后面Imgcodecs.imwrite()就能生成图片。

			//用生产者那边的mat还原图片
			Imgcodecs.imwrite("H:\\example_write.jpg", mat);
			
		//	byte[] data2 = data;
			Mat mat2 = new Mat();
			//有下面这行可以正确运行出来
		//	Imgproc.resize(mat2, mat2, new Size(1080, 720), 0, 0, Imgproc.INTER_CUBIC);
			mat2.put(0, 0, data);
			Imgcodecs.imwrite("H:\\example_write222.jpg", mat2);

代码目前的问题是,mat与mat2(新建的,消费者那边的)不完全一样,就生成不了。
在这里插入图片描述
如果Imgproc.resize(mat2, mat2, new Size(1080, 720), 0, 0, Imgproc.INTER_CUBIC);这行,把第一个mat2改成mat,如下图,就没问题。因此我推测是mat2大小跟数组不匹配导致的。
在这里插入图片描述

3.解决方法

前面遇到的问题是从mat还原图片时候,如果用Imgproc.resize(mat2, mat2, new Size(1080, 720), 0, 0, Imgproc.INTER_CUBIC)修改mat的大小,会发现mat2的各种成员变量有问题,最后无法还原。
最终发现不必修改大小,在消费者端创建mat2时候就能指定它的各种参数,如大小,CV_TYPE等。这样就可以还原了。

后来过了很久我又发现了一种mat还原图片的方法,opencv的mat是可以转换成Java的BufferedImage的,用Java的方法再生成图片。这种方法的好处了可以跨语言传数据,比如我是Java opencv获取到的数据,要传给python opencv使用,就可以这么做。

/**
* 将Mat类型转化成BufferedImage类型
* 
* @param amatrix Mat对象
* @param fileExtension 文件扩展名
* @return
*/
public static BufferedImage Mat2Img(Mat mat, String fileExtension) {
    MatOfByte mob = new MatOfByte();
 
 
    Highgui.imencode(fileExtension, mat, mob);
    // convert the "matrix of bytes" into a byte array
    byte[] byteArray = mob.toArray();
    BufferedImage bufImage = null;
    try {
        InputStream in = new ByteArrayInputStream(byteArray);
        bufImage = ImageIO.read(in);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return bufImage;
}

参考博客:
https://blog.csdn.net/u013092293/article/details/53538138
https://blog.csdn.net/tryflys/article/details/78906157

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值