Linux 下V4l2摄像头采集图片,实现yuyv转RGB,RGB转BMP,RGB伸缩,jpeglib 库实现压缩RGB到内存中,JPEG经UDP发送功

最近自己所在小组做了一个智能家居系统,本人主要负责摄像头图像采集部分,需要完成的功能是实现摄像头数据采集,而且图片需要在LCD上显示,需要经过网络远程发送,自己小白一个,做之前什么都不懂,经历各种查资料请教过后总算出效果了,感触颇深。这期间CSDN上各位大神的各种博客对自己帮助很大,在此一并谢过!!!!!同时也发现很多博客都只包含一个小部分,感觉如果有一个篇完整的介绍可能对新手会有帮助,因此在此简单介绍摄像头采集整个流程。第一次发博客,恳请各位大神多多指教,如有不妥之处,还请见谅。

    废话少说,直接进正题。首先要说明的是我们的摄像头是在ARM Cortex-A8要下运行的,出来的图像结果与PC机上会有一定的不同,请各位注意。

1、驱动支持

    在那位法国牙医的无私奉献下,Linux内核几乎支持所有的USB摄像头,不过要想自己的Linux内核支持USB免驱摄像头,还需要先配置内核,

Device Drivers  --->
  <*> Multimedia support  --->
 
<*>   Video For Linux
 
[ ]   Enable Video For Linux API 1 (DEPRECATED)
  [*]   Video capture adapters  ---> 
      [*]   V4L USB devices  ---> 
     
<*>   USB Video Class (UVC) 
  [*]     UVC input events device support

    这样在板子上插入摄像头后终端就会有显示:

[root@farsight /]# usb 1-1.1: new full speed USB device using s3c2410-ohci and a ddress 4
uvcvideo: Found UVC 1.00 device Webcam C110 (046d:0829)
input: Webcam C110 as /class/input/input2

    同时输入命令:lsusb 也会有相应信息,在此不就不详细展开了,网上有很多资料。最主要的是此时进入/dev 目录下,ls 会新增加一个设备,我的是video0,不同情况下需自己确认,这个设备名很重要。至此,Linux内核对摄像头的驱动支持就没问题了。

2、开始操作摄像头

    经典操作v4l2的方法一共也就那么步,大致为:打开设备->查看设备功能->设置图片格式->申请帧缓冲->内存映射->帧缓冲入列->开始采集->读数据(包括处理数据)->帧缓冲重新入列->关闭设备。看着名字挺霸气的,其实每一步都是调用内核驱动提供的出来的接口就可以了。

2.1 打开设备

 

 
  1. fd = open(dev_name, O_RDWR, 0 );//打开设备文件,阻塞模式

  2. if (fd < 0){

  3. perror("open /dev/video0 fialed! ");

  4. return -1;

  5. }

打开一个open就OK了,注意此处用的是阻塞模式,如果是非阻塞模式(O_NONBLOCK)的话,即使摄像头尚未捕获到信息,驱动依旧会把缓存(DQBUFF)里的东西返回给应用程序,感觉这样有点不合理,也懂内核为何要这样设计。

