在Java Swing中实现一个能逐帧显示图片的控件。
写一个类继承JComponent,并且重写paint方法:
private class PaintSurface extends JComponent {
private LinkedBlockingQueue<int[]> rgbQueue = new LinkedBlockingQueue<>();
public void inQueue(int[] rgbData) throws InterruptedException {
rgbQueue.put(rgbData);
}
private BufferedImage rgbArrayToImg(int[] rgbData) {
BufferedImage bi = new BufferedImage(CanvasTest.WIDTH,CanvasTest.HEIGHT,
BufferedImage.TYPE_INT_RGB);
bi.setRGB(0,0,CanvasTest.WIDTH,CanvasTest.HEIGHT,rgbData,0,CanvasTest.WIDTH);
return bi;
}
@Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2 = (Graphics2D) g;
try {
g2.drawImage(rgbArrayToImg(rgbQueue.take()),0,0,null);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Paint frames:" + ++frames);
}
}
在重写的paint方法中,将传入的Graphics对象转换成Graphics2D对象,之后调用drawImage方法绘制一帧的图像即可。
之后在主函数中往窗体添加自定义的类:
public class CanvasTest {
private PaintSurface ps = new PaintSurface();
private JPanel mainPanel;
//...
public static void main(String[] args) {
CanvasTest cv = new CanvasTest();
JFrame frame = new JFrame("CanvasTest");
frame.setContentPane(cv.mainPanel);
//...
cv.ps.setSize(640,480);
frame.getContentPane().add(cv.ps);
//...
}
}
在子线程里使用父控件的updateUI()
方法刷新显示:
Thread repaint = new Thread(new Runnable() {
volatile int lastFrames = -1;
@Override
public void run() {
while (true) {
if(lastFrames != frames) {
mainPanel.updateUI();
lastFrames = frames;
}
}
}
});
本例中经过TCP传到电脑上的是摄像头采集回来的裸流,为YUV2格式。电脑上可使用YUV Player播放。
现在需要在上位机程序上显示,则需要将其转换成RGB数据后再显示。
此处代码参考了http://www.aichengxu.com/java/2320.htm:
public class YUV2RGB {
private static int R = 0;
private static int G = 1;
private static int B = 2;
private static class RGB{
public int r, g, b;
}
private static RGB yuvTorgb(byte Y, byte U, byte V){
RGB rgb = new RGB();
rgb.r = (int)((Y&0xff) + 1.4075 * ((V&0xff)-128));
rgb.g = (int)((Y&0xff) - 0.3455 * ((U&0xff)-128) - 0.7169*((V&0xff)-128));
rgb.b = (int)((Y&0xff) + 1.779 * ((U&0xff)-128));
rgb.r =(rgb.r<0? 0: rgb.r>255? 255 : rgb.r);
rgb.g =(rgb.g<0? 0: rgb.g>255? 255 : rgb.g);
rgb.b =(rgb.b<0? 0: rgb.b>255? 255 : rgb.b);
return rgb;
}
public static int[] YUY2ToRGB(byte[] src, int width, int height){
int numOfPixel = width * height;
int[] rgb = new int[numOfPixel*3];
int[] retRGB = new int[numOfPixel];
int lineWidth = 2*width;
for(int i=0; iint startY = i*lineWidth;
for(int j = 0; j < lineWidth; j+=4){
int Y1 = j + startY;
int Y2 = Y1+2;
int U = Y1+1;
int V = Y1+3;
int index = (Y1>>1)*3;
RGB tmp = yuvTorgb(src[Y1], src[U], src[V]);
rgb[index+R] = tmp.r;
rgb[index+G] = tmp.g;
rgb[index+B] = tmp.b;
index += 3;
tmp = yuvTorgb(src[Y2], src[U], src[V]);
rgb[index+R] = tmp.r;
rgb[index+G] = tmp.g;
rgb[index+B] = tmp.b;
}
//Orginal Code Return rgb[] Here...
for(int convert = 0;convert < numOfPixel;convert++) {
retRGB[convert] = 0xff000000 | rgb[convert*3] << 16 | rgb[convert*3 + 1] << 8 |
rgb[convert*3 + 2];
}
}
return retRGB;
}
}
根据上述代码实现YUV2转RGB。
注意:
代码中的注释处是原始代码中返回的地方。
原始代码返回的数组中,每个像素点分成了三个int变量来表示,例如第一个点占用了rgb[0],rgb[1],rgb[2]。
在BufferedImage中的RGB数组是以固定格式存在的,即不论原格式是RGB565,还是灰度图,或是RGB888,ARGB等格式,和getRGB或setRGB方法相关的int[]数组,一定都是按照每个像素AARRGGBB的格式占用一个int进行存储。
因此若要使用setRGB的方式设置图像内容,需要将对应的int[]数组进行转换。即本代码中的后半部分。
得到int[]数组后,通过setRGB方法得到一幅BufferedImage:
private BufferedImage rgbArrayToImg(int[] rgbData) {
BufferedImage bi = new BufferedImage(CanvasTest.WIDTH,CanvasTest.HEIGHT, BufferedImage.TYPE_INT_RGB);
bi.setRGB(0,0,CanvasTest.WIDTH,CanvasTest.HEIGHT,rgbData,0,CanvasTest.WIDTH);
return bi;
}
setRGB方法的JavaDoc如下:
/**
* Sets an array of integer pixels in the default RGB color model
* (TYPE_INT_ARGB) and default sRGB color space,
* into a portion of the image data. Color conversion takes place
* if the default model does not match the image
* {@code ColorModel}. There are only 8-bits of precision for
* each color component in the returned data when
* using this method. With a specified coordinate (x, y) in the
* this image, the ARGB pixel can be accessed in this way:
*
* pixel = rgbArray[offset + (y-startY)*scansize + (x-startX)];
*
* WARNING: No dithering takes place.
*
*
*
* An {@code ArrayOutOfBoundsException} may be thrown
* if the region is not in bounds.
* However, explicit bounds checking is not guaranteed.
*
* @param startX the starting X coordinate
* @param startY the starting Y coordinate
* @param w width of the region
* @param h height of the region
* @param rgbArray the rgb pixels
* @param offset offset into the {@code rgbArray}
* @param scansize scanline stride for the {@code rgbArray}
* @see #getRGB(int, int)
* @see #getRGB(int, int, int, int, int[], int, int)
*/
public void setRGB(int startX, int startY, int w, int h,
int[] rgbArray, int offset, int scansize)
scansize是图像中一行的长度。
此处其实可以使用Xuggler或调用ffmpeg库进行解码,会有更高效率。
因为能力和时间问题,暂时不做考虑。
该方法最大的问题就是转换速率太慢,一帧640x480的图像需要200ms进行转换。
因为转换YUV为RGB格式效率太低,因此考虑设置OV5640直接输出RGB565格式的视频并进行显示。
本例中的摄像头使用的是iTOP-4412开发板提供的摄像头测试程序,代码中使用了V4L2对摄像头进行了配置。
代码中对摄像头的颜色空间的配置相关代码如下:
struct v4l2_format fmt;
//...
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
//V4L2_PIX_FMT_YVU420, V4L2_PIX_FMT_YUV420 ! Planar formats with 1/2 horizontal and vertical chroma resolution, also known as YUV 4:2:0
//V4L2_PIX_FMT_YUYV ! Packed format with 1/2 horizontal chroma resolution, also known as YUV 4:2:2
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //摄像头颜色空间配置
对应的选项在Linux内核的include\linux\videodev2.h
中:
/* Pixel format FOURCC depth Description */
/* RGB formats */
#define V4L2_PIX_FMT_RGB332 v4l2_fourcc('R', 'G', 'B', '1') /* 8 RGB-3-3-2 */
#define V4L2_PIX_FMT_RGB444 v4l2_fourcc('R', '4', '4', '4') /* 16 xxxxrrrr ggggbbbb */
#define V4L2_PIX_FMT_RGB555 v4l2_fourcc('R', 'G', 'B', 'O') /* 16 RGB-5-5-5 */
#define V4L2_PIX_FMT_RGB565 v4l2_fourcc('R', 'G', 'B', 'P') /* 16 RGB-5-6-5 */
#define V4L2_PIX_FMT_RGB555X v4l2_fourcc('R', 'G', 'B', 'Q') /* 16 RGB-5-5-5 BE */
#define V4L2_PIX_FMT_RGB565X v4l2_fourcc('R', 'G', 'B', 'R') /* 16 RGB-5-6-5 BE */
#define V4L2_PIX_FMT_BGR666 v4l2_fourcc('B', 'G', 'R', 'H') /* 18 BGR-6-6-6 */
#define V4L2_PIX_FMT_BGR24 v4l2_fourcc('B', 'G', 'R', '3') /* 24 BGR-8-8-8 */
#define V4L2_PIX_FMT_RGB24 v4l2_fourcc('R', 'G', 'B', '3') /* 24 RGB-8-8-8 */
#define V4L2_PIX_FMT_BGR32 v4l2_fourcc('B', 'G', 'R', '4') /* 32 BGR-8-8-8-8 */
#define V4L2_PIX_FMT_RGB32 v4l2_fourcc('R', 'G', 'B', '4') /* 32 RGB-8-8-8-8 */
/* Grey formats */
#define V4L2_PIX_FMT_GREY v4l2_fourcc('G', 'R', 'E', 'Y') /* 8 Greyscale */
#define V4L2_PIX_FMT_Y4 v4l2_fourcc('Y', '0', '4', ' ') /* 4 Greyscale */
#define V4L2_PIX_FMT_Y6 v4l2_fourcc('Y', '0', '6', ' ') /* 6 Greyscale */
#define V4L2_PIX_FMT_Y10 v4l2_fourcc('Y', '1', '0', ' ') /* 10 Greyscale */
#define V4L2_PIX_FMT_Y12 v4l2_fourcc('Y', '1', '2', ' ') /* 12 Greyscale */
#define V4L2_PIX_FMT_Y16 v4l2_fourcc('Y', '1', '6', ' ') /* 16 Greyscale */
/* Grey bit-packed formats */
#define V4L2_PIX_FMT_Y10BPACK v4l2_fourcc('Y', '1', '0', 'B') /* 10 Greyscale bit-packed */
/* Palette formats */
#define V4L2_PIX_FMT_PAL8 v4l2_fourcc('P', 'A', 'L', '8') /* 8 8-bit palette */
/* Luminance+Chrominance formats */
#define V4L2_PIX_FMT_YVU410 v4l2_fourcc('Y', 'V', 'U', '9') /* 9 YVU 4:1:0 */
#define V4L2_PIX_FMT_YVU420 v4l2_fourcc('Y', 'V', '1', '2') /* 12 YVU 4:2:0 */
#define V4L2_PIX_FMT_YUYV v4l2_fourcc('Y', 'U', 'Y', 'V') /* 16 YUV 4:2:2 */
#define V4L2_PIX_FMT_YYUV v4l2_fourcc('Y', 'Y', 'U', 'V') /* 16 YUV 4:2:2 */
#define V4L2_PIX_FMT_YVYU v4l2_fourcc('Y', 'V', 'Y', 'U') /* 16 YVU 4:2:2 */
#define V4L2_PIX_FMT_UYVY v4l2_fourcc('U', 'Y', 'V', 'Y') /* 16 YUV 4:2:2 */
#define V4L2_PIX_FMT_VYUY v4l2_fourcc('V', 'Y', 'U', 'Y') /* 16 YUV 4:2:2 */
#define V4L2_PIX_FMT_YUV422P v4l2_fourcc('4', '2', '2', 'P') /* 16 YVU422 planar */
#define V4L2_PIX_FMT_YUV411P v4l2_fourcc('4', '1', '1', 'P') /* 16 YVU411 planar */
#define V4L2_PIX_FMT_Y41P v4l2_fourcc('Y', '4', '1', 'P') /* 12 YUV 4:1:1 */
#define V4L2_PIX_FMT_YUV444 v4l2_fourcc('Y', '4', '4', '4') /* 16 xxxxyyyy uuuuvvvv */
#define V4L2_PIX_FMT_YUV555 v4l2_fourcc('Y', 'U', 'V', 'O') /* 16 YUV-5-5-5 */
#define V4L2_PIX_FMT_YUV565 v4l2_fourcc('Y', 'U', 'V', 'P') /* 16 YUV-5-6-5 */
#define V4L2_PIX_FMT_YUV32 v4l2_fourcc('Y', 'U', 'V', '4') /* 32 YUV-8-8-8-8 */
#define V4L2_PIX_FMT_YUV410 v4l2_fourcc('Y', 'U', 'V', '9') /* 9 YUV 4:1:0 */
#define V4L2_PIX_FMT_YUV420 v4l2_fourcc('Y', 'U', '1', '2') /* 12 YUV 4:2:0 */
#define V4L2_PIX_FMT_HI240 v4l2_fourcc('H', 'I', '2', '4') /* 8 8-bit color */
#define V4L2_PIX_FMT_HM12 v4l2_fourcc('H', 'M', '1', '2') /* 8 YUV 4:2:0 16x16 macroblocks */
#define V4L2_PIX_FMT_M420 v4l2_fourcc('M', '4', '2', '0') /* 12 YUV 4:2:0 2 lines y, 1 line uv interleaved */
...
因此,将颜色空间配置的代码做如下修改即可:
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565;
注意:
颜色空间配置完成后,一定要确认摄像头是否支持该颜色空间!
将ARM上传来的RGB565图像格式转换成BufferedImage的RGB int数组
public static int[] RGB565ByteToInt(byte[] src, int width, int height) { //RGB565 Little Endian
int numOfPixel = width*height;
int[] retRGB = new int[numOfPixel];
for(int convert = 0;convert < numOfPixel;convert++) {
byte[] srlLe = {src[convert*2 + 1],src[convert*2]};
int r = (srlLe[0] & 0xf8);
int g = ((srlLe[0] & 0x07) << 5 | (srlLe[1] & 0xe0 >> 3));
int b = (srlLe[1] & 0x1F) << 3;
retRGB[convert] = 0xff000000 | r<<16 | g << 8 | b ;
}
return retRGB;
}
RGB565是以两个字节(16Bit)来存放一个像素的色彩信息,格式为:
RRRRRGGG GGGBBBB
要转换成RGB888,则按照补0的方式进行转换:
RRRRR000 GGGGGG00 BBBBB000
注意:
上面说的是Big Endian的编码方式,即高字节在前的编码方式。
OV5640摄像头**不支持**BE编码,因此传回来的数据是Little Endian,即一个像素是以
GGGBBBB RRRRRGGG
的方式进行储存(这里的RRRRRGGG
中的GGG
是绿色分量的高三位)。 因此需要先转换为高字节序后进行解码。
在ARM板子上通过while(1)循环进行摄像头数据的采集发送,发送一定时间后,出现Segment Fault。
不在根目录下执行可执行文件即可解决问题。(本问题起因不明,解决方法也不明所以,待深究)