日期:2014年11月1日—2014年11月7日
1.本周已完成任务:解读使用摄像头读取心电图的程序
2.本周未完成任务:项目整体规划与可行性论证
3.下周计划:使用摄像头读出具体的心跳数
4.关键技术点说明:
程序来源:
http://download.csdn.net/download/u010967074/6023749
简单地来说测心跳的原理非常简单,就是使用摄像头与闪光灯观察血液颜色的变化,而这个程序只是将颜色变化显示在屏幕上,并没有测量实际的心跳数,而且写得很混乱、多余。
以下是代码注释
package prox.yuv420sp2rgb;
import java.util.concurrent.atomic.AtomicBoolean;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
public class yuv420sp2rgb extends Activity
{
SurfaceView showDataSurfaceView=null;
SurfaceHolder mSurfaceHolder=null;
int centerY;//中心线
int oldX=0;
int oldY=250;
int newX=0;
int newY=250;//上一个XY 点
int currentX;//当前绘制到的X 轴上的点
int ScreenWidth;
int length=0;
DisplayMetrics dm;
private static String msg = "";
private static final String TAG = "HeartRateMonitor";
private static final AtomicBoolean processing = new AtomicBoolean(false);
private static SurfaceView preview = null;
private static SurfaceHolder previewHolder = null;
private static Camera camera = null;
private static View image = null;
private static ImageView resultimageview=null;
private static TextView text = null;
private static WakeLock wakeLock = null;
private static int averageIndex = 0;
private static final int averageArraySize = 4;
private static final int[] averageArray = new int[averageArraySize];
public static enum TYPE { GREEN, RED };
private static TYPE currentType = TYPE.GREEN;
public static TYPE getCurrent() {
return currentType;
}
private static int beatsIndex = 0;
private static final int beatsArraySize = 3;
private static final int[] beatsArray = new int[beatsArraySize];
private static double beats = 0;
private static long startTime = 0;
static
{
System.loadLibrary("HeartBeatProcessFun");
}
public native int DecodeYUV420SP2RGB(int[] bitmapDataBufJava,byte[] rgbBufJava, byte[] yuv420spJava,int width, int height,int[] RGBYValueJava);
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//联系控件,这里使用了两个SurfaceView,一个用来显示摄像头图像,一个用来显示心跳曲线
preview = (SurfaceView)findViewById(R.id.preview);
previewHolder = preview.getHolder();//获得SurfaceHolder对象
previewHolder.addCallback(surfaceCallback);//添加回调函数
//SURFACE_TYPE_PUSH_BUFFERS:表明该Surface不包含原生数据,Surface用到的数据由其他对象提供
//在Camera图像预览中就使用该类型的Surface,有Camera负责提供给预览Surface数据,这样图像预览会比较流畅。
//如果设置这种类型则就不能调用lockCanvas来获取Canvas对象了。
previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
resultimageview=(ImageView)findViewById(R.id.resultimageview);
//PowerManager,可以参考http://blog.csdn.net/chenzujie/article/details/12906517
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "DoNotDimScreen");
//显示心跳曲线的SurfaceView
showDataSurfaceView=(SurfaceView)findViewById(R.id.showView);
mSurfaceHolder=showDataSurfaceView.getHolder();
centerY=150;
dm = new DisplayMetrics();//DisplayMetrics用来获取屏幕的分辨率,打点打到结束后返回0
getWindowManager().getDefaultDisplay().getMetrics(dm);
ScreenWidth=dm.widthPixels;//屏幕的横向分辨率
}
void drawline()//画线(点)
{
//获取Canvas,锁定画布
Canvas canvas = mSurfaceHolder.lockCanvas(new Rect(oldX, 0, newX,dm.heightPixels));
Paint mPaint=new Paint();
mPaint.setColor(Color.GREEN);//设定为绿色
mPaint.setStrokeWidth(1);//线宽为1像素
canvas.drawLine(oldX, oldY, newX, newY, mPaint);//Canvas绘画
oldX=newX;
oldY=newY;
mSurfaceHolder.unlockCanvasAndPost(canvas);//结束锁定画图,并提交改变,将图形显示
}
void ClearDraw() //清除画布
{
Canvas canvas = mSurfaceHolder.lockCanvas();
canvas.drawColor(Color.BLACK);// 清除画布
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
private Handler DrawHandler = new Handler() //画图Handler
{
public void handleMessage(Message m)
{
//msgEdit.append(msg);
//Log.d("msg",msg);
//画点
drawline();
currentX++;
newX++;
//newY=Integer.valueOf(msg, 16);
//Red的值在0-255之间!!!->大概在215左右变化!!!->稳定之后!!!
//newY=(int) Math.abs(490-((480.0)/255*Integer.valueOf(msg,10)+10));
//newY=Integer.valueOf(msg,10);
//newY=(int) Math.abs(Integer.valueOf(msg,10)-200)*2;
//newY=Integer.valueOf(msg,10);
//Log.d("chenxupro", String.valueOf(newY));
newY=Integer.valueOf(msg,10)*10-2000;//根据msg的值来确定当前的Y坐标
//newY=200;
Log.d("chenxuro", msg);
if(newX==ScreenWidth-1)//当X坐标超过屏幕横向分辨率的时候清除屏幕,然后返回0
{
ClearDraw();
currentX=0;
oldX=0;
newX=0;
}
}
};
//------------------------------------------------------------
@Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
}
@Override
public void onResume()
{
super.onResume();
wakeLock.acquire();
camera = Camera.open();//打开摄像头
startTime = System.currentTimeMillis();
}
@Override
public void onPause()
{
super.onPause();
wakeLock.release();
camera.setPreviewCallback(null);
camera.stopPreview();//停止摄像头
camera.release();
camera = null;
}
private PreviewCallback previewCallback = new PreviewCallback() //摄像头回调
{
@Override
public void onPreviewFrame(byte[] data, Camera cam) //摄像头帧预览
{
if (data == null)
throw new NullPointerException();
Camera.Size size = cam.getParameters().getPreviewSize();
if (size == null)
throw new NullPointerException();
int width = size.width;
int height = size.height;
//关闭摄像头预览回调
camera.setPreviewCallback(null);
int previewWidth=0;
int previewHeight=0;
int[] bitmapData=null;
byte[] rgbBuffer=null;
if (data != null) //获取预览图片的信息
{
previewWidth = camera.getParameters().getPreviewSize().width;
previewHeight = camera.getParameters().getPreviewSize().height;
bitmapData = new int[previewWidth * previewHeight];
rgbBuffer = new byte[previewWidth * previewHeight * 3]; //RGB888!!!->3 bytes
}
//int CurrentRedSum=decodeYUV420SP(bitmapData, rgbBuffer, data, previewWidth, previewHeight);
//int CurrentRedAverage=CurrentRedSum/(previewWidth * previewHeight);
int[] RGBYValueArray=new int[4];
//将图像转换成RGB格式,并获取RGB数组
//这里很奇怪,没有调用自己写的函数,反而使用了JNI的。自己写的函数在上面被注释掉了。
DecodeYUV420SP2RGB(bitmapData,rgbBuffer,data,previewWidth,previewHeight,RGBYValueArray);
msg = String.valueOf(RGBYValueArray[0]);//获取红色部分的数据
DrawHandler.sendEmptyMessage(0);//启动绘图
//Log.d("chenxupro",String.valueOf(CurrentRedSum)+","+String.valueOf(CurrentRedAverage));
//取得摄像头画面
//Bitmap resultImg=Bitmap.createBitmap(w, h, Config.RGB_565);
Bitmap image = Bitmap.createBitmap(bitmapData, previewWidth, previewHeight, Bitmap.Config.ARGB_8888);
Log.d("chenxupro", String.valueOf(previewWidth)+","+String.valueOf(previewHeight));
//image.setPixels(bitmapData, 0, previewWidth, 0, 0,previewWidth, previewHeight);
//resultimageview
resultimageview.setImageBitmap(image);
//打开摄像头预览回调
camera.setPreviewCallback(this);
}
};
private SurfaceHolder.Callback surfaceCallback=new SurfaceHolder.Callback() //SurfaceView回调函数
{
@Override
public void surfaceCreated(SurfaceHolder holder)
{
try
{
camera.setPreviewDisplay(previewHolder);//将camera连接到一个SurfaceView,准备实时预览
camera.setPreviewCallback(previewCallback);//添加回调函数
}
catch (Throwable t)
{
Log.e("PreviewDemo-surfaceCallback", "Exception in setPreviewDisplay()", t);
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
Camera.Parameters parameters = camera.getParameters();//获取camera界限
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);//打开闪光灯
Camera.Size size = getSmallestPreviewSize(width, height, parameters);//得到最小尺寸预览
if (size!=null) //设置预览大小
{
//parameters.setPreviewSize(size.width, size.height);//这里应该才是本意
parameters.setPreviewSize(352, 288);//这里这样前面获取最小尺寸预览显得很莫名其妙
parameters.setPreviewFrameRate(5);//每秒5帧
Log.d(TAG, "Using width="+size.width+" height="+size.height);
}
camera.setParameters(parameters);//设置camera界限
camera.startPreview();//开始预览
}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
//Ignore???
//android.os.Process.killProcess(android.os.Process.myPid());
}
};
//得到最小尺寸预览
private static Camera.Size getSmallestPreviewSize(int width, int height, Camera.Parameters parameters)
{
Camera.Size result=null;
for (Camera.Size size : parameters.getSupportedPreviewSizes())
{
if (size.width<=width && size.height<=height)
{
if (result==null)
{
result=size;
}
else
{
int resultArea=result.width*result.height;
int newArea=size.width*size.height;
if (newArea<resultArea)
result=size;
}
}
}
return result;
}
//获得Red数据和,将yuv420sp转换为rgb和ARGB
static public int decodeYUV420SP(int[] bitmapDataBuf,byte[] rgbBuf, byte[] yuv420sp,int width, int height)
{
int RedSum=0;
final int frameSize = width * height;
if (rgbBuf == null)
throw new NullPointerException("buffer 'rgbBuf' is null");
if (rgbBuf.length < frameSize * 3)
throw new IllegalArgumentException("buffer 'rgbBuf' size "+ rgbBuf.length + " < minimum " + frameSize * 3);
if (yuv420sp == null)
throw new NullPointerException("buffer 'yuv420sp' is null");
//YUV不是分成3个平面而是分成2个平面。Y数据一个平面,UV数据合用一个平面。UV平面的数据格式是UVUVUV...。
if (yuv420sp.length < frameSize * 3 / 2)
throw new IllegalArgumentException("buffer 'yuv420sp' size "
+ yuv420sp.length + " < minimum " + frameSize * 3 / 2);
int i = 0, y = 0;
int uvp = 0, u = 0, v = 0;
int y1192 = 0, r = 0, g = 0, b = 0;
for (int j = 0, yp = 0; j < height; j++) {
uvp = frameSize + (j >> 1) * width;
u = 0;
v = 0;
for (i = 0; i < width; i++, yp++) {
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;
}
y1192 = 1192 * y;
r = (y1192 + 1634 * v);
g = (y1192 - 833 * v - 400 * u);
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;
rgbBuf[yp * 3] = (byte) (r >> 10);
rgbBuf[yp * 3 + 1] = (byte) (g >> 10);
rgbBuf[yp * 3 + 2] = (byte) (b >> 10);
//把原来的rgbbuffer,转成用于生成bitmap的buffer,格式是ARGB
bitmapDataBuf[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
int currentred = (bitmapDataBuf[yp] >> 16) & 0xff;
RedSum+=currentred;
}
}
return RedSum;
}
//获得Red数据和,相比与上面的函数就只有获取数据和
private static int decodeYUV420SPtoRedSum(byte[] yuv420sp, int width, int height)
{
if (yuv420sp==null)
return 0;
final int frameSize = width * height;
int sum = 0;
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;
int pixel = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
int red = (pixel >> 16) & 0xff;
sum+=red;
}
}
return sum;
}
}