利用手机摄像头的心电图测试是如何实现的?

3 Answers

个人直觉和P-sensor有关!
2014-06-19 21:54:17
打开任意手机的摄像头,把手指紧贴摄像头,然后对准灯光,这时就可以发现原理了.. 程序就是不断的采集图片,不断的处理图片
2014-06-19 21:54:09
基本可以确定是通过摄像头采集指尖部分的图像数据,然后通过医学意义上的图像模式去匹配采集到的数据,简单的说就是一个图像处理的过程。指尖的血液循环和脉搏无非体现在图像颜色和形状的变化。上google上找到了一个简单例子的源码,供参考。

主activity


package com.jwetherell.heart_rate_monitor;

import java.util.concurrent.atomic.AtomicBoolean; 

import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.TextView;

/**
* This class extends Activity to handle a picture preview, process the preview for a red values 
* and determine a heart beat.
* 
* @author Justin Wetherell <phishman3579@gmail.com>
*/
public class HeartRateMonitor extends Activity {
    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 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;

/**
* {@inheritDoc}
*/
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        preview = (SurfaceView)findViewById(R.id.preview);
        previewHolder = preview.getHolder();
        previewHolder.addCallback(surfaceCallback);
        previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

        image = findViewById(R.id.image);
        text = (TextView) findViewById(R.id.text);

PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "DoNotDimScreen");
    }

/**
* {@inheritDoc}
*/
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
    }

/**
* {@inheritDoc}
*/
    @Override
    public void onResume() {
        super.onResume();

        wakeLock.acquire();
        
        camera = Camera.open();
        
        startTime = System.currentTimeMillis();
    }

/**
* {@inheritDoc}
*/
    @Override
    public void onPause() {
        super.onPause();

        wakeLock.release();
        
        camera.setPreviewCallback(null);
        camera.stopPreview();
        camera.release();
        camera = null;
    }

    private static PreviewCallback previewCallback = new PreviewCallback() {
        /**
         * {@inheritDoc}
         */
        @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();

            if (!processing.compareAndSet(false, true)) return;
            
            int width = size.width;
            int height = size.height;

            int imgAvg = ImageProcessing.decodeYUV420SPtoRedAvg(data.clone(), height, width);
            Log.i(TAG, "imgAvg="+imgAvg);
            if (imgAvg==0 || imgAvg==255) {
                processing.set(false);
                return;
            }
            
            int averageArrayAvg=0;
            int averageArrayCnt=0;
            for (int i=0; i<averageArray.length; i++) {
                if (averageArray[i]>0) {
                    averageArrayAvg += averageArray[i];
                    averageArrayCnt++;
                }
            }

            int rollingAverage = (averageArrayCnt>0)?(averageArrayAvg/averageArrayCnt):0;
            TYPE newType = currentType;
            if (imgAvg<rollingAverage) {
                newType = TYPE.RED;
                if (newType!=currentType) {
                    beats++;
                    Log.e(TAG, "BEAT!! beats="+beats);
                }
            } else if (imgAvg>rollingAverage) {
                newType = TYPE.GREEN;
            }
            
            if (averageIndex==averageArraySize) averageIndex = 0;
            averageArray[averageIndex] = imgAvg;
            averageIndex++;
            
            //Transitioned from one state to another to the same
            if (newType!=currentType) {
                currentType=newType;
                image.postInvalidate();
            }
            
            long endTime = System.currentTimeMillis();
            double totalTimeInSecs = (endTime-startTime)/1000d;
            if (totalTimeInSecs>=10) {
                double bps = (beats/totalTimeInSecs);
                int dpm = (int)(bps*60d);
                if (dpm<30 || dpm>180) {
                    startTime = System.currentTimeMillis();
                    beats = 0;
                    processing.set(false);
                    return;
                }
                
                Log.e(TAG, "totalTimeInSecs="+totalTimeInSecs+" beats="+beats);

                if (beatsIndex==beatsArraySize) beatsIndex = 0;
                beatsArray[beatsIndex] = dpm;
                beatsIndex++;
                
                int beatsArrayAvg=0;
                int beatsArrayCnt=0;
                for (int i=0; i<beatsArray.length; i++) {
                    if (beatsArray[i]>0) {
                        beatsArrayAvg += beatsArray[i];
                        beatsArrayCnt++;
                    }
                }
                int beatsAvg = (beatsArrayAvg/beatsArrayCnt);
                text.setText(String.valueOf(beatsAvg));
                startTime = System.currentTimeMillis();
                beats = 0;
            }
            processing.set(false);
        }
    };

    private static SurfaceHolder.Callback surfaceCallback=new SurfaceHolder.Callback() {    
        /**
         * {@inheritDoc}
         */
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            try {
                camera.setPreviewDisplay(previewHolder);
                camera.setPreviewCallback(previewCallback);
            } catch (Throwable t) {
                Log.e("PreviewDemo-surfaceCallback", "Exception in setPreviewDisplay()", t);
            }
        }
        
        /**
         * {@inheritDoc}
         */
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            Camera.Parameters parameters = camera.getParameters();
            parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
            Camera.Size size = getSmallestPreviewSize(width, height, parameters);
            if (size!=null) {
                parameters.setPreviewSize(size.width, size.height);
                Log.d(TAG, "Using width="+size.width+" height="+size.height);
            }
            camera.setParameters(parameters);
            camera.startPreview();
        }
        
        /**
         * {@inheritDoc}
         */
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            // Ignore
        }
    };

    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;
    }
}

