Android用摄像头的那点破事(续)

By | 2011/07/30

摄像头上次讲的是摄像头的初始化,如果觉得这么就万事OK的话,那就大错特错了。接下来的东西让人感到更加头痛。

在我的这个应用里,不需要把拍下来的图片存储,只需要把预览的图片数据处理一下就好,很自然的我只是用了onPreviewFrame调用,考虑处理传递进来的data数据流就是了。

网上很多帖子都说,然后用BitmapFactory的decodeByteArray()函数来解析图片就行了,我试了一下,发现这真是彻头彻尾的谎言,data字节流默认是YCbCr_420_SP(虽然可以改,但其他的格式未必兼容),decodeByteArray()压根儿不认!SDK2.2之后,似乎提供了一个YuvImage的类来转一下(那Google一开始提供这个借口是做什么的?),难道就要把老机给抛弃了么??万万不能啊(穷人最理解穷人们了)!

好在这个世界总是不缺少好人和牛人的,有人提供了这么一段转换的代码:

static public void decodeYUV420SP(int[] rgb, byte[] yuv420sp, int width, int height) {
    final int frameSize = width * height;

    for (int j = 0, yp = 0; j < height; j++) {
        int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
        for (int i = 0; i < width; i++, yp++) {
            int y = (0xff & ((int) yuv420sp[yp])) - 16;
            if (y < 0) y = 0;
            if ((i & 1) == 0) {
                v = (0xff & yuv420sp[uvp++]) - 128;
                u = (0xff & yuv420sp[uvp++]) - 128;
            }

            int y1192 = 1192 * y;
            int r = (y1192 + 1634 * v);
            int g = (y1192 - 833 * v - 400 * u);
            int b = (y1192 + 2066 * u);

            if (r < 0) r = 0; else if (r > 262143) r = 262143;
            if (g < 0) g = 0; else if (g > 262143) g = 262143;
            if (b < 0) b = 0; else if (b > 262143) b = 262143;

            rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
        }
    }
}

我不是很清楚这里面的原理,但是它能在我这里工作,暂时可以了……然后你才可以吧处理完的rgb[]传给decodeByteArray()。

顺便好心的把使用SDK2.2之后的也贴上吧,万一有用呢……

public void onPreviewFrame(byte[] data, Camera arg1) {
    FileOutputStream outStream = null;
    try {
        YuvImage yuvimage = new YuvImage(data,ImageFormat.NV21,arg1.getParameters().getPreviewSize().width,arg1.getParameters().getPreviewSize().height,null);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        yuvimage.compressToJpeg(new Rect(0,0,arg1.getParameters().getPreviewSize().width,arg1.getParameters().getPreviewSize().height), 80, baos);

        outStream = new FileOutputStream(String.format("/sdcard/%d.jpg", System.currentTimeMillis()));        
        outStream.write(baos.toByteArray());
        outStream.close();

        Log.d(TAG, "onPreviewFrame - wrote bytes: " + data.length);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
    }
    Preview.this.invalidate();
}

哦,得到的图像旋转了90°(似乎有的机型设置一下setRotation(90)可以搞定,但还是那句话,不通用啊,况且这个是2.1之后的API)。手动转一下吧……

Matrix matrix = new Matrix(); 
matrix.postRotate(90); 
// 这里的rgb就是刚刚转换处理的东东
Bitmap bmp = Bitmap.createBitmap(rgb, 0, w, w, h, Bitmap.Config.ARGB_4444);
Bitmap nbmp = Bitmap.createBitmap(bmp,
        		0, 0, bmp.getWidth(),  bmp.getHeight(), matrix, true);

终于正常了~~~

考虑到需要做识别,自然得先把它转成灰度图像,经典心理公式Gray = R*0.299 + G*0.587 + B*0.114出场了,但是手机的计算速度不那么快,这样的浮点运算还是尽量避免吧~ 于是考虑Gray = (R*299 + G*587 + B*114 + 500) / 1000或者Gray = (R*30 + G*59 + B*11 + 50) / 100。但是除法总是还是不够快,用移位吧……Gray = (R*19595 + G*38469 + B*7472) >> 16,稍微小一点,用Gray = (R*38 + G*75 + B*15) >> 7也足够了。

经过一番努力学习,把写就的代码兴致勃勃的在手机上跑了一下,虽然不够快结果出来了,想想也是大负荷运算啊,自我安慰客户应该可以有这样的耐心吧。