2.2  查看设备功能

 
  1. <span style="font-size:14px;">struct v4l2_capability cap;

  2. ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);//查看设备功能

  3. if (ret < 0){

  4. perror("requre VIDIOC_QUERYCAP fialed! \n");

  5. return -1;

  6. }

  7. printf("driver:\t\t%s\n",cap.driver);

  8. printf("card:\t\t%s\n",cap.card);

  9. printf("bus_info:\t%s\n",cap.bus_info);

  10. printf("version:\t%d\n",cap.version);

  11. printf("capabilities:\t%x\n",cap.capabilities);

  12.  
  13. if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE){

  14. printf("Device %s: supports capture.\n",dev_name);

  15. }

  16. if ((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING){

  17. printf("Device %s: supports streaming.\n",dev_name);

  18. }</span>

         查看设备功能也没什么好说的,看代码就OK啦。

2.3  设置图片格式

 
  1. struct v4l2_format fmt;

  2. fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

  3. fmt.fmt.pix.width = WIDTH;

  4. fmt.fmt.pix.height = HEIGHT;

  5. fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;

  6. fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

  7. if(-1 == ioctl(fd, VIDIOC_S_FMT, &fmt)){//设置图片格式

  8. perror("set format failed!");

  9. return -1;

  10. }

  11. if(-1 == ioctl(fd, VIDIOC_G_FMT, &fmt)){//得到图片格式

  12. perror("set format failed!");

  13. return -1;

  14. }

  15.  
  16. printf("fmt.type:\t\t%d\n",fmt.type);

  17. printf("pix.pixelformat:\t%c%c%c%c\n", \

  18. fmt.fmt.pix.pixelformat & 0xFF,\

  19. (fmt.fmt.pix.pixelformat >> 8) & 0xFF, \

  20. (fmt.fmt.pix.pixelformat >> 16) & 0xFF,\

  21. (fmt.fmt.pix.pixelformat >> 24) & 0xFF);

  22. printf("pix.width:\t\t%d\n",fmt.fmt.pix.width);

  23. printf("pix.height:\t\t%d\n",fmt.fmt.pix.height);

  24. printf("pix.field:\t\t%d\n",fmt.fmt.pix.field);

      也是一个命令就完成:VIDIOC_S_FMT,其中WIDTH,HEGHT 是定义的宏,后面很多地方都要用这两个参数,定义成宏比传参方便。V4L2_PIX_FMT_YUYV 指定输出格式为YUYV,关于YUYV,RGB等等什么什么格式,网上也有很详细的介绍,比如这篇:谈谈RGB、YUY2、YUYV、YVYU、UYVY、AYUV。
       当然,图片格式设置也不只这3个,还有像帧率什么的也是可以设置的。
       需要注意的是,对于不用的摄像头,内核有不一样的支持,并不是你设置了就一定能用,如果内核中该视频设备驱动不支持你所设定的图像格式,视频驱动会重新修改struct v4l2_format结构体变量的值为该视频设备所支持的图像格式,所以在程序设计中,设定完所有的视频格式后,要获取实际的视频格式,要重新读取 struct v4l2_format结构体变量。
   

2.3.1  查看图片格式

 
  1. if(-1 == ioctl(fd, VIDIOC_G_FMT, &fmt)){//得到图片格式

  2. perror("set format failed!");

  3. return -1;

  4. }

  5.  
  6. printf("fmt.type:\t\t%d\n",fmt.type);

  7. printf("pix.pixelformat:\t%c%c%c%c\n", \

  8. fmt.fmt.pix.pixelformat & 0xFF,\

  9. (fmt.fmt.pix.pixelformat >> 8) & 0xFF, \

  10. (fmt.fmt.pix.pixelformat >> 16) & 0xFF,\

  11. (fmt.fmt.pix.pixelformat >> 24) & 0xFF);

  12. printf("pix.width:\t\t%d\n",fmt.fmt.pix.width);

  13. printf("pix.height:\t\t%d\n",fmt.fmt.pix.height);

  14. printf("pix.field:\t\t%d\n",fmt.fmt.pix.field); <span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;"> </span>

        对于我们组的摄像头来说,设置的WIDTH=320,HEGHT=240,但是重新读取图片格式后,得到的却是 WIDTH=176,HEGHT=144,内核只支持这样,没办法......

2.4 申请帧缓冲

 
  1. <span style="font-size:14px;">req.count = 5;//申请缓冲数量

  2. req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

  3. req.memory = V4L2_MEMORY_MMAP;

  4. ioctl(fd, VIDIOC_REQBUFS, &req);//申请缓冲,

  5. if (req.count < 2){

  6. perror("buffer memory is Insufficient! \n");

  7. return -1;

  8. }</span>

       需要注意的是设定申请帧缓冲数量=5,但不一定就一定有5,一般要求帧缓冲数量大于2,因此有个判断。测试过最多能申请的数量为32。

2.5 映射用户空间

 
  1. <span style="font-size:14px;"> yuyv_buffers0 = calloc(req.count, sizeof(*yuyv_buffers0));//内存中建立对应空间

  2. for (n_buffers = 0; n_buffers < req.count; ++n_buffers){

  3. struct v4l2_buffer buf;//驱动中的一帧

  4. CLEAR(buf);

  5. buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

  6. buf.memory = V4L2_MEMORY_MMAP;

  7. buf.index = n_buffers;

  8.  
  9. if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf)){//映射用户空间

  10. perror("VIDIOC_QUERYBUF error!\n");

  11. return -1;

  12. }

  13.  
  14. yuyv_buffers0[n_buffers].length = buf.length;

  15. yuyv_buffers0[n_buffers].start =(char*) mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);

  16.  
  17. if (MAP_FAILED == yuyv_buffers0[n_buffers].start){

  18. close(fd);

  19. perror("mmap faild! \n");

  20. return -1;

  21. }

  22.  
  23. printf("Frame buffer %d: address = 0x%x, length = %d \n",req.count, (unsigned int)yuyv_buffers0[n_buffers].start, yuyv_buffers0[n_buffers].length);

  24. }</span>

       好像也没什么好说的..........不过这篇博客说得挺详细的,直接引用了,博主勿怪..... 和菜鸟一起学linux之V4L2摄像头应用流程