界面部分 

package com.jwetherell.heart_rate_monitor;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

/**
* This class extends the View class and is designed draw the heartbeat image.
* 
* @author Justin Wetherell <phishman3579@gmail.com>
*/
public class HeartbeatView extends View {
    private static final Matrix matrix = new Matrix();
    private static final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    
    private static Bitmap greenBitmap = null;
    private static Bitmap redBitmap = null;
    
    private static int parentWidth = 0;
    private static int parentHeight = 0;

public HeartbeatView(Context context, AttributeSet attr) {
super(context,attr);

greenBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.green_icon);
redBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.red_icon);
}

public HeartbeatView(Context context) {
super(context);

greenBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.green_icon);
redBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.red_icon);
}

/**
* {@inheritDoc}
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);

parentWidth = MeasureSpec.getSize(widthMeasureSpec);
parentHeight = MeasureSpec.getSize(heightMeasureSpec);
setMeasuredDimension(parentWidth, parentHeight);
}

/**
* {@inheritDoc}
*/
@Override
protected void onDraw(Canvas canvas) {
    if (canvas==null) throw new NullPointerException();

    Bitmap bitmap = null;
if (HeartRateMonitor.getCurrent()==HeartRateMonitor.TYPE.GREEN) bitmap = greenBitmap;
else bitmap = redBitmap;

int bitmapX = bitmap.getWidth()/2;
int bitmapY = bitmap.getHeight()/2;

int parentX = parentWidth/2;
int parentY = parentHeight/2;

int centerX = parentX-bitmapX;
int centerY = parentY-bitmapY;
    
matrix.reset();
matrix.postTranslate(centerX, centerY);
canvas.drawBitmap(bitmap, matrix, paint);
}
}
图形处理部分,注意到其处理红颜色的部分,估计是手指表面血液显示的颜色测量的值。
package com.jwetherell.heart_rate_monitor; /** * This abstract class is used to process images. * * @author Justin Wetherell <phishman3579@gmail.com> */ public abstract class ImageProcessing { 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; } /** * Given a byte array representing a yuv420sp image, determine the average amount of red in the image. * Note: returns 0 if the byte array is NULL. * * @param yuv420sp Byte array representing a yuv420sp image * @param width Width of the image. * @param height Height of the image. * @return int representing the average amount of red in the image. */ public static int decodeYUV420SPtoRedAvg(byte[] yuv420sp, int width, int height) { if (yuv420sp==null) return 0; final int frameSize = width * height; int sum = decodeYUV420SPtoRedSum(yuv420sp,width,height); return (sum/frameSize); } }
2011-01-31 22:16:12
您不能回答该问题或者回答已经关闭!

相关文章推荐

  • C#中using指令的几种用法

    using + 命名空间名字,这样可以在程序中直接用命令空间中的类型,而不必指定类型的详细命名空间,类似于Java的import,这个功能也是最常用的,几乎每个cs的程序都会用到

  • C#实例解析适配器设计模式

    将一个类的接口变成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够一起工作

  • 使用托管C++粘合C#和C++代码(二)

    本文实现一下C++代码调用C#代码的过程。我构造一个简单并且直观的例子:通过C++ UI 触发C# UI.

  • C#开发高性能Log Help类设计开发

    项目中要在操作数据库的异常处理中加入写Log日志,对于商业上有要求,写log时对其它操作尽可能影响小,不能因为加入log导致耗时太多

  • Async和Await使异步编程更简单

    C#5.0中async和await两个关键字,这两个关键字简化了异步编程,之所以简化了,还是因为编译器给我们做了更多的工作

  • C#开发中的反射机制

    反射的定义:审查元数据并收集关于它的类型信息的能力。元数据(编译以后的最基本数据单元)就是一大堆的表,当编译程序集或者模块时,编译器会创建一个类定义表,一个字段定义表,和一个方法定义表等

  • C#运行时相互关系

    C#运行时相互关系,包括运行时类型、对象、线程栈和托管堆之间的相互关系,静态方法、实例方法和虚方法的区别等等

  • C#协变和逆变

    “协变”是指能够使用与原始指定的派生类型相比,派生程度更大的类型,“逆变”则是指能够使用派生程度更小的类型

  • C#基础概念之延迟加载

    延迟加载(lazy load)是Hibernate3关联关系对象默认的加载方式,延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作

  • 使用托管C++粘合C#和C++代码(一)

    C#在xml读写,数据库操纵,界面构造等很多方面性能卓越;C++的效率高,是底层开发的必备武器

  • C#中的索引器的简单理解和用法

    C#中的类成员可以是任意类型,包括数组和集合。当一个类包含了数组和集合成员时,索引器将大大简化对数组或集合成员的存取操作

  • 深入C# 序列化(Serialize)、反序列化(Deserialize)

    C#中的序列化和反序列化,序列化是.NET运行时环境用来支持用户定义类型的流化的机制