嵌入式linux之在lcd上显示摄像头图像

编程入门 行业动态 更新时间:2024-10-25 18:31:47

<a href=https://www.elefans.com/category/jswz/34/1770281.html style=嵌入式linux之在lcd上显示摄像头图像"/>

嵌入式linux之在lcd上显示摄像头图像

前言

在学习本章的实验后,可以实现的功能是:在嵌入式板卡上跑linux系统,通过对设备接口的操作实现将摄像头的数据解码运输到lcd上,使得lcd上面实时显示摄像头采集的图像。
在学习本章之前需要有一定的基础才更好地了解学习,建议大家先翻阅前面的两篇文章:
使用摄像头采集图片
使用摄像头采集图像并显示在pc上

摄像头的数据采集格式

目前主流的用于开发的摄像头,如ov5640、ov2640、ov7725等,基本都带有采集YUV格式图像的功能,但是采集jpg等格式大多不支持。我们经常接触到的色彩空间的概念,主要是RGB , YUV这两种(实际上,这两种体系包含了许多种不同的颜色表达方式和模型,如sRGB, Adobe RGB, YUV422, YUV420 …), RGB如前所述就是按三基色加光系统的原理来描述颜色,而YUV则是按照 亮度,色差的原理来描述颜色。然而,lcd是使用rgb格式图片才能显示的,所有在这里我将介绍怎么将YUV格式的数据转换成rgb格式然后显示在lcd上。
首先介绍一下什么是YUV,拿一个像素点来说明。Y表示这像素点的亮度,而U和V分别代表色彩的分量。其实YUV格式下又分几种格式,我这里来说说YUYV 4:2:2格式,一般都是支持这种格式的。
这里的比例意思是一个像素点中,Y:U:V=4:2:2。也就是说亮度占的比例是其他的两倍,每一帧数据的排列是码流Y0 U0 Y1 V1 Y2 U2 Y3 V3 。但是这样不行,完整的一个像素点需要有U和V,那怎么办呢?那就要复制隔壁像素点的U或者V过来,这样一个像素点就凑齐了YUV了。于是补全之后就成了YUYV像素[Y0 U0 V1] [Y1 U0 V1] [Y2 U2 V3] [Y3 U2 V3]。到了这一步,就可以将每个像素点转化成RGB像素了。下面是转换公司,因为YUV格式是比rgb占用内存小的,用四个个字节表示两个像素点而rgb888是使用3个字节表示一个像素点。转换公式是固定的,不理解的话直接套用这个函数就可以了。

void yuyv_to_rgb(unsigned char *yuyvdata, unsigned char *rgbdata, int w, int h)
{//码流Y0 U0 Y1 V1 Y2 U2 Y3 V3 --》YUYV像素[Y0 U0 V1] [Y1 U0 V1] [Y2 U2 V3] [Y3 U2 V3]--》RGB像素int r1, g1, b1; int r2, g2, b2;for(int i=0; i<w*h/2; i++){char data[4];memcpy(data, yuyvdata+i*4, 4);unsigned char Y0=data[0];unsigned char U0=data[1];unsigned char Y1=data[2];unsigned char V1=data[3]; //Y0U0Y1V1  -->[Y0 U0 V1] [Y1 U0 V1]r1 = Y0+1.4075*(V1-128); if(r1>255)r1=255; if(r1<0)r1=0;g1 =Y0- 0.3455 * (U0-128) - 0.7169*(V1-128); if(g1>255)g1=255; if(g1<0)g1=0;b1 = Y0 + 1.779 * (U0-128);  if(b1>255)b1=255; if(b1<0)b1=0;r2 = Y1+1.4075*(V1-128);if(r2>255)r2=255; if(r2<0)r2=0;g2 = Y1- 0.3455 * (U0-128) - 0.7169*(V1-128); if(g2>255)g2=255; if(g2<0)g2=0;b2 = Y1 + 1.779 * (U0-128);  if(b2>255)b2=255; if(b2<0)b2=0;rgbdata[i*6+0]=r1;rgbdata[i*6+1]=g1;rgbdata[i*6+2]=b1;rgbdata[i*6+3]=r2;rgbdata[i*6+4]=g2;rgbdata[i*6+5]=b2;}
}