2.6 申请到的缓冲进入队列

 
  1. <span style="font-size:14px;"> </span><span style="font-size:14px;"> for (i=0; i<n_buffers; ++i){

  2. struct v4l2_buffer buf;

  3. CLEAR(buf);

  4.  
  5. buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

  6. buf.memory = V4L2_MEMORY_MMAP;

  7. buf.index = i;

  8. if ( -1 == ioctl(fd, VIDIOC_QBUF, &buf)){//申请到的缓冲进入队列

  9. close(fd);

  10. perror("VIDIOC_QBUF failed! \n");

  11. return -1;

  12. }

  13. }</span>

       直接看代码......

2.7 开始捕捉图像数据

 
  1. <span style="font-size:14px;"> type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

  2. if (-1 == ioctl(fd, VIDIOC_STREAMON, &type)){//开始捕捉图像数据

  3. close(fd);

  4. perror("VIDIOC_STREAMON failed! ");

  5. exit(-1);

  6. } </span>

        函数执行成功后,摄像头开始采数据,一般来说可以用一个select判断一帧视频数据是否采集完成,当视频设备驱动完成一帧视频数据采集并保存到视频缓冲区中时,select函数返回,应用程序接着可以读取视频数据;否则select函数阻塞直到视频数据采集完成。

 
  1. <span style="font-size:14px;"> </span><span style="font-size:14px;"> enum v4l2_buf_type type;

  2. type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

  3. fd_set fds;

  4. struct timeval tv;

  5. int r;

  6.  
  7. FD_ZERO(&fds);//将指定文件描述符集清空

  8. FD_SET(fd, &fds);//在文件描述符集合中增加一个新的文件描述符

  9. tv.tv_sec = 2;//time out

  10. tv.tv_usec = 0;

  11. r = select(fd+1, &fds, NULL, NULL, &tv);//判断摄像头是否准备好,tv是定时

  12.  
  13. if(-1 == r){

  14. if(EINTR == errno){

  15. printf("select erro! \n");

  16. }

  17. }

  18. else if(0 == r){

  19. printf("select timeout! \n");//超时

  20. return 1;

  21. //exit(EXIT_FAILURE);

  22. }

  23.  
  24. read_frame(); //处理一帧数据</span>

2.8 读数据

 
  1. <span style="font-size:14px;"> file_fd = fopen(path1, "w");//yuyv图片

  2. if (file_fd < 0){

  3. perror("open test_mmap.jpg fialed! \n");

  4. exit(-1);

  5. }

  6.  
  7. CLEAR(buf);

  8. buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

  9. buf.memory = V4L2_MEMORY_MMAP;

  10. ret = ioctl(fd, VIDIOC_DQBUF, &buf);//出列采集的帧缓冲,成功返回0

  11. if(0 != ret){

  12. printf("VIDIOC_DQBUF failed!\n");

  13. exit(-1);<pre name="code" class="objc"> }<pre name="code" class="objc"> ret = fwrite(yuyv_buffers0[buf.index].start, yuyv_buffers0[buf.index].length, 1, file_fd);//将摄像头采集得到的yuyv数据写入文件中<pre name="code" class="objc"> if(ret <= 0){

  14. printf("write yuyv failed!\n");

  15. exit(-1);

  16. }</span><span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;"> </span><span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;"> </span>

 
 
 

       这里有个帧缓冲出列的概念,也就是读取里面的数据并保存置文件中,不过保存的文件是yuyv图片,直接打开自然是不行的,需要用工具YUVViewer.exe,并且工具软件里面的参数配置也必须符合你设定的图片格式,这样才能看到真正的效果。到此为止,恭喜你,已经成功的迈出第一步了。是的,你没听错,才第一步,一张图片要直接在LCD上显示,要远程发送,是不能直接用yuyv数据的,还得经过一系列转换。

2.9 帧缓冲入列

 
  1. <span style="font-size:14px;"> ret = ioctl(fd, VIDIOC_QBUF,&buf);//帧缓冲入列

  2. if(0 != ret){

  3. printf("VIDIOC_QBUF failed!\n");

  4. exit(-1);

  5. }</span>

      读取完帧缓冲里面的数据,别忘了将其入列,方便下次使用。

2.10 关闭设备

 
  1. <span style="font-size:14px;">static void v4l2_close(void)

  2. {

  3. int i=0;

  4. unmap:

  5. for(i=0; i<n_buffers; ++i){

  6. if(-1 == munmap(yuyv_buffers0[i].start, yuyv_buffers0[i].length)){

  7. printf("munmap error! \n");

  8. exit(-1);

  9. }

  10. }

  11. close(fd);

  12. exit(EXIT_SUCCESS);

  13. }</span>

关闭设备一个close就可以了,不过需要注意的是还有一个解除内存映射的工作需要完成。