就在这个时候,我突然想起一件很重要的事情!
我需要的是灰度图,也就是亮度风量,而最开始的YUV,不就是亮度色度饱和度么?!那么Y分类不就是我需要的灰度值吗!!我在做什么,辛辛苦苦转成RGB,再转成亮度,吃饱了撑着不是。想到这里我立刻用头撞墙九九一百八十一次,一悼念我那白白死去的脑细胞的在天之灵。立刻重写,删除大量代码,快多了,效果也好~~ 鄙视一下两小时前的自己!

另外,今天去看变形金刚3了,还不错吧,人好多,坐前排脖子都抬酸了……

23 thoughts on “Android用摄像头的那点破事(续)

  1. myplxdm

    请问一下您有没有发现有的手机不回调onPreviewFrame方法,我在XT800上测试时就不回调,在G1,G2,G13上都正常。请问您有没有解决办法?

    Reply
    1. xishui Post author

      嗯,我也发现了类似的问题,不过手头没有真机,没法调试……

      Reply
  2. 叻ME

    @myplxdm: 1楼设置的回调方是用setPreviewCallbackWithBuffer吗?这个在onPreviewFrame的传入的第一个参数data用完后,用第二个参数camera.addCallbackBuffer(data)一下,否则只会回调一次,因为data数据没有了,以后就不回调了,官方文档是大概这样说的

    Reply
  3. tutu

    请问为什么使用decodeYUV420SP方法所得到的r,g,b值完全不在0~255 之间呢?那个rgb[]数组中的数据更是大得离谱啊! 请问为什么要这样来处理r,g,b呢?
    rgb[yp] = 0xff000000 | ((r <> 2) & 0xff00) | ((b >> 10) & 0xff);
    十分感谢您的帮助!最近我为此苦恼不已啊!

    Reply
  4. 某某某

    求大侠发给俺一份 Android用摄像头的那点破事 中的源码吧,最好是整个工程

    初学乍到,敬请不吝赐教!~

    Reply
  5. ztyhome

    求源码,我想将摄像头读取的数据转换成一个值,反映当前的明亮程度,然后把这些值化成曲线。

    Reply
  6. skyseraph

    请教:
    在onPreviewFrame函数中,这个参数值(data.length)应该是多少?
    我设定的长宽为176*144,而获得的值为38016,请问为什么不是25344(176*144)呢?
    谢谢!
    zgzhaobo@gmail.com

    Reply
    1. xishui Post author

      嗯,您可以研究一下 YCbCr_420_SP (NV21) 就能明白了,初步的说(我不保证正确哦),这个格式一个Y分量(一个字节)分管两对UV分量(各半个字节),也就是两个像素3字节,所以如此。

      Reply
    2. 那是因为摄像头采集出来的是yuv数据,yuv数据是图片宽*高的1.5倍

      Reply
  7. goodboy

    我想知道楼主怎么把处理完的图像回显到屏幕上的?求赐教

    Reply
  8. littleresearcher

    你好,最近在学android的视觉处理,我目前想识别一下camera里的某种特定的颜色的物体,并对其进行跟踪。由于是初学,弄的非常挠头。看了你的分析,觉得我做的东西和你做的有点像,请问可以传一份你的源码给我看看么,学习一下如何使用相关的函数等等,十分感谢。我的邮箱:fred_chen_2008@hotmail.com
    谢谢
    后面可能还有一些问题要问,但是不会是和代码相关的了。
    再次感谢

    Reply
  9. xle

    你好 楼主!我正在做那个,可是传过去的数据解码后在电脑上出现花屏现象,还有怎样把接收来的一帧一帧的图片转成视频格式存在文件夹里?
    希望你能把你的整个代码发过来!xule_chn@foxmail.com 谁要是会的话能不能指导下。尽快的话
    谢谢了!

    Reply
  10. 王充

    大哥求视频通信的源码。这个是我的邮箱icnface@126.com、万分感谢!

    Reply
  11. 小威

    大哥請求源碼…我也想知道到底該如何將處理完的圖像顯示在屏幕上. 請賜教

    我信箱rayallen3001@gmail.com

    Reply
  12. lantuling

    有个问题纠结了很长时间。。。就是如何动态修改data的数据,叫预览出现一些特效 ?

    Reply
  13. 初學者

    求大爺发给一份”Android用摄像头的那点破事”中的源码吧
    初学乍到,敬请不吝赐教!~
    mail:calmsky0827@gmail.com

    Reply
  14. tmp966

    博主,找了2天的android yuv 转RGB565来检测人脸,博文中的灰度图方法不错,能否提供一份源码参考,谢谢。
    邮箱tmp966@126.com

    Reply
    1. xishui Post author

      代码早已经不知去向了,记忆中,因为YCbCr_420_SP第一个字节或者前几位就是Y的值,用位运算就能取出来了。

      Reply

某某某进行回复 取消回复

您的电子邮箱地址不会被公开。