• float类型又称为单精度浮点类型,在 IEEE 754-2008 中是这样定义它的结构的:

      S     EEEEEEEE      FFFFFFFFFFFFFFFFFFFFFFF
    31   30        23    22                               0

    float类型总共4个字节——32位:

    1. 符号位
      其中最左边的为符号位,0为正,1为负。
    2. 指数
      接下来的E是指数,一共8位,也用二进制来表示。
    3. 尾数
      最后的F是小数部分,尾数正是由这23位的小数部分+1位组成的。(这个稍后解释)。

    这里我们需要多说一下指数。虽然指数也是用8位二进制来表示的,但是IEEE在定义它的时候做了些手脚,使用了偏移来计算指数。

    IEEE规定,在float类型中,用来计算指数的偏移量为127。也就是说,如果你的指数实际是0,那么在内存中存的就是0+127=127的二进制。稍后我们来看这个到底如何使用。

    好了,看了这么多,我们该演示一下计算机如何将一个十进制的实数转换为二进制的。就拿6.9这个数字来举例吧。-_-||!

    首先,我们按照上面说的方法,分别将整数和小数转换成对应的二进制。这样6.9的二进制表示就是110.1110011001100...。这里就看出来 了,6.9转换成二进制,小数部分是无限循环的,这在现在的计算机系统上是无法精确表示的。这是计算机在计算浮点数的时候常常不精确的原因之一。

    其次,将小数点左移(或右移)到第一个有效数字之后。说的通俗些,就是把小数点移到第一个1之后。这样的话,对于上面的110.1110011001100...我们就需要把小数点左移2位,得到1.101110011001100...。

    接下来的事情就有意思了。首先我们把得到的1.101110011001100..这个数,从小数点后第一位开始,数出23个来,填充到上面float内存 结构的尾数部分(就是那一堆F的地方),我们这里数出来的就是10111001100110011001100。这里又要发生一次不精确了,小数点后超出 23位的部分都将被舍弃,太惨了。

    不过,这里有一个可能让大家觉得特别坑爹的事情,就是小数点前面的1也不要了。仔细看看上面的内存结构,确实没有地方存放这个1。原因是这样的:IEEE觉 得,既然我们大家都约定把小数点移动到第一个有效数字之后,那也就默认小数点前面一定有且只有一个1,所以把这个1存起来也浪费,干脆就不要了,以后大家 都这么默契的来就好。这也是为什么我上面说尾数是23位+1位的原因。

    填充完尾数,该填充指数了。这个指数就是刚才我们把小数点移动的位数,左移为正,右移为负,再按照上面所说的偏移量算法,我们填充的指数应该是2+127=129。转换成8位二进制就是10000001。

    最后,根据这个数的正负来填充符号位。我们这里是正数,所以填0。这样6.9的在内存中的存储结果就出来了:

    0  10000001  10111001100110011001100

    总结一下,实数转二进制float类型的方法:

    A. 分别将实数的整数和小数转换为二进制
    B. 左移或者右移小数点到第一个有效数字之后
    C. 从小数点后第一位开始数出23位填充到尾数部分
     
    D. 把小数点移动的位数,左移为正,右移为负,加上偏移量127,将所得的和转换为二进制填充到指数部分
    E. 根据实数的正负来填充符号位,0为正,1为负

    摘录自:http://blog.csdn.net/netHibernate/archive/2011/01/06/6120382.aspx

  • 转载时请注明出处和作者联系方式
    文章出处:http://blog.csdn.net/jack0106
    作者联系方式:冯牮 
    fengjian0106@yahoo.com.cn

     

          V4L2  V4L 有较大的改动,并已成为 2.6 的标准接口,函盖 video\ dvb \FM… ,多数驱动都在向 V4l2 迁移  V4L2 采用流水线的方式,操作更简单直观。 一般来说,需要用到的函数就是 open()  close()  ioctl () 

     

          推荐的参考资源:

          (1) Video for Linux Two API Specification---Revision 2.6.32

          http:// linuxtv.org/downloads/video4linux/API/V4L2_API/spec-single/v4l2.html

          (2) capture.c 官方示例程序

     

          说明:强烈建议阅读这两个参考资源!!!如果是初次接触,建议可以先浏览一下第一个参考文档,但是capture.c程序一定要仔细的看一下,至少得先让它能够跑起来(正常运行情况下,在终端下会不断的输出一个 ".")。

          下面的讲解,都将是基于capture.c程序。但是,capture.c程序有一点点小的不足,后面的内容中会介绍到,在末尾,会附上我稍微修改过的一个版本。

     

          对照capture.c程序,值得我们注意的主要是以下几点:  

    (1)3  I/O 方式

          (1.1)read/write,直接使用 read  write 函数进行读写。这种方式最简单,但是这种方式会在 用户空间和内核空间不断拷贝数据 ,同时在用户空间和内核空间占用  大量内存, 效率不高。

         (1.2)mmap ,把硬件设备 里的内存映射 到位于用户空间的应用程序中的内存地址上, 直接处理设备内存,这是一种有效的 方式。

          (1.3) userptr ,内存由用户空间的应用程序分配,并把地址传递到内核中的驱动程序, 然后由 v4l2 驱动程序直接将数据填充到用户空间的内存中。

     

           第一种方式效率是最低的,后面两种方法都能提高执行的效率,但是对于mmap 方式,文档中有这样一句描述 --Remember the buffers are allocated in physical memory, as opposed to virtual memory which can be swapped out to disk. Applications should free the buffers as soon as possible with the munmap () function .(使用mmap方法的时候,buffers相当于是在内核空间中分 配的,这种情况下,这些buffer是不能被交换到虚拟内存中,虽然这种方法不怎么影响读写效率,但是它一直占用着内核空间中的内存,当系统的内存有限的 时候,如果同时运行有大量的进程,则对系统的整体性能会有一定的影响。如果对于这里描述的这些概念还不是很清楚的话,没事,先记住下面给出的结论就行了, 以后再详细的去了解)。

           所以,对于 I/O 方法的选择,推荐的顺序是 userptr  mmap  read-write 

     

    (2) 当使用 mmap  userptr 方式的时候,有一个环形缓冲队列的概念,这个队列中,有 n  buffer ,驱动程序采集到的视频帧数据,就是存储在每个 buffer 中。

          在每次用 VIDIOC_DQBUF 取出一个 buffer ,并且处理完数据后,一定要用 VIDIOC_QBUF 将这个 buffer 再次放回到环形缓冲队列中。

          环形缓冲队列,也使得这两种 I/O 方式的效率高于直接 read/write 

     

     

    ( 3 ) 采集视频的分辨率

           (3.1) Cropping ,裁剪 ( 并非所有的摄像头都支持裁剪功能 )

           (3.2) Scaling ,缩放

     

          下面有个参考图片,注意图片中的 4 个矩形:

    v4l2裁剪示意图

     

    红色 —bounds ,是 最大的能捕捉到的图像 范围。这个是摄像头本身的硬件属性,比如摄像头CCD的分辨率。

    蓝色  defrect ,是 我们的设备能够得到的最大的 范围。要区别于红色的部分,这是在CCD分辨率的基础上,我们的系统通过驱动软件能够获得的最大的分辨率。defrect和bounds可能会有小的差别,也可能是重合的。

    绿色 —crop ,是我们希望裁剪的部分 ( 区别裁剪和缩放 ) 。也就是我我们希望获取CCD中的某个矩形部分。

    紫色  fmt ,伸缩,这一部分是我们最终获得的图像的大小。在使用 VIDIOC_S_FMT 时,驱动程序会计算出图像帧数据的大小,并且返回给我们。 ( 后面还会提到这个 )

     

           如果硬件设备不支持 crop ,则相当于就是直接在 defrect 矩形上面设置大小。可以简单的理解为直接设置期望的视频分辨率。普通的摄像头,通常都不支持crop,在capture.c程序中也能看到对crop的处理方式--如果有crop功能,则crop,否则就跳过,直接使用VIDIOC_S_FMT。

       

    (4) capture.c 程序中的 process_image 函数。

           capture.c 程序主要是用来演示怎样使用 v4l2 接口,并没有对采集到的视频帧数据做任何实际的处理,仅仅用 process_image 函数表示了处理图像的代码位置。

           process_image 函数只有一个参数,就是存储视频帧的内存的地址指针,但是在真正的应用中,通常还需要知道该指针指向的数据的大小。

           因此可以修改函数,改成 void process_image ( const void * p, int len ) ,但是每次调用 process_image 的时候,第 2 个参数该传递什么值?

     

    考虑到程序中对 buffer 的定义

      struct buffer {

      void * start;

      size_t length};

     

           如果将 buffer.length 作为第 2 个参数传递到修改后的 process_image 函数中,这样做是不正确的。 process_image需要的第二个参数应该是每帧图像的大小,仔细阅读代码后会发现, buffer.length 并不一定就等于图像帧的大小。 (buffer 的大小,还需要考虑其他的一些因素,比如内存对齐等 )。

       

    (5) 图像帧的大小和图像的格式  

           首先要明确一点, RGB  YUV 只是两种很笼统的划分方法,还需要知道具体的封装方式,才有办法计算出视频帧数据的实际大小。

          对于YUV而言, YUV 格式通常有两大类:打包( packed )格式和平面( planar )格式。前者将 YUV 分量存放在同一个数组中,通常是几个相邻的像素组成一个宏像素( macro-pixel );而后者使用三个数组分开存放 YUV 三个分量,就像是一个三维平面一样。

           以h.263为例, H.263 编码算法要求图象被编码为一个亮度信号和两个色差成分( Y  Cb  Cr ),可以记为 YCbCr  亮度的取样结构都是 dx  象素每行, dy 行每幅图象。 两个色差成分的取样都是 dx/2 个象素每行, dy /2 行每幅 图象。如下图。

    h.263采样示意图


          H.263编码算法 要求的这种图象格式对应到 v4l2 里面,就是 V4L2_PIX_FMT_YUV420  (YUV  YCbCr  YVU  YCrCb ) 

          V4L2_PIX_FMT_YUV420是一种平坦存储格式,也就是说,在内存中,先存储所有的 Y 值,然后是所有的 Cb 值,最后才是 Cr 值。

          假设有一个 V 4L2_PIX_FMT_ Y UV 420 格式的图像,分辨率是 × 4 像素,那么该图像帧在内存中存储形式就是

      V4L2_PIX_FMT_YUV420 格式的内存示意图

     

     

           根据前面的描述,可以看出一个公式,当使用 V4L2_PIX_FMT_YUV420 格式采集图像的时候,如果图像的宽度为 width ,高度为 height ,那么图像占用的内存的大小就是 imagesize = width height * 3 / 2。

     

           前面提到过, 使用 VIDIOC_S_FMT 时,驱动程序会计算出图像帧数据的大小,并且返回给 我们。当 width = 640  height = 480 的时候,根据公式, imagesize = 640 * 480 * 3 / 2 = 460800 

          同样设置的时候,返回的 fmt.fmt.pix.sizeimage 也是 460800 

          如果是使用其他的格式,都可以根据各种格式的定义来计算它们实际占用的内存的大小以及他们在内 存中的存储方式。

          建议将 Video for Linux Two API Specification 作为手册,里面对视频格式的介绍比较全面。

                                                                                                                                                        

     (6)下面给出修改过的capture.c代码,只改动了一点点

     

    1. #include   
    2. #include   
    3. #include   
  • 2010-12-26

    明德楼露台远眺 - [生活]

  • 2010-12-24

    写给自己 - [生活]

    1、不要生气

    没有任何人、任何事值得你发怒,尤其是别人的事情,这些和你无关。要理智,不要被别人抓住易怒的特点,不要中了他们激怒你的奸计。每当发现自己生气了,就要马上转移,而不是克制和发泄,何况很多事情都是小事一桩。

    永远记住你发怒时眼睛模糊、头脑充血、心跳加速、身体发抖!想更健康地多活十年么?千万不要生气!没有任何人的事情比自己的健康更重要。

    2、管好自己的事

    每个人的思维方式和追求都不同,不要把自己的想法和处世态度强加到别人身上,即使是朋友和爱人,否则会两败俱伤,他们不值得听到你的劝告。

    有功夫先忙乎忙乎自己,让自己早些脱离“泥菩萨”的状态吧。更功利和“自私”些!

  • 2010-12-20

    祝我生日快乐 - [生活]

         周一我过25周岁生日。

          2010年,苏州。

        下午在去吃晚饭的路上,母亲打电话过来在三叮嘱让我明天早晨吃面条和鸡蛋。父亲和母亲在电话里对我说生日快乐,父亲加我不要节省钱,找同学吃些好的,说他赚钱就是让我过的舒服。

          2004年,哈尔滨。

        我过农历生日那天正好和我出生那年的阳历是同一天。晚上坐在十公寓五楼拐角的自习室里,听叶文姐的《新大学时代》,发现那天是毛泽东诞辰111周年。

        大学里的生日我多是很低调的,一般都赶上快考试了忙着复习,另外也被圣诞、元旦淹没了。可能会无奈地站着倚着床铺的铁管,装模作样地抽烟,还尽量不让烟进入肺里,只是摆摆姿势罢了,想想大学抽烟的次数寥寥可数,万宝路,云烟,小熊猫。

          2008年,天津。

        我过生日那天是星期五,我在加班。吃过加班饭后我和同事说今天是我生日,同事惊讶又有些无奈与同情地说了生日快乐,我又继续对着电脑改图纸。当时的朋友下班后乘轻轨从开发区赶来,我原本以为要一起去吃饭,结果发现他穿着工作时的西装,拎着蛋糕站在寒风中的楼口。

        这些事都历历在目,仿佛就发生在昨天,我歇斯底里地功利过,现在又回到了装懵懂的混沌状态,我需要中和一下,变得既理智、功利,又有人情味,珍惜爱我的人和我爱的人,工作学习和生活分开。在新的一岁里,更成熟,更理智,多想想父母,多珍惜现在生活,做一个有良心,有感情,有智慧,有魄力,有头脑的人。