3.1 yuyv 转RGB

      以上2.1 - 2.10步骤 只是完成一帧数据采集的过程,实际运用中我们需要的是摄像头不停的工作,同时还要对数据进行转换等工作,因此,2.1 - 2.7步只需初始化一次就OK,使用VIDIOC_STREAMON,开始采集数据后2.8,.29要不停地循环进行,最后才是2.10,关闭设备。

      数据的转换等处理主要集中在2.8步,读出数据后就进行相应的转换。

      首先是完成yuyv转RGB。

 

 
  1. <span style="font-size:14px;">void yuyv_to_rgb(unsigned char* yuv,unsigned char* rgb)

  2. {

  3. unsigned int i;

  4. unsigned char* y0 = yuv + 0;

  5. unsigned char* u0 = yuv + 1;

  6. unsigned char* y1 = yuv + 2;

  7. unsigned char* v0 = yuv + 3;

  8.  
  9. unsigned char* r0 = rgb + 0;

  10. unsigned char* g0 = rgb + 1;

  11. unsigned char* b0 = rgb + 2;

  12. unsigned char* r1 = rgb + 3;

  13. unsigned char* g1 = rgb + 4;

  14. unsigned char* b1 = rgb + 5;

  15.  
  16. float rt0 = 0, gt0 = 0, bt0 = 0, rt1 = 0, gt1 = 0, bt1 = 0;

  17.  
  18. for(i = 0; i <= (WIDTH * HEIGHT) / 2 ;i++)

  19. {

  20. bt0 = 1.164 * (*y0 - 16) + 2.018 * (*u0 - 128);

  21. gt0 = 1.164 * (*y0 - 16) - 0.813 * (*v0 - 128) - 0.394 * (*u0 - 128);

  22. rt0 = 1.164 * (*y0 - 16) + 1.596 * (*v0 - 128);

  23.  
  24. bt1 = 1.164 * (*y1 - 16) + 2.018 * (*u0 - 128);

  25. gt1 = 1.164 * (*y1 - 16) - 0.813 * (*v0 - 128) - 0.394 * (*u0 - 128);

  26. rt1 = 1.164 * (*y1 - 16) + 1.596 * (*v0 - 128);

  27.  
  28.  
  29. if(rt0 > 250) rt0 = 255;

  30. if(rt0< 0) rt0 = 0;

  31.  
  32. if(gt0 > 250) gt0 = 255;

  33. if(gt0 < 0) gt0 = 0;

  34.  
  35. if(bt0 > 250) bt0 = 255;

  36. if(bt0 < 0) bt0 = 0;

  37.  
  38. if(rt1 > 250) rt1 = 255;

  39. if(rt1 < 0) rt1 = 0;

  40.  
  41. if(gt1 > 250) gt1 = 255;

  42. if(gt1 < 0) gt1 = 0;

  43.  
  44. if(bt1 > 250) bt1 = 255;

  45. if(bt1 < 0) bt1 = 0;

  46.  
  47. *r0 = (unsigned char)rt0;

  48. *g0 = (unsigned char)gt0;

  49. *b0 = (unsigned char)bt0;

  50.  
  51. *r1 = (unsigned char)rt1;

  52. *g1 = (unsigned char)gt1;

  53. *b1 = (unsigned char)bt1;

  54.  
  55. yuv = yuv + 4;

  56. rgb = rgb + 6;

  57. if(yuv == NULL)

  58. break;

  59.  
  60. y0 = yuv;

  61. u0 = yuv + 1;

  62. y1 = yuv + 2;

  63. v0 = yuv + 3;

  64.  
  65. r0 = rgb + 0;

  66. g0 = rgb + 1;

  67. b0 = rgb + 2;

  68. r1 = rgb + 3;

  69. g1 = rgb + 4;

  70. b1 = rgb + 5;

  71. }

  72. }</span>

       出列帧缓冲后就可以调用该函数,rgb 需要先开辟大小为WIDTH * HEIGTH *  3的空间,因为我们用的RGB是24位格式,3个字节分别代表一个像素点的R、G、B,根据公式转换就好了。

        这里解释一下为什么我们要定义:float rt0 = 0, gt0 = 0, bt0 = 0, rt1 = 0, gt1 = 0, bt1 = 0;  for 开始后的前6个公式是对2个像素点的转换,结果是浮点数,而且也会大于255,小于0,但是每个像素点的每一位范围又只有0到255, 因此紧跟着有6个if判断,做一个范围判定。网上有些程序是直接用unsigned char 型变量做变换计算,完了也有个判断,但是感觉这样的话因为unsigned char都直接带强制转换了,那么后面的if根本就不会执行,不过怎样选择大家可以自己考虑。同时我们的if判断还有个修正,颜色大于250的都直接置为255,这样出来的效果明显要好一点,但是不同的摄像头,结果肯定有不一样,请慎重考虑,多实践。

        说到这里就不得不吐槽一下我们组领的那个摄像头了,罗技的,小巧,效果还不错,但是,别人家的数据出来的颜色排列的是RGB,我们的出来的排列偏偏是BGR,BGR!!!还半天没反应过来!!!!!!!

 

