日期: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;
}
}