实现代码讲解

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>#include <linux/fb.h>
#include <stdio.h>int fd_fb;                                                        /* 文件句柄 */
static struct fb_var_screeninfo var; /* LCD可变参数 */
static unsigned int *fb_base = NULL;                        /* Framebuffer映射基地址 */
static int screen_size;                                      /* 整个Framebuffer大小*/
int lcd_w = 800 ,lcd_h= 480;//解码函数
void yuyv_to_rgb(unsigned char *yuyvdata, unsigned char *rgbdata, int w, int h)
{//码流Y0 U0 Y1 V1 Y2 U2 Y3 V3 --》YUYV像素[Y0 U0 V1] [Y1 U0 V1] [Y2 U2 V3] [Y3 U2 V3]--》RGB像素int r1, g1, b1; int r2, g2, b2;for(int i=0; i<w*h/2; i++){char data[4];memcpy(data, yuyvdata+i*4, 4);unsigned char Y0=data[0];unsigned char U0=data[1];unsigned char Y1=data[2];unsigned char V1=data[3]; //Y0U0Y1V1  -->[Y0 U0 V1] [Y1 U0 V1]r1 = Y0+1.4075*(V1-128); if(r1>255)r1=255; if(r1<0)r1=0;g1 =Y0- 0.3455 * (U0-128) - 0.7169*(V1-128); if(g1>255)g1=255; if(g1<0)g1=0;b1 = Y0 + 1.779 * (U0-128);  if(b1>255)b1=255; if(b1<0)b1=0;r2 = Y1+1.4075*(V1-128);if(r2>255)r2=255; if(r2<0)r2=0;g2 = Y1- 0.3455 * (U0-128) - 0.7169*(V1-128); if(g2>255)g2=255; if(g2<0)g2=0;b2 = Y1 + 1.779 * (U0-128);  if(b2>255)b2=255; if(b2<0)b2=0;rgbdata[i*6+0]=r1;rgbdata[i*6+1]=g1;rgbdata[i*6+2]=b1;rgbdata[i*6+3]=r2;rgbdata[i*6+4]=g2;rgbdata[i*6+5]=b2;}
}void lcd_show_rgb(unsigned char *rgbdata, int w ,int h)
{unsigned int *ptr = fb_base;  //不要直接对lcd基地址操作以免卡住for(int i = 0; i <h; i++) {for(int j = 0; j < w; j++) {memcpy(ptr+j,rgbdata+j*3,4);//rgb用3个字节表示一个像素点}ptr += lcd_w;rgbdata += w*3;}
}int main(void) 
{fd_fb =  open("/dev/fb0", O_RDWR); //打开LCD文件if(fd_fb < 0){perror("/dev/fb0");exit(-1);}if (ioctl(fd_fb,FBIOGET_VSCREENINFO,&var))  //读取lcd参数{printf("can't get fb_var_screeninfo \n");goto err1;}printf("X:%d  Y:%d  bbp:%d\n",var.xres,var.yres,var.bits_per_pixel);screen_size = var.xres *var.yres *var.bits_per_pixel /8;  //整个Framebuffer大小,bits_per_pixel 表示色深,除以8表示转换单位字节//建立内存映射 方便控制fb_base = (unsigned int*)mmap(NULL,screen_size,PROT_READ|PROT_WRITE,MAP_SHARED, fd_fb,0);if(fb_base == NULL){printf("can't mmap Framebuffer\n");goto err1;}int fd = open("/dev/video1",O_RDWR); //打开摄像头节点,请根据自己的摄像头所在节点修改if (fd < 0){perror("打开设备失败");return -1;}//获取摄像头支持格式 ioctl(文件描述符,命令,与命令对应的结构体)struct v4l2_format vfmt;vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //摄像头采集vfmt.fmt.pix.width = 640; //设置摄像头采集参数,不可以任意设置vfmt.fmt.pix.height = 480;vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //设置视频采集格式 ,根据上一步测得int ret = ioctl(fd,VIDIOC_S_FMT,&vfmt); //写入摄像头参数if (ret < 0){perror("设置格式失败1");}struct v4l2_streamparm Stream_Parm; //定义结构体设置摄像头帧率memset(&Stream_Parm, 0, sizeof(struct v4l2_streamparm));Stream_Parm.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;Stream_Parm.parm.capture.timeperframe.denominator = 15 ;  //分母Stream_Parm.parm.capture.timeperframe.numerator = 1 ;  //分子ret = ioctl(fd,VIDIOC_S_PARM,&Stream_Parm); //写入摄像头帧率if (ret < 0){perror("设置帧率失败");}//以下函数是读取目前摄像头帧率
/*    Stream_Parm.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd,VIDIOC_G_PARM,&Stream_Parm);if (ret < 0){perror("获取帧率失败");}printf("Frame rate: %u/%u\n",Stream_Parm.parm.capture.timeperframe.numerator,Stream_Parm.parm.capture.timeperframe.denominator);
*///申请内核空间struct v4l2_requestbuffers reqbuffer;reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;reqbuffer.count = 4; //申请4个缓冲区reqbuffer.memory = V4L2_MEMORY_MMAP;  //映射方式ret = ioctl(fd,VIDIOC_REQBUFS,&reqbuffer);if (ret < 0){perror("申请空间失败");}//映射unsigned char *mptr[4];//保存映射后用户空间的首地址unsigned int size[4];struct v4l2_buffer mapbuffer;//初始化type和indexmapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;for(int i = 0; i <4;i++) {mapbuffer.index = i;ret = ioctl(fd,VIDIOC_QUERYBUF,&mapbuffer); //从内核空间中查询一个空间作映射if (ret < 0){perror("查询内核空间失败");}//映射到用户空间mptr[i] = (unsigned char *)mmap(NULL,mapbuffer.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,mapbuffer.m.offset);size[i] = mapbuffer.length; //保存映射长度用于后期释放//查询后通知内核已经放回ret = ioctl(fd,VIDIOC_QBUF,&mapbuffer); if (ret < 0){perror("放回失败");}}//开始采集int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;ret = ioctl(fd,VIDIOC_STREAMON,&type); if (ret < 0){perror("开启失败");}//定义一个空间存储解码后的rgbunsigned char rgbdata[640*480*3];while(1){//从队列中提取一帧数据struct v4l2_buffer readbuffer;readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //每个结构体都需要设置type为这个参赛要记住ret = ioctl(fd,VIDIOC_DQBUF,&readbuffer); if (ret < 0){perror("读取数据失败");}//显示在lcd上      yuyv_to_rgb(mptr[readbuffer.index],rgbdata,640,480);//把jpeg数据解码为rgb数据lcd_show_rgb(rgbdata,640,480);//通知内核使用完毕ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);if(ret < 0){perror("放回队列失败");}}//停止采集ret = ioctl(fd,VIDIOC_STREAMOFF,&type);//释放映射for(int i=0; i<4; i)munmap(mptr[i], size[i]);close(fd); //关闭文件return 0;err1:close(fd_fb);return -1;
}

实验结果


使用交叉编译器编译没有问题,使用nfs功能传输到开发板运行。

看得出,图像其实还是偏蓝的,这和图像的色彩矫正有关,感兴趣的同学可以看看如下的文章:

图像处理原理

更多推荐

嵌入式linux之在lcd上显示摄像头图像

本文发布于:2024-03-09 04:49:07,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1723866.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:嵌入式   摄像头   图像   lcd   linux

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!