3.2  RGB 转BMP

 
  1. <span style="font-size:14px;">void rgb_to_bmp(unsigned char* pdata, FILE* bmp_fd)

  2. {

  3. //分别为rgb数据,要保存的bmp文件名

  4. int size = WIDTH * HEIGHT * 3 * sizeof(char); // 每个像素点3个字节

  5. // 位图第一部分,文件信息

  6. BMPFILEHEADER_T bfh;

  7. bfh.bfType = (unsigned short)0x4d42; //bm

  8. bfh.bfSize = size // data size

  9. + sizeof( BMPFILEHEADER_T ) // first section size

  10. + sizeof( BMPINFOHEADER_T ) // second section size

  11. ;

  12. bfh.bfReserved1 = 0; // reserved

  13. bfh.bfReserved2 = 0; // reserved

  14. bfh.bfOffBits = sizeof( BMPFILEHEADER_T )+ sizeof( BMPINFOHEADER_T );//真正的数据的位置

  15. // printf("bmp_head== %ld\n", bfh.bfOffBits);

  16. // 位图第二部分,数据信息

  17. BMPINFOHEADER_T bih;

  18. bih.biSize = sizeof(BMPINFOHEADER_T);

  19. bih.biWidth = WIDTH;

  20. bih.biHeight = -HEIGHT;//BMP图片从最后一个点开始扫描,显示时图片是倒着的,所以用-height,这样图片就正了

  21. bih.biPlanes = 1;//为1,不用改

  22. bih.biBitCount = 24;

  23. bih.biCompression = 0;//不压缩

  24. bih.biSizeImage = size;

  25.  
  26. bih.biXPelsPerMeter = 0;//像素每米

  27.  
  28. bih.biYPelsPerMeter = 0;

  29. bih.biClrUsed = 0;//已用过的颜色,为0,与bitcount相同

  30. bih.biClrImportant = 0;//每个像素都重要

  31.  
  32. fwrite( &bfh, 8, 1, bmp_fd);

  33. fwrite(&bfh.bfReserved2, sizeof(bfh.bfReserved2), 1, bmp_fd);

  34. fwrite(&bfh.bfOffBits, sizeof(bfh.bfOffBits), 1, bmp_fd);

  35. fwrite(&bih, sizeof(BMPINFOHEADER_T), 1, bmp_fd);

  36. fwrite(pdata, size, 1, bmp_fd);

  37. } </span>

        说是转,其实就是一个另存为,只是加个格式头而已,需要注意的也只有size, biWidth, biHeight 这几个参数而已,如果你发现你生成的BMP图是倒立的,改下 bih.biHeight = -HEIGHT就OK了。

         需要注意的是对于我们组来说保存位图只是一个测试方法而已,因为这个时候LCD模块还未准备好,保存位图就可以直接在windows上查看结果了,也不需要再用YUVViewer.exe去查看,很方便。但是由于文件操作比较耗时,每读取一帧数据就要操作一次文件的话,帧率下降也非常明显,加上保存位图对于我们这个项目来说明显没什么必要,因此后期的程序中rgb_to_bmp()函数的调用是注释掉了的 ,特此声明,免得挨砖头。

 

