调试ARM+OV5640+上位机遇到的问题总结

调试ARM+OV5640+上位机遇到的问题总结

1. 在Java Swing编写的上位机显示图像(实现类似视频播放的效果)

目标:

在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;
            }
        }
    }
});

2. Java中转换YUV图像成BufferedImage的RGB int数组

目标:

本例中经过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; i<height; i++){
            int 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,&nbsp;y) in the
 * this image, the ARGB pixel can be accessed in this way:
 * <pre>
 *    pixel   = rgbArray[offset + (y-startY)*scansize + (x-startX)];
 * </pre>
 * WARNING: No dithering takes place.
 *
 * <p>
 *
 * 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进行转换。

3. 设置OV5640的输出格式为RGB

目标:

因为转换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;

注意:

​ 颜色空间配置完成后,一定要确认摄像头是否支持该颜色空间!

4. 上位机中将RGB565解码为BufferedImage的RGB int数组

目标:

将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是绿色分量的高三位)。

​ 因此需要先转换为高字节序后进行解码。

5. ARM板上出现的Segment Fault问题

现象:

在ARM板子上通过while(1)循环进行摄像头数据的采集发送,发送一定时间后,出现Segment Fault。

解决方法:

不在根目录下执行可执行文件即可解决问题。(本问题起因不明,解决方法也不明所以,待深究)

猜你喜欢

转载自blog.csdn.net/u010034969/article/details/80725349