以前,一直没明白yuv和YcbCr之间的差异,想必有些朋友也会有同样的疑惑。所以,我看完之后就记载下来了。
在android上实现基于肤色的皮肤检测的几个技术要点:
(1)android上使用相机预览,包括相机api的使用和surfaceview的应用。
(2)android上相机使用的色彩空间NV12.
(3)NV12是YCrCb的色彩空间,了解YCrCb色彩空间。YCrCb和YUV之间的转换。
yuv色彩模型来源于rgb模型,该模型的特点是将亮度和色度分离开,从而适合于图像处理领域。YCbCr模型来源于yuv模型.
(4)YCrCb色彩空间表示的人类肤色的特征。这个特征是133≤Cr≤173,77≤Cb≤127.实验表 明Cr在[140,160]区间是符合黄种人的肤色。
YUV和RGB的转换:
★这里是不是不是yuv而是Y Cb Cr???★
Y = 0.299 R + 0.587 G + 0.114 B
U = -0.1687 R - 0.3313 G + 0.5 B + 128
V = 0.5 R - 0.4187 G - 0.0813 B + 128
R = Y + 1.402 (V-128)
G = Y - 0.34414 (U-128) - 0.71414 (V-128)
B = Y + 1.772 (U-128)
以前,一直没明白yuv和YcbCr之间的差异,想必有些朋友也会有同样的疑惑。
所以,我看完之后就记载下来了。
一、和rgb之间换算公式的差异
yuv<-->rgb
Y'= 0.299*R' + 0.587*G' + 0.114*B'
U'= -0.147*R' - 0.289*G' + 0.436*B' = 0.492*(B'- Y')
V'= 0.615*R' - 0.515*G' - 0.100*B' = 0.877*(R'- Y')
R' = Y' + 1.140*V'
G' = Y' - 0.394*U' - 0.581*V'
B' = Y' + 2.032*U'
yCbCr<-->rgb
Y= 0.257*R' + 0.504*G' + 0.098*B' + 16
Cb' = -0.148*R' - 0.291*G' + 0.439*B' + 128
Cr' = 0.439*R' - 0.368*G' - 0.071*B' + 128
R' = 1.164*(Y -16) + 1.596*(Cr'-128)
G' = 1.164*(Y -16) - 0.813*(Cr'-128) - 0.392*(Cb'-128)
B' = 1.164*(Y -16) + 2.017*(Cb'-128)
Note: 上面各个符号都带了一撇,表示该符号在原值基础上进行了gamma correction
源代码如下:
001 package com.example.hearrate;
002
003 import java.io.FileNotFoundException;
004 import java.io.FileOutputStream;
005 import java.io.IOException;
006 import java.util.List;
007
008 import android.graphics.Bitmap;
009 import android.graphics.BitmapFactory;
010 import android.graphics.Canvas;
011 import android.graphics.Color;
012 import android.graphics.Paint;
013 import android.graphics.PixelFormat;
014 import android.graphics.PorterDuffXfermode;
015 import android.graphics.Rect;
016 import android.hardware.Camera;
017 import android.hardware.Camera.CameraInfo;
018 import android.hardware.Camera.Size;
019 import android.os.AsyncTask;
020 import android.os.Build;
021 import android.os.Bundle;
022 import android.annotation.SuppressLint;
023 import android.app.Activity;
024 import android.content.res.Configuration;
025 import android.util.Log;
026 import android.view.Menu;
027 import android.view.SurfaceHolder;
028 import android.view.SurfaceView;
029 import android.graphics.PorterDuff;
030 import android.graphics.PorterDuff.Mode;
031 public class MainActivity extends Activity implementsSurfaceHolder.Callback ,Camera.PreviewCallback{
032 SurfaceHolder mHolder;
033 SurfaceView mView;
034 SurfaceView mLayer;
035 SurfaceHolder mLayerHolder;
036 private Camera mCamera =null;
037 private boolean bIfPreview =false;
038 private int mPreviewHeight;
039 private int mPreviewWidth;
040 private Canvas canvas;
041 private Paint paint;
042 private int facex=0,facey=0;
043 private boolean bprocessing=false;
044 private int[] RGBData;
045 private byte[] mYUVData;
046 private boolean bfront=false;
047@Override
048protected void onCreate(Bundle savedInstanceState) {
049super.onCreate(savedInstanceState);
050setContentView(R.layout.activity_main);
051mView=(SurfaceView)findViewById(R.id.layer0);
052paint = new Paint();
053paint.setColor(Color.RED);
054paint.setAntiAlias(true);
055
056mPreviewWidth=320;
057mPreviewHeight=400;
058mHolder=mView.getHolder();
059mHolder.addCallback(this);
060mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
061
062mLayer=(SurfaceView)findViewById(R.id.layer1);
063mLayer.setZOrderOnTop(true);
064//mLayer.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
065
066
067mLayerHolder=mLayer.getHolder();
068mLayerHolder.setFormat(PixelFormat.TRANSPARENT);
069mLayerHolder.addCallback(this);
070mLayerHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
071}
072 void drawlayer1()
073 {
074canvas=mLayerHolder.lockCanvas();
075// canvas.drawRGB(0, 0, 0);
076// canvas.save();
077Bitmap bmp=BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
078//绘制
079// canvas.drawBitmap(bmp, null, paint);
080drawImage(canvas,bmp,facex,facex,72,72,0,0);
081canvas.restore();
082bmp=null;
083mLayerHolder.unlockCanvasAndPost(canvas);
084 }
085@Override
086public boolean onCreateOptionsMenu(Menu menu) {
087// Inflate the menu; this adds items to the action bar if it is present.
088getMenuInflater().inflate(R.menu.activity_main, menu);
089return true;
090}
091
092@Override
093public void surfaceChanged(SurfaceHolder arg0, int arg1, int width, int height) {
094// TODO Auto-generated method stub
095mPreviewWidth=width;
096mPreviewHeight=height;
097if(arg0.equals(mLayerHolder))
098{
099//drawlayer1();
100return;
101}
102
103RGBData= new int[mPreviewHeight* mPreviewWidth];
104mYUVData= new byte[mPreviewHeight* mPreviewWidth+(mPreviewHeight/2)* (mPreviewWidth/2)+(mPreviewHeight/2)* (mPreviewWidth/2)];
105initCamera();
106}
107
108@SuppressLint("NewApi")
109@Override
110public void surfaceCreated(SurfaceHolder arg0) {
111// TODO Auto-generated method stub
112// TODO Auto-generated method stub
113if(arg0.equals(mLayerHolder))
114return;
115 if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.GINGERBREAD)
116 {
117 for(int i=0;i<Camera.getNumberOfCameras();i++)
118 {
119 CameraInfo info=new CameraInfo();
120 Camera.getCameraInfo(i, info);
121 if(info.facing==CameraInfo.CAMERA_FACING_FRONT)
122 {
123//mCamera = Camera.open(i);
124//bfront=true;
125 }
126 }
127 }
128 if(mCamera==null)
129 {
130mCamera = Camera.open();// 开启摄像头(2.3版本后支持多摄像头,需传入参数)
131bfront=false;
132 }
133try
134{
135
136mCamera.setPreviewDisplay(mHolder);//set the surface to be used for live preview
137
138Log("成功打开");
139} catch (Exception ex)
140{
141if(null != mCamera)
142{
143mCamera.release();
144mCamera = null;
145}
146canvas=mHolder.lockCanvas();
147canvas.drawRGB(0, 0, 0);
148canvas.save();
149Bitmap bmp=BitmapFactory.decodeResource(getResources(), R.drawable.bg);
150//绘制
151// canvas.drawBitmap(bmp, null, paint);
152drawImage(canvas,bmp,0,0,mPreviewWidth,mPreviewHeight,0,0);
153canvas.restore();
154bmp=null;
155 mHolder.unlockCanvasAndPost(canvas);
156Log("打开失败"+ex.getMessage());
157}
158}
159 //GameView.drawImage(canvas, mBitDestTop, miDTX, mBitQQ.getHeight(), mBitDestTop.getWidth(), mBitDestTop.getHeight()/2, 0, 0);
160public static void drawImage(Canvas canvas, Bitmap blt, int x, int y, int w, int h, int bx, int by)
161{//x,y表示绘画的起点,
162Rect src = new Rect();// 图片
163Rect dst = new Rect();// 屏幕位置及尺寸
164//src 这个是表示绘画图片的大小
165src.left = bx;//0,0
166src.top = by;
167src.right = bx + w;// mBitDestTop.getWidth();,这个是桌面图的宽度,
168src.bottom = by + h;//mBitDestTop.getHeight()/2;// 这个是桌面图的高度的一半
169// 下面的 dst 是表示 绘画这个图片的位置
170dst.left = x;//miDTX,//这个是可以改变的,也就是绘图的起点X位置
171dst.top = y;//mBitQQ.getHeight();//这个是QQ图片的高度。 也就相当于 桌面图片绘画起点的Y坐标
172dst.right = x + w;//miDTX + mBitDestTop.getWidth();// 表示需绘画的图片的右上角
173dst.bottom = y + h;// mBitQQ.getHeight() + mBitDestTop.getHeight();//表示需绘画的图片的右下角
174canvas.drawBitmap(blt, src, dst, null);//这个方法第一个参数是图片原来的大小,第二个参数是 绘画该图片需显示多少。也就是说你想绘画该图片的某一些地方,而不是全部图片,第三个参数表示该图片绘画的位置
175
176src = null;
177dst = null;
178}
179@Override
180public void surfaceDestroyed(SurfaceHolder arg0) {
181// TODO Auto-generated method stub
182if(arg0.equals(mLayerHolder))
183return;
184if(null != mCamera)
185{
186mCamera.setPreviewCallback(null); //!!这个必须在前,不然退出出错
187mCamera.stopPreview();
188bIfPreview = false;
189mCamera.release();
190mCamera = null;
191}
192}
193
194
195@Override
196public void onPreviewFrame(byte[] data, Camera camera) {
197// TODO Auto-generated method stub
198Log("going into onPreviewFrame"+data.length);
199
200int imageWidth = camera.getParameters().getPreviewSize().width;
201int imageHeight =camera.getParameters().getPreviewSize().height ;
202//int RGBData[] = new int[imageWidth* imageHeight];
203if(!bprocessing)
204{
205System.arraycopy(data, 0, mYUVData, 0, data.length);
206
207new ProcessTask().execute(mYUVData);
208}
209//decodeYUV420SP(RGBData, mYUVData, imageWidth, imageHeight);
210
211//Bitmap bitmap = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888);
212//bitmap.setPixels(RGBData, 0, imageWidth, 0, 0, imageWidth, imageHeight);
213//FileOutputStream outStream = null;
214// ByteArrayOutputStream baos = new ByteArrayOutputStream();
215
216// outStream = new FileOutputStream(String.format("/sdcard/%d.bmp", System.currentTimeMillis()));
217// outStream.write(bitmap.);
218//outStream.close();
219/*
220FileOutputStream out;
221try {
222String path=String.format("/mnt/sdcard/%d.png", System.currentTimeMillis());
223out = new FileOutputStream(path);
224bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
225out.close();
226} catch (FileNotFoundException e) {
227// TODO Auto-generated catch block
228e.printStackTrace();
229} catch (IOException e) {
230// TODO Auto-generated catch block
231e.printStackTrace();
232}
233*/
234//mYUV420sp = data; // 获取原生的YUV420SP数据
235//int mInitPos= mPreviewWidth*mPreviewHeight;
236
237//if(mYUV420sp.length<=mInitPos+1)
238//return;
239//byte cr=0;
240//int framesize=mInitPos;
241//int uvp=0;
242//int i,j,u=0,v=0,yp = 0;
243//int uvp=framesize+(i>>1)*w+j;
244 //canvas=mLayerHolder.lockCanvas();
245// canvas.drawRGB(0, 0, 0);
246 //canvas.save();
247// Bitmap bmp=BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
248//绘制
249// canvas.drawBitmap(bmp, null, paint);
250// drawImage(canvas,bmp,facex,facex,72,72,0,0);
251
252
253// bmp=null;
254//int RGBData[] = new int[mPreviewHeight* mPreviewWidth];
255//byte[] mYUVData = new byte[mYUV420sp.length];
256//System.arraycopy(mYUV420sp, 0, mYUVData, 0, mYUV420sp.length);
257 /*
258for( i=0,yp = 0;i<mPreviewHeight;i++)
259{
260uvp=framesize+(i>>1)*mPreviewWidth;
261for( j=0;j<mPreviewWidth;j++)
262{
263int y = (0xff & ((int) mYUVData[yp])) - 16;
264if (y < 0) y = 0;
265if((j&1)==0)
266{
267v = (0xff & mYUVData[uvp++]) - 128;
268u = (0xff & mYUVData[uvp++]) - 128;
269}
270// if(133≤Cr≤173,77≤Cb≤127
271if(v>133&&v<173)
272canvas.drawPoint(j, i, paint);
273
274int y1192 = 1192 * y;
275
276int r = (y1192 + 1634 * v);
277
278int g = (y1192 - 833 * v - 400 * u);
279
280int b = (y1192 + 2066 * u);
281
282if (r < 0) r = 0; else if (r > 262143) r = 262143;
283
284if (g < 0) g = 0; else if (g > 262143) g = 262143;
285
286if (b < 0) b = 0; else if (b > 262143) b = 262143;
287//int rgb=0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
288
289//r=(rgb&0x00ff0000)>>4;
290//g=(rgb&0x0000ff00)>>2;
291//b=(rgb&0x000000ff);
292//if(r>200&&g>200&&b>200)
293//canvas.drawPoint(j, i, paint);
294//rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
295}
296
297
298}
299
300
301canvas.restore();
302mLayerHolder.unlockCanvasAndPost(canvas);
303*/
304 /*
305* framesize=w*h;
306* yp=0;
307* for (int i=0;i<h;i++)
308* {
309*uvp=framesize+(i>>1)*w;
310*for(int j=0;j<w;j++,yp++)
311*{
312*int y = (0xff & ((int) yuv420sp[yp])) - 16;
313*if(j&1==0)
314*{
315*v = (0xff & yuv420sp[uvp++]) - 128;
316
317 u = (0xff & yuv420sp[uvp++]) - 128;
318*}
319*
320*
321*}
322* }
323*
324*
325* */
326}
327public void drawdetect()
328{
329canvas=mLayerHolder.lockCanvas();
330
331if(canvas==null)
332return;
333canvas.drawColor(Color.TRANSPARENT);
334Paint p = new Paint();
335//清屏
336p.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
337canvas.drawPaint(p);
338p.setXfermode(new PorterDuffXfermode(Mode.SRC));
339canvas.save();
340canvas.drawBitmap(RGBData, 0, mPreviewWidth, 0, 0, mPreviewWidth, mPreviewHeight, true, p);
341
342canvas.restore();
343mLayerHolder.unlockCanvasAndPost(canvas);
344}
345public void detectwhite(byte[] yuv420sp, int width, int height)
346{
347//检测所有白色
348final int frameSize = width * height;
349
350for (int j = 0, yp = 0; j < height; j++) {
351int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
352for (int i = 0; i < width; i++, yp++) {
353int y = (0xff & ((int) yuv420sp[yp]));
354if (y < 0) y = 0;
355if ((i & 1) == 0) {
356v = (0xff & yuv420sp[uvp++]);;
357u = (0xff & yuv420sp[uvp++]);
358}
359///133≤Cr≤173,77≤Cb≤127
360 if(y>250)
361 {
362RGBData[yp]=Color.RED;
363// canvas.drawPoint(i, j, paint);
364}else
365{
366RGBData[yp]=Color.TRANSPARENT;
367}
368
369}
370
371}
372
373}
374public void detectface(byte[] yuv420sp, int width, int height)
375{
376
377final int frameSize = width * height;
378
379for (int j = 0, yp = 0; j < height; j++) {
380int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
381for (int i = 0; i < width; i++, yp++) {
382
383if ((i & 1) == 0) {
384v = (0xff & yuv420sp[uvp++]);;
385u = (0xff & yuv420sp[uvp++]);
386}
387///133≤Cr≤173,77≤Cb≤127
388 if((v)>133&&(v)<160&&(u>77)&&(u<127))
389 {
390RGBData[yp]=Color.RED;
391// canvas.drawPoint(i, j, paint);
392}else
393{
394RGBData[yp]=Color.TRANSPARENT;
395}
396
397}
398
399}
400
401}
402public void decodeYUV420SP(int[] rgb, byte[] yuv420sp, int width, int height) {
403final int frameSize = width * height;
404canvas=mLayerHolder.lockCanvas();
405Paint paint1 = new Paint();
406paint1.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
407canvas.drawPaint(paint1);
408canvas.save();
409for (int j = 0, yp = 0; j < height; j++) {
410int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
411for (int i = 0; i < width; i++, yp++) {
412int y = (0xff & ((int) yuv420sp[yp])) - 16;
413if (y < 0) y = 0;
414if ((i & 1) == 0) {
415v = (0xff & yuv420sp[uvp++]) - 128;
416u = (0xff & yuv420sp[uvp++]) - 128;
417}
418///133≤Cr≤173,77≤Cb≤127
419 if((v)>133&&(v)<160&&(u>77)&&(u<127))
420 {
421canvas.drawPoint(i, j, paint);
422}
423 /*
424* 这个是yuv转RGB的处理
425**/
426int y1192 = 1192 * y;
427int r = (y1192 + 1634 * v);
428int g = (y1192 - 833 * v - 400 * u);
429int b = (y1192 + 2066 * u);
430
431if (r < 0) r = 0; else if (r > 262143) r = 262143;
432if (g < 0) g = 0; else if (g > 262143) g = 262143;
433if (b < 0) b = 0; else if (b > 262143) b = 262143;
434
435rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
436
437r = (rgb[yp] >> 16)&0xff;
438g = (rgb[yp] >> 8) & 0xff;
439b = rgb[yp] & 0xff;
440//if(r==255&&g==255&&b==255)
441// canvas.drawPoint(i, j, paint);
442
443
444}
445
446}
447canvas.restore();
448mLayerHolder.unlockCanvasAndPost(canvas);
449 }
450
451
452private void initCamera()
453{
454if (bIfPreview)
455{
456mCamera.stopPreview();
457}
458if(null != mCamera)
459{
460try{
461Camera.Parameters parameters = mCamera.getParameters();
462// parameters.setFlashMode("off"); // 无闪光灯
463parameters.setPictureFormat(PixelFormat.JPEG); //Sets the image format for picture 设定相片格式为JPEG,默认为NV21
464parameters.setPreviewFormat(PixelFormat.YCbCr_420_SP); //Sets the image format for preview picture,默认为NV21
465
466mCamera.setPreviewCallback(this);
467// 【调试】获取caera支持的PictrueSize,看看能否设置??
468List pictureSizes = mCamera.getParameters().getSupportedPictureSizes();
469List previewSizes = mCamera.getParameters().getSupportedPreviewSizes();
470List previewFormats = mCamera.getParameters().getSupportedPreviewFormats();
471List previewFrameRates = mCamera.getParameters().getSupportedPreviewFrameRates();
472
473Size psize = null;
474for (int i = 0; i < pictureSizes.size(); i++)
475{
476psize = (Size) pictureSizes.get(i);
477
478}
479for (int i = 0; i < previewSizes.size(); i++)
480{
481psize = (Size) previewSizes.get(i);
482
483}
484Integer pf = null;
485for (int i = 0; i < previewFormats.size(); i++)
486{
487pf = (Integer) previewFormats.get(i);
488
489}
490
491// 设置拍照和预览图片大小
492parameters.setPictureSize(640, 480); //指定拍照图片的大小
493parameters.setPreviewSize(mPreviewWidth, mPreviewHeight); // 指定preview的大小
494//这两个属性 如果这两个属性设置的和真实手机的不一样时,就会报错
495if(bfront)
496{
497parameters.set("orientation", "landscape"); //
498parameters.set("rotation", 0); // 镜头角度转90度(默认摄像头是横拍)
499mCamera.setDisplayOrientation(0); // 在2.2以上可以使用
500}
501// 横竖屏镜头自动调整
502/*if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE)
503{
504parameters.set("orientation", "portrait"); //
505parameters.set("rotation", 90); // 镜头角度转90度(默认摄像头是横拍)
506mCamera.setDisplayOrientation(90); // 在2.2以上可以使用
507} else// 如果是横屏
508{
509parameters.set("orientation", "landscape"); //
510mCamera.setDisplayOrientation(0); // 在2.2以上可以使用
511}
512*/
513//添加对视频流处理函数
514// 设定配置参数并开启预览
515mCamera.setParameters(parameters); // 将Camera.Parameters设定予Camera
516mCamera.startPreview(); // 打开预览画面
517bIfPreview = true;
518// 【调试】设置后的图片大小和预览大小以及帧率
519Camera.Size csize = mCamera.getParameters().getPreviewSize();
520mPreviewHeight = csize.height; //
521mPreviewWidth = csize.width;
522
523csize = mCamera.getParameters().getPictureSize();
524
525
526}catch(Exception e)
527{
528Log(e.getMessage());
529}
530
531}
532}
533void Log(String msg)
534{
535System.out.println("LOG:"+msg);
536}
537int[] g_v_table,g_u_table,y_table;
538int[][] r_yv_table,b_yu_table;
539int inited = 0;
540
541
542void initTable()
543{
544g_v_table=new int[256];
545g_u_table=new int[256];
546y_table=new int[256];
547r_yv_table=new int[256][256];
548b_yu_table=new int[256][256];
549if (inited == 0)
550{
551inited = 1;
552int m = 0,n=0;
553for (; m < 256; m++)
554{
555g_v_table[m] = 833 * (m - 128);
556g_u_table[m] = 400 * (m - 128);
557y_table[m] = 1192 * (m - 16);
558}
559int temp = 0;
560for (m = 0; m < 256; m++)
561for (n = 0; n < 256; n++)
562{
563temp = 1192 * (m - 16) + 1634 * (n - 128);
564if (temp < 0) temp = 0; else if (temp > 262143) temp = 262143;
565r_yv_table[m][n] = temp;
566
567temp = 1192 * (m - 16) + 2066 * (n - 128);
568if (temp < 0) temp = 0; else if (temp > 262143) temp = 262143;
569b_yu_table[m][n] = temp;
570}
571}
572}
573public class ProcessTask extends AsyncTask<byte[], Void, Void>
574{
575
576@Override
577protected void onPostExecute(Void result) {
578// TODO Auto-generated method stub
579super.onPostExecute(result);
580drawdetect();
581bprocessing=false;
582}
583
584@Override
585protected void onPreExecute() {
586// TODO Auto-generated method stub
587super.onPreExecute();
588if(bprocessing)
589this.cancel(true);
590
591}
592
593@Override
594protected Void doInBackground(byte[]... params) {
595// TODO Auto-generated method stub
596bprocessing=true;
597byte[] data=params[0];
//皮肤检测
detectface(data,mPreviewWidth, mPreviewHeight);
//白色检测
//detectwhite(data,mPreviewWidth, mPreviewHeight);
//publishProgress(null);
return null;
}
}
}
以下是layout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<SurfaceView
android:id="@+id/layer0"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<SurfaceView
android:id="@+id/layer1"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>
</RelativeLayout>
以下是manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hearrate"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.CAMERA"/>
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- 往SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.camera.autofocus"/>
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.hearrate.MainActivity"
android:label="@string/app_name"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="landscape"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>