3.3 RGB放缩算法

      前面说过,我们的摄像头出来的图像分辨率只有176 * 144大小,内核自己优化成这样,花了一下午时间研究内核中的有关v4l2分辨率设置的源码,奈何自己太菜了,岂是源码的对手?还是老老实实用软件实现图片放大吧。

      图像放缩算法有很多种,各有优点,也难免有各自的缺点,要选择最合适的,实践才是好办法。我们组使用的是最临近插值算法。关于图像放缩算法的讨论,这里又有一篇资料:图像放大算法,这位博主好像也是转载的,那谁原创的呢?

 
  1. void rgb_stretch(char* src_buf, char* dest_buf, int des_width, int des_hight)

  2. {

  3. //最临近插值算法

  4. //双线性内插值算法放大后马赛克很严重 而且帧率下降严重

  5. printf("des_width = %d, des_hight = %d \n ",des_width, des_hight);

  6. double rate_w = (double) WIDTH / des_width;//横向放大比

  7. double rate_h = (double) HEIGHT / des_hight;//轴向放大比

  8.  
  9. int dest_line_size = ((des_width * BITCOUNT +31) / 32) * 4;

  10. int src_line_size = BITCOUNT * WIDTH / 8;

  11. int i = 0, j = 0, k = 0;

  12. for (i = 0; i < des_hight; i++)//desH 目标高度

  13. {

  14. //选取最邻近的点

  15. int t_src_h = (int)(rate_h * i + 0.5);//rateH (double)srcH / desH;

  16. for (j = 0; j < des_width; j++)//desW 目标宽度

  17. {

  18. int t_src_w = (int)(rate_w * j + 0.5);

  19. memcpy(&dest_buf[i * dest_line_size] + j * BITCOUNT / 8, \

  20. &src_buf[t_src_h * src_line_size] + t_src_w * BITCOUNT / 8,\

  21. BITCOUNT / 8);

  22. }

  23. }

  24. }

      也尝试了双线性内插值算法,从原理上分析,应该是双线性内插值算法效果更好,结果却不是这样,为什么是这样,我这种菜鸟也弄不明白......

      放大后的数据就适合320 * 240 的LCD屏了,LCD显示的话,直接将RGB数据放入LCD的帧缓冲就OK了,这里也不详说,毕竟是另一位组员的劳动成果嘛。有了Linux系统,就是方便啊,再也不用苦逼的去写裸机驱动了。
        这里补充一个第2.8步的详细调用:

 
  1. int numb = 0;

  2. static int read_frame(char *rgb_buffers)

  3. {

  4. struct v4l2_buffer buf;

  5. int ret =0;

  6. static char path1[30];

  7. static char path2[30];

  8. FILE *file_fd;//yuyv 图片文件流

  9. FILE *bmp_fd;//bmp 图片文件流

  10.  
  11. numb ++;

  12.  
  13. sprintf(path1, "./test_mmap%d.jpg", numb);//文件名

  14. sprintf(path2, "./image%d.bmp", numb);

  15. printf("path1=%s, path2=%s %d\n", path1, path2, numb);

  16.  
  17. file_fd = fopen(path1, "w");//yuyv图片

  18. if (file_fd < 0){

  19. perror("open test_mmap.jpg fialed! \n");

  20. exit(-1);

  21. }

  22. bmp_fd = fopen(path2, "w");//bmp图片

  23. if (bmp_fd < 0){

  24. perror("open image.bmp failed!");

  25. exit(-1);

  26. }

  27. CLEAR(buf);

  28. buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

  29. buf.memory = V4L2_MEMORY_MMAP;

  30. ret = ioctl(fd, VIDIOC_DQBUF, &buf);//出列采集的帧缓冲,成功返回0

  31. if(0 != ret){

  32. printf("VIDIOC_DQBUF failed!\n");

  33. exit(-1);

  34. }

  35.  
  36. yuyv_to_rgb(yuyv_buffers0[buf.index].start, rgb_buffers);//yuyv -> rgb24

  37.  
  38. rgb_stretch(rgb_buffers, dest_buffers, DEST_WIDTH, DEST_HEIGHT);//176 X 144 -> 320 X 240

  39.  
  40. rgb24_to_rgb565(dest_buffers, rgb565_buffers);//rgb24 -> rgb565

  41.  
  42. ret = fwrite(yuyv_buffers0[buf.index].start, yuyv_buffers0[buf.index].length, 1, file_fd);//将摄像头采集得到的yuyv数据写入文件中

  43. if(ret <= 0){

  44. printf("write yuyv failed!\n");

  45. exit(-1);

  46. }

  47.  
  48. rgb_to_bmp(rgb565_buffers, bmp_fd);//rgb -> bmp

  49.  
  50. ret = ioctl(fd, VIDIOC_QBUF,&buf);//帧缓冲入列

  51. if(0 != ret){

  52. printf("VIDIOC_QBUF failed!\n");

  53. exit(-1);

  54. }

  55. fclose(file_fd);

  56. fclose(bmp_fd);

  57. return 1;

  58. }

这个函数,调用了以上3.1 - 3.3 列举出来的所有函数,当然了,是先放大还是先保存怎样怎样的,大家都可以自己调整。
 

 

3.4 RGB 转JPEG 

 

        如果本文以上所有部分对您来说,都没有任何价值,那既然都到这里了,或许可以继续翻翻?说不定接下来的内容您会很感兴趣哦!
        众说周知,jpeglib 是一个强大的jpeg类库,直接调用里面的一些接口函数,就能直接实现一些异常复杂的jpeg处理。但老版jpeglib 最不好的地方就是只支持文件流的输入输出,不支持从内存中解压或者压缩至内存中,实际工程中哪会处处都是文件操作???因此用起来挺麻烦的,也照着网上的资料,修改了jpeglib的源码,结果渣渣技术,一运行就报段错误,搞了2天,放弃了,请教老师,结果,一句代码就解决了我2天的麻烦。哪句代码?
       首先是编译源码和准备开发环境。
       1.感觉编译jpeglib ,准备环境也没有网上那些资料说得那么简单样,因此在此还是简单介绍下jpeglib的编译以及使用环境的准备。
       下载源码,注意最好是用新版的jpeglib-8b 版,因为老版本貌似是不支持内存中操作的(当然也没下载到老版源码,没实验,有兴趣的同学可以试试),这里有个链接,自己的资源:jpegsrc.v8b.tar.gz
