V4L2图像采集+图片格式转换(YUYV、RGB、JPEG) | 您所在的位置:网站首页 › 怎么转换为jpeg格式 › V4L2图像采集+图片格式转换(YUYV、RGB、JPEG) |
2.5 映射用户空间 yuyv_buffers0 = calloc(req.count, sizeof(*yuyv_buffers0));//内存中建立对应空间 for (n_buffers = 0; n_buffers < req.count; ++n_buffers){ struct v4l2_buffer buf;//驱动中的一帧 CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = n_buffers; if (-1 == ioctl(fd, VIDIOC_QUERYBUF, &buf)){//映射用户空间 perror("VIDIOC_QUERYBUF error!\n"); return -1; } yuyv_buffers0[n_buffers].length = buf.length; yuyv_buffers0[n_buffers].start =(char*) mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); if (MAP_FAILED == yuyv_buffers0[n_buffers].start){ close(fd); perror("mmap faild! \n"); return -1; } printf("Frame buffer %d: address = 0x%x, length = %d \n",req.count, (unsigned int)yuyv_buffers0[n_buffers].start, yuyv_buffers0[n_buffers].length); }2.6 申请到的缓冲进入队列 for (i=0; i 250) bt0 = 255; if(bt0 < 0) bt0 = 0; if(rt1 > 250) rt1 = 255; if(rt1 < 0) rt1 = 0; if(gt1 > 250) gt1 = 255; if(gt1 < 0) gt1 = 0; if(bt1 > 250) bt1 = 255; if(bt1 < 0) bt1 = 0; *r0 = (unsigned char)rt0; *g0 = (unsigned char)gt0; *b0 = (unsigned char)bt0; *r1 = (unsigned char)rt1; *g1 = (unsigned char)gt1; *b1 = (unsigned char)bt1; yuv = yuv + 4; rgb = rgb + 6; if(yuv == NULL) break; y0 = yuv; u0 = yuv + 1; y1 = yuv + 2; v0 = yuv + 3; r0 = rgb + 0; g0 = rgb + 1; b0 = rgb + 2; r1 = rgb + 3; g1 = rgb + 4; b1 = rgb + 5; } }出列帧缓冲后就可以调用该函数,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 void rgb_to_bmp(unsigned char* pdata, FILE* bmp_fd) { //分别为rgb数据,要保存的bmp文件名 int size = WIDTH * HEIGHT * 3 * sizeof(char); // 每个像素点3个字节 // 位图第一部分,文件信息 BMPFILEHEADER_T bfh; bfh.bfType = (unsigned short)0x4d42; //bm bfh.bfSize = size // data size + sizeof( BMPFILEHEADER_T ) // first section size + sizeof( BMPINFOHEADER_T ) // second section size ; bfh.bfReserved1 = 0; // reserved bfh.bfReserved2 = 0; // reserved bfh.bfOffBits = sizeof( BMPFILEHEADER_T )+ sizeof( BMPINFOHEADER_T );//真正的数据的位置 // printf("bmp_head== %ld\n", bfh.bfOffBits); // 位图第二部分,数据信息 BMPINFOHEADER_T bih; bih.biSize = sizeof(BMPINFOHEADER_T); bih.biWidth = WIDTH; bih.biHeight = -HEIGHT;//BMP图片从最后一个点开始扫描,显示时图片是倒着的,所以用-height,这样图片就正了 bih.biPlanes = 1;//为1,不用改 bih.biBitCount = 24; bih.biCompression = 0;//不压缩 bih.biSizeImage = size; bih.biXPelsPerMeter = 0;//像素每米 bih.biYPelsPerMeter = 0; bih.biClrUsed = 0;//已用过的颜色,为0,与bitcount相同 bih.biClrImportant = 0;//每个像素都重要 fwrite( &bfh, 8, 1, bmp_fd); fwrite(&bfh.bfReserved2, sizeof(bfh.bfReserved2), 1, bmp_fd); fwrite(&bfh.bfOffBits, sizeof(bfh.bfOffBits), 1, bmp_fd); fwrite(&bih, sizeof(BMPINFOHEADER_T), 1, bmp_fd); fwrite(pdata, size, 1, bmp_fd); }说是转,其实就是一个另存为,只是加个格式头而已,需要注意的也只有size, biWidth, biHeight 这几个参数而已,如果你发现你生成的BMP图是倒立的,改下bih.biHeight = -HEIGHT就OK了。 需要注意的是对于我们组来说保存位图只是一个测试方法而已,因为这个时候LCD模块还未准备好,保存位图就可以直接在windows上查看结果了,也不需要再用YUVViewer.exe去查看,很方便。但是由于文件操作比较耗时,每读取一帧数据就要操作一次文件的话,帧率下降也非常明显,加上保存位图对于我们这个项目来说明显没什么必要,因此后期的程序中rgb_to_bmp()函数的调用是注释掉了的 ,特此声明,免得挨砖头。 3.3 RGB放缩算法 前面说过,我们的摄像头出来的图像分辨率只有176 * 144大小,内核自己优化成这样,花了一下午时间研究内核中的有关v4l2分辨率设置的源码,奈何自己太菜了,岂是源码的对手?还是老老实实用软件实现图片放大吧。 图像放缩算法有很多种,各有优点,也难免有各自的缺点,要选择最合适的,实践才是好办法。我们组使用的是最临近插值算法。关于图像放缩算法的讨论,这里又有一篇资料:图像放大算法,这位博主好像也是转载的,那谁原创的呢? void rgb_stretch(char* src_buf, char* dest_buf, int des_width, int des_hight) { //最临近插值算法 //双线性内插值算法放大后马赛克很严重 而且帧率下降严重 printf("des_width = %d, des_hight = %d \n ",des_width, des_hight); double rate_w = (double) WIDTH / des_width;//横向放大比 double rate_h = (double) HEIGHT / des_hight;//轴向放大比 int dest_line_size = ((des_width * BITCOUNT +31) / 32) * 4; int src_line_size = BITCOUNT * WIDTH / 8; int i = 0, j = 0, k = 0; for (i = 0; i < des_hight; i++)//desH 目标高度 { //选取最邻近的点 int t_src_h = (int)(rate_h * i + 0.5);//rateH (double)srcH / desH; for (j = 0; j < des_width; j++)//desW 目标宽度 { int t_src_w = (int)(rate_w * j + 0.5); memcpy(&dest_buf[i * dest_line_size] + j * BITCOUNT / 8, \ &src_buf[t_src_h * src_line_size] + t_src_w * BITCOUNT / 8,\ BITCOUNT / 8); } } }也尝试了双线性内插值算法,从原理上分析,应该是双线性内插值算法效果更好,结果却不是这样,为什么是这样,我这种菜鸟也弄不明白...... 放大后的数据就适合320 * 240 的LCD屏了,LCD显示的话,直接将RGB数据放入LCD的帧缓冲就OK了,这里也不详说,毕竟是另一位组员的劳动成果嘛。有了Linux系统,就是方便啊,再也不用苦逼的去写裸机驱动了。 这里补充一个第2.8步的详细调用: int numb = 0; static int read_frame(char *rgb_buffers) { struct v4l2_buffer buf; int ret =0; static char path1[30]; static char path2[30]; FILE *file_fd;//yuyv 图片文件流 FILE *bmp_fd;//bmp 图片文件流 numb ++; sprintf(path1, "./test_mmap%d.jpg", numb);//文件名 sprintf(path2, "./image%d.bmp", numb); printf("path1=%s, path2=%s %d\n", path1, path2, numb); file_fd = fopen(path1, "w");//yuyv图片 if (file_fd < 0){ perror("open test_mmap.jpg fialed! \n"); exit(-1); } bmp_fd = fopen(path2, "w");//bmp图片 if (bmp_fd < 0){ perror("open image.bmp failed!"); exit(-1); } CLEAR(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; ret = ioctl(fd, VIDIOC_DQBUF, &buf);//出列采集的帧缓冲,成功返回0 if(0 != ret){ printf("VIDIOC_DQBUF failed!\n"); exit(-1); } yuyv_to_rgb(yuyv_buffers0[buf.index].start, rgb_buffers);//yuyv -> rgb24 rgb_stretch(rgb_buffers, dest_buffers, DEST_WIDTH, DEST_HEIGHT);//176 X 144 -> 320 X 240 rgb24_to_rgb565(dest_buffers, rgb565_buffers);//rgb24 -> rgb565 ret = fwrite(yuyv_buffers0[buf.index].start, yuyv_buffers0[buf.index].length, 1, file_fd);//将摄像头采集得到的yuyv数据写入文件中 if(ret bmp ret = ioctl(fd, VIDIOC_QBUF,&buf);//帧缓冲入列 if(0 != ret){ printf("VIDIOC_QBUF failed!\n"); exit(-1); } fclose(file_fd); fclose(bmp_fd); return 1; }这个函数,调用了以上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中,包含,编译时加上-ljpeg ,编译(这个编译器一定得是你上面配置的CC后面跟的那编译器哦!)不报错,那就是真正的没问题了。反之,报XXXXlib 找不到,XXXX.h 找不到,那就得好好检查上面几步了。 库准备好了就可以直接用了: long rgb_to_jpeg(const char *rgb, char *jpeg) { long jpeg_size; struct jpeg_compress_struct jcs; struct jpeg_error_mgr jem; JSAMPROW row_pointer[1]; int row_stride; jcs.err = jpeg_std_error(&jem); jpeg_create_compress(&jcs); jpeg_mem_dest(&jcs, jpeg, &jpeg_size);//就是这个函数!!!!!!! jcs.image_width = WIDTH; jcs.image_height = HEIGHT; jcs.input_components = 3;//1; jcs.in_color_space = JCS_RGB;//JCS_GRAYSCALE; jpeg_set_defaults(&jcs); jpeg_set_quality(&jcs, 180, TRUE); jpeg_start_compress(&jcs, TRUE); row_stride =jcs.image_width * 3; while(jcs.next_scanline < jcs.image_height){//对每一行进行压缩 row_pointer[0] = &rgb[jcs.next_scanline * row_stride]; (void)jpeg_write_scanlines(&jcs, row_pointer, 1); } jpeg_finish_compress(&jcs); jpeg_destroy_compress(&jcs); #ifdef JPEG //jpeg 保存,测试用 FILE *jpeg_fd; sprintf(path3, "./jpeg%d.jpg", numb); jpeg_fd = fopen(path3,"w"); if(jpeg_fd < 0 ){ perror("open jpeg.jpg failed!\n"); exit(-1); } fwrite(jpeg, jpeg_size, 1, jpeg_fd); close(jpeg_fd); #endif return jpeg_size; }上面的代码注释不是很详细,jpeglib 详细使用方法看这篇博客就OK:利用jpeglib压缩图像为jpg格式,想必大家也发现了,是的,如果你用的是jpeg_stdio_dest()函数,那么就是文件操作,最后压缩完成的结果直接保存在文件中,反之,如果你用的是jpeg_mem_dest()函数,那么压缩完成的结果就保存在内存中。 注:再次声明,我用的jpeglib 是-8b 版本,老版本是否支持jpeg_mem_dest() 函数,我没验证过.......... 这里有一个以上功能的完整版程序,完整版与上面的代码片段有稍许不同,请注意。Linux 下V4l2摄像头采集图片,实现yuyv转RGB,RGB转BMP,RGB伸缩,RGB转JPEG(保存到内存中),JPEG经UDP发送功能,或者您也可以直接到我的资源中去下载。 到此为止,大功告成!!! |
CopyRight 2018-2019 实验室设备网 版权所有 |