背景:代码的原理是opencv连接摄像头,捕获到帧数据mat(矩阵),将其转成byte数组然后进行base64编码,把数据与帧的其他信息(宽、高、通道数、时间戳)写入到json对象中,Kafka把json对象以字符串是方式发送出去;消费者(接收方)是ceph集群,拿到json对象后从中提取出帧的编码数据,作为对象内容存进去。要播放视频,就要把数据读出来,然后还原。
从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类,用它应该也能还原。应该第一种方法更好。
opencv里的imwrite()方法用于将图像保存到指定的文件,可以为各种格式的图像,有三个参数:
我们只需要使用前两个参数。使用方法为: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大小跟数组不匹配导致的。
前面遇到的问题是从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