2.解压。

3.配置:
        ./configure CC=/opt/arm-cortex_a8/bin/arm-cortex_a8-linux-gnueabi-gcc LD=/opt/arm-cortex_a8/bin/arm-cortex_a8-linux-gnueabi-ld --host=arm-linux --prefix=/opt/arm-cortex_a8/arm-cortex_a8-linux-gnueabi  --exec-prefix=/opt/arm-cortex_a8/arm-cortex_a8-linux-gnueabi  --enable-shared  --enable-static 
       配置好后,make,没报错,就make install , 千万不能忘记make install  !!!
       make install 成功后会在我的/opt/arm-cortex_a8/arm-cortex_a8-linux-gnueabi/lib下生成5个库:libjpeg.a lalibjpeg.la libjpeg.so libjpeg.so.8 libjpeg.so.8.0.2 ,会在我的/opt/arm-cortex_a8/arm-cortex_a8-linux-gnueabi/include下生成4个头文件:jconfig.h jerror.h jmorecfg.h jpeglib.h, 这几个库和头文件是后面会用到。
       注:(1)针对ARM下使用的jpeglib,CC:交叉编译工具链,CC 后面接的路径就是我的交叉编译器的路径,你的在哪里自己才清楚啊;  LD:链接用,同上; --host:指定主机,得是arm-linux,网上有资料说是arm-unkown-linux, 实测不行;--prefix:生成的头文件存放目录;  --exec-prefix:生成的动态库静态库存放目录,这个很关键,必须得是arm-cortex_a8/arm-cortex_a8-linux-gnueabi;--enable-shared : 用GNU libtool编译成动态链接库 。想强调的一点是以上几个参数,大家最好都配置上,注意严格检查路径,"="前后不要留空格,网上有些资料只说了要配置CC,LD,没说配置host,结果自然还是使用不了。
               (2)如果ARM板子是挂载的根文件系统,那还必须把那5个库拷贝到rootfs/lib 目录下。
               (3)如果你是要在PC机上使用jpeglib 库的话,那么只需把生成库的路径改为/lib 或者 /usr/lib 就行了。
         4.所谓的准备开发环境,一是上面刚刚说的在正确的位置下准备好那5个库,二是还需要把那4个头文件放到你的工程目录下,这样基本上就没问题了。
        心里没底的同学可以先测试下jpeglib库能否使用,只需在一个最简单的.C中,包含<jpeglib.h>,编译时加上-ljpeg ,编译(这个编译器一定得是你上面配置的CC后面跟的那编译器哦!)不报错,那就是真正的没问题了。反之,报XXXXlib 找不到,XXXX.h 找不到,那就得好好检查上面几步了。
