Android用摄像头的那点破事

前篇:

好早就装了开发环境,真正着手还是这两天,非常的生疏,虽然有SDK文档,那么多蚊子一般的字,实在没心思慢慢研究。这不想调用摄像头,原以为很容易就能搞定的,累计花了大概有一天的时间才只能保证不出错……至于效果嘛,难说啊!

先看API-examples里有调用 摄像头的例子,在模拟器上虽然看不出什么效果,毕竟还是能执行的,就是一个方块在黑白相间的背景上移动呗。

就这么一个Google提供的范例,传到我的HTC G2上也能一执行就报错,我对Google的尊敬之情顿时减少了0.0001%啊……(当然有可能是G2不够标准,但毕竟其他的软件都是能用的,看来是有不少健壮代码了啊)。联机调试看了一下,出错的这一行(android-7里的):

parameters.setPreviewSize(w, h);

查一下,摄像头不是所有随便的(w, h)都能够认识的,所以呢,我们有了下面这样的增强版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
List<Size> mSupportedPreviewSizes;
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
 
private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
         final double ASPECT_TOLERANCE = 0.1 ;
         double targetRatio = ( double ) w / h;
         if (sizes == null ) return null ;
 
         Size optimalSize = null ;
         double minDiff = Double.MAX_VALUE;
 
         int targetHeight = h;
 
         // Try to find an size match aspect ratio and size
         for (Size size : sizes) {
             double ratio = ( double ) size.width / size.height;
             if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue ;
             if (Math.abs(size.height - targetHeight) < minDiff) {
                 optimalSize = size;
                 minDiff = Math.abs(size.height - targetHeight);
             }
         }
 
         // Cannot find the one match the aspect ratio, ignore the requirement
         if (optimalSize == null ) {
             minDiff = Double.MAX_VALUE;
             for (Size size : sizes) {
                 if (Math.abs(size.height - targetHeight) < minDiff) {
                     optimalSize = size;
                     minDiff = Math.abs(size.height - targetHeight);
                 }
             }
         }
         return optimalSize;
     }

后来的Sample里有了这段代码,看起来强大了不少。然而非常不幸的,首先getSupportedPreviewSizes()这个函数在2.1之后才有,我一开始是打算用1.6开发的……好吧我改,这个先不说,自己的手机已经刷到2.1了,这个函数的返回值居然是null?!如果确实想老版本上也用的话,怎么办??

有鉴于有软件可以达成,所以肯定是有方法的!得这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class SupportedSizesReflect {
     private static Method Parameters_getSupportedPreviewSizes = null ;
     private static Method Parameters_getSupportedPictureSizes = null ;
 
     static {
         initCompatibility();
     };
 
     private static void initCompatibility() {
         try {
             Parameters_getSupportedPreviewSizes = Camera.Parameters. class .getMethod(
                     "getSupportedPreviewSizes" , new Class[] {});
 
             Parameters_getSupportedPictureSizes = Camera.Parameters. class .getMethod(
                     "getSupportedPictureSizes" , new Class[] {});
 
         } catch (NoSuchMethodException nsme) {
             nsme.printStackTrace();
             Parameters_getSupportedPreviewSizes = Parameters_getSupportedPictureSizes = null ;
         }
     }
 
     /**
      * Android 2.1之后有效
      * @param p
      * @return Android1.x返回null
      */
     public static List<Size> getSupportedPreviewSizes(Camera.Parameters p) {
         return getSupportedSizes(p, Parameters_getSupportedPreviewSizes);
     }
 
     public static List<Size> getSupportedPictureSizes(Camera.Parameters p){
         return getSupportedSizes(p, Parameters_getSupportedPictureSizes);
     }   
 
     @SuppressWarnings ( "unchecked" )
     private static List<Size> getSupportedSizes(Camera.Parameters p, Method method){
         try {
             if (method != null ) {
                 return (List<Size>) method.invoke(p);
             } else {
                 return null ;
             }
         } catch (InvocationTargetException ite) {
             Throwable cause = ite.getCause();
             if (cause instanceof RuntimeException) {
                 throw (RuntimeException) cause;
             } else if (cause instanceof Error) {
                 throw (Error) cause;
             } else {
                 throw new RuntimeException(ite);
             }
         } catch (IllegalAccessException ie) {
             return null ;
         }
     }
}

啊啊~,リフレクションなんか、大嫌い……然后还要用类似这样的方法调用~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
 
     Camera.Parameters params = camera.getParameters();
 
     List<Size> supportedPictureSizes
                 = SupportedSizesReflect.getSupportedPictureSizes(params);
     List<Size> supportedPreviewSizes
                 = SupportedSizesReflect.getSupportedPreviewSizes(params);
 
     if ( supportedPictureSizes != null &&
         supportedPreviewSizes != null &&
         supportedPictureSizes.size() > 0 &&
         supportedPreviewSizes.size() > 0 ) {
 
         //2.x
         pictureSize = supportedPictureSizes.get( 0 );
 
         int maxSize = 1280 ;
         if (maxSize > 0 ){
             for (Size size : supportedPictureSizes){
                 if (maxSize >= Math.max(size.width,size.height)){
                     pictureSize = size;
                     break ;
                 }
             }
         }
 
         WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         Display display = windowManager.getDefaultDisplay();
         DisplayMetrics displayMetrics = new DisplayMetrics();
         display.getMetrics(displayMetrics);
 
         previewSize = getOptimalPreviewSize(
                             supportedPreviewSizes,
                             display.getWidth(),
                             display.getHeight());
 
         params.setPictureSize(pictureSize.width, pictureSize.height);
         params.setPreviewSize(previewSize.width, previewSize.height);                               
 
     }
     this .camera.setParameters(params);
     try {
         this .camera.setPreviewDisplay(holder);
     } catch (IOException e) {
         e.printStackTrace();
     }
     this .camera.startPreview();
}

死机无数次之后总结出来的啊,发现程序写的一个不好强制结束了,摄像头都无法再次启用了,kill都不行,只能重新启动手机才好。重启一次还那么慢,谁知道有比较适合G2的row?

哦还有一个,预览画面90°的,2.X后可以用parameters.set(“rotation”, “90″),之前的话得写成parameters.set(“orientation”, “portrait”)。但是据说不是所有的机器都可以的……


续篇:

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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之后的也贴上吧,万一有用呢……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)。手动转一下吧……

1
2
3
4
5
6
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了,还不错吧,人好多,坐前排脖子都抬酸了……

转自:http://eyehere.net/2011/android-camera-2/


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值