库准备好了就可以直接用了:

 
  1. long rgb_to_jpeg(const char *rgb, char *jpeg)

  2. {

  3. long jpeg_size;

  4. struct jpeg_compress_struct jcs;

  5. struct jpeg_error_mgr jem;

  6. JSAMPROW row_pointer[1];

  7. int row_stride;

  8.  
  9. jcs.err = jpeg_std_error(&jem);

  10. jpeg_create_compress(&jcs);

  11.  
  12. jpeg_mem_dest(&jcs, jpeg, &jpeg_size);//就是这个函数!!!!!!!

  13.  
  14. jcs.image_width = WIDTH;

  15. jcs.image_height = HEIGHT;

  16.  
  17. jcs.input_components = 3;//1;

  18. jcs.in_color_space = JCS_RGB;//JCS_GRAYSCALE;

  19.  
  20. jpeg_set_defaults(&jcs);

  21. jpeg_set_quality(&jcs, 180, TRUE);

  22.  
  23. jpeg_start_compress(&jcs, TRUE);

  24. row_stride =jcs.image_width * 3;

  25.  
  26. while(jcs.next_scanline < jcs.image_height){//对每一行进行压缩

  27. row_pointer[0] = &rgb[jcs.next_scanline * row_stride];

  28. (void)jpeg_write_scanlines(&jcs, row_pointer, 1);

  29. }

  30. jpeg_finish_compress(&jcs);

  31. jpeg_destroy_compress(&jcs);

  32.  
  33. #ifdef JPEG //jpeg 保存,测试用

  34. FILE *jpeg_fd;

  35.  
  36. sprintf(path3, "./jpeg%d.jpg", numb);

  37. jpeg_fd = fopen(path3,"w");

  38. if(jpeg_fd < 0 ){

  39. perror("open jpeg.jpg failed!\n");

  40. exit(-1);

  41. }

  42.  
  43. fwrite(jpeg, jpeg_size, 1, jpeg_fd);

  44. close(jpeg_fd);

  45. #endif

  46. return jpeg_size;

  47. }


        上面的代码注释不是很详细,jpeglib 详细使用方法看这篇博客就OK:利用jpeglib压缩图像为jpg格式,想必大家也发现了,是的,如果你用的是jpeg_stdio_dest()函数,那么就是文件操作,最后压缩完成的结果直接保存在文件中,反之,如果你用的是jpeg_mem_dest()函数,那么压缩完成的结果就保存在内存中。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: v4l2是一个用于视频采集和输出设备的Linux内核模块,它提供了一些基本的对视频设备进行控制和操作的能。其yuyv是一种视频格式,它采用了压缩而不是无损的方式来编码视频。 在v4l2,我们可以通过以下步骤来进行yuyv的无损换为bmp格式: 1. 打开视频设备:使用v4l2函数打开视频设备文件,例如/dev/video0。 2. 查询和设置视频设备参数:使用v4l2函数获取并设置视频设备的参数,包括帧大小、格式等。在这个步骤,我们需要设置yuyv作为输入格式。 3. 请求帧缓冲:使用v4l2函数向视频设备请求一块内存作为帧缓冲区,用于存储采集到的视频帧数据。 4. 启动视频流:使用v4l2函数启动视频流,开始采集视频帧数据。 5. 采集视频帧:使用v4l2函数从视频设备读取采集到的视频帧数据,并将其存储在帧缓冲区。 6. 将yuyv格式换为bmp格式:对于每一帧的数据,我们可以根据yuyv的编码规则,进行逐像素的解码。然后,将解码后的RGB像素数据存储在一个新的缓冲区。 7. 将RGB数据写入bmp文件:使用标准C函数,我们可以将RGB像素数据以bmp格式的形式写入一个新的file.bmp文件。 8. 停止视频流和释放资源:使用v4l2函数停止视频流,释放请求的帧缓冲区,关闭视频设备文件。 通过以上步骤,便可以实现v4l2yuyv无损换为bmp格式能。 ### 回答2: v4l2Linux系统用于视频设备的驱动程序框架,支持多种不同的视频格式。其yuyv是一种常见的视频格式,也被称为YUV422。 YUYV是一种压缩格式,其每个像素占据16位(2个字节)的空间。这个格式使用了颜色子采样技术,即每两个像素共享一组颜色样本。这种格式在保留一定图像质量的同时,减少了存储和传输数据的大小。 要将YUYV格式的视频数据无损换为BMP格式的图像,需要以下步骤: 1. 从视频设备获取YUYV格式的原始视频数据。 2. 解压缩YUYV数据,将每个像素的Y、U、V分量分开。其,Y是亮度分量,U、V是色度分量。 3. 根据BMP格式的要求,将YUV分量换为RGB分量。这个过程通常使用色彩空间换算法,如YUV到RGB的矩阵运算。 4. 将RGB分量组装为BMP图像的像素数据,按照BMP文件格式的要求进行排列和存储。 5. 将像素数据保存为BMP格式的文件,以便后续使用或显示。 无损换意味着换后的BMP图像将保持与原始YUYV数据相同的色彩和图像质量。 需要注意的是,以上步骤的具体实现可能因不同的编程语言、和平台而有所差异。可以使用像OpenCV这样的开源图像处理来进行YUYVBMP换操作。通过使用适当的API和函数,可以在编程实现这种无损换过程。 ### 回答3: V4L2是视频4 Linux 2的缩写,是一个用于Linux系统的视频设备驱动程序接口。YUYV是一种被广泛用作视频流格式压缩格式,它将颜色信息和亮度信息进行压缩,从而可以更高效地传输和存储。 要将V4L2YUYV无损换为BMP格式,我们需要了解YUYVBMP格式和数据结构。 首先,V4L2驱动程序会将图像从摄像头读取并以YUYV格式存储。YUYV格式使用4个字节来存储2个像素的数据。每个像素由一个Y(亮度)值和一对U和V(颜色差值)值组成。 接下来,我们需要将YUYV格式的数据解码并换为BMP格式。在这个换过程,我们需要考虑颜色空间的换和像素排列顺序的调整。 首先,我们将YUYV数据的每个像素解码并计算出RGB值。这个过程涉及到颜色空间的换,需要用到YUV到RGB换公式。 然后,我们将解码后的RGB值按照BMP格式的像素排列方式进行调整,即将像素依次排列在内存。 最后,我们将调整后的像素数据写入BMP文件,以生成一个无损换的BMP图像。 需要注意的是,YUYV格式是一种压缩格式换为BMP格式后虽然不会有质量损失,但是图像文件的大小可能会增大。另外,换过程还需要考虑字节对齐等细节问题。 总之,将V4L2YUYV格式无损换为BMP格式需要进行颜色空间换和像素排列调整等步骤。通过这些步骤我们可以得到一个无损的BMP图像文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沐白001

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值