因为做竞赛要用到机器视觉,本来想用CM4开源平台但是由于需要很大的计算资源,手机更加合适方便,并且开发周期短,不需要操纵让人心烦的硬件逻辑
本Demo程序是基于安卓摄像头动态提取图像加工后输出到屏幕上的。
这个程序的逻辑很简单,找到图像中某一个颜色区域,比如,找到图像中的红色方块(环境颜色都是蓝色这种容易跟红色分开的色泽)
首先,在Manifest.xml文件中加入
上面这些是开启摄像头允许
然后我们在布局文件中定义两个SurfaceView
为什么是两个surfaceview呢,因为有一个surfaceview是用来预览摄像头的,但是我们需要预览的是经过处理后的图像,因此我们另一个surfaceview来覆盖掉原本用来预览摄像头的surfaceview
下面试MainActivity
public class MainActivity extends Activity implements SurfaceHolder.Callback,PreviewCallback{
private Camera camera;
private SurfaceView view=null;
private SurfaceView surfaceDraw = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = (SurfaceView) findViewById(R.id.surface_view);
view.getHolder().addCallback(this);
surfaceDraw= (SurfaceView) findViewById(R.id.surface_view2);
surfaceDraw.setVisibility(View.VISIBLE);
surfaceDraw.setZOrderOnTop(true);//我们自己的预览窗口放到顶部,这个必须
}
public void surfaceCreated(SurfaceHolder holder) {
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
try
{
camera = Camera.open();//打开摄像头
camera.setPreviewDisplay(holder);
Camera.Parameters parameters = camera.getParameters();
//支持照片格式,这里向调试器打印出所支持的所有照片格式
List supPicFmtList = parameters.getSupportedPictureFormats();
Log.d("LeoHDRTest", "Supported Picutre Format :" );
for(int num = 0; num < supPicFmtList.size(); num++)
{
Integer ISupPicFmt = supPicFmtList.get(num);
switch(ISupPicFmt)
{
case ImageFormat.JPEG:
Log.d("lava", "Current Preview Format is JPEG");
break;
case ImageFormat.NV16:
Log.d("lava", "Current Preview Format is NV16");
break;
case ImageFormat.NV21:
Log.d("lava", "Current Preview Format is NV21");
break;
case ImageFormat.RGB_565:
Log.d("lava", "Current Preview Format is RGB_565");
break;
case ImageFormat.YUY2:
Log.d("lava", "Current Preview Format is YUY2");
break;
case ImageFormat.YV12:
Log.d("lava", "Current Preview Format is YV12");
break;
case ImageFormat.UNKNOWN:
Log.d("lava", "Current Preview Format is UNKNOWN");
break;
default:
Log.d("lava", "Current Preview Format is default : UNKNOWN");
break;
}
}
//支持视频格式,这里向调试器打印出所支持的所有视频格式
List supPreFmtList = parameters.getSupportedPreviewFormats();
Log.d("LeoHDRTest", "Supported Preview Format :");
for(int num = 0; num < supPreFmtList.size(); num++)
{
Integer ISupPreFmt = supPreFmtList.get(num);
switch(ISupPreFmt)
{
case ImageFormat.JPEG:
Log.d("lava", "Current Preview Format is JPEG");
break;
case ImageFormat.NV16:
Log.d("lava", "Current Preview Format is NV16");
break;
case ImageFormat.NV21:
Log.d("lava", "Current Preview Format is NV21");
break;
case ImageFormat.RGB_565:
Log.d("lava", "Current Preview Format is RGB_565");
break;
case ImageFormat.YUY2:
Log.d("lava", "Current Preview Format is YUY2");
break;
case ImageFormat.YV12:
Log.d("lava", "Current Preview Format is YV12");
break;
case ImageFormat.UNKNOWN:
Log.d("lava", "Current Preview Format is UNKNOWN");
break;
default:
Log.d("lava", "Current Preview Format is default : UNKNOWN");
break;
}
}
//获取摄像头支持图片尺寸
List PicSupSizeList = parameters.getSupportedPictureSizes();
Log.d("LeoHDRTest", "Camera Supported Picture Size are :");
for(int num = 0; num < PicSupSizeList.size(); num++)
{
Size PicSupSize = PicSupSizeList.get(num);
Log.d("LeoHDRTest", "< " + num +" >" + PicSupSize.width + " x " + PicSupSize.height);
}
//获取摄像头支持视频尺寸
List supPreviewList = parameters.getSupportedPreviewSizes();
Log.d("LeoHDRTest", "Camera Supported Preview Size are :");
for(int num = 0; num < supPreviewList.size(); num++)
{
Size PicPreSupSize = supPreviewList.get(num);
Log.d("LeoHDRTest", "< " + num +" >" + PicPreSupSize.width + " x " + PicPreSupSize.height);
}
parameters.setPreviewFormat(ImageFormat.NV21);//设置视频的输出格式支持NV21/YV12两种
parameters.setPreviewSize(640, 480);//在这里修改获取图片大小,一定是能支持的
parameters.setPreviewFrameRate(30);//每秒30帧,一般支持5,8,12,15,30,50fps
camera.setParameters(parameters);
camera.setDisplayOrientation(90);//旋转角度,垂直手机用90度
camera.startPreview();//开始预览
camera.setPreviewCallback(this);
}
catch(Exception e){
e.printStackTrace();
Log.e("lava", "vedio print error");
}
}
/*下面是每一帧到来后所进入的method,这里的data为图像原始数据,一般为yuv420(NV21)格式
所以我们在应用机器视觉时一般先把它转化为rgb888*/
public void onPreviewFrame(byte[] data, Camera camera) {
camProcess(data, 640, 480);
}
public void surfaceDestroyed(SurfaceHolder holder) {
if(camera != null) {
camera.release();//需要释放摄像头资源
}
camera = null ;
}
private void camProcess(byte[] data, int width, int height){
int i,j,Y,U,V,r,g,b
int frame_size=width*height;
for(i=0;i> 1) * width + (j& ~1) + 0; //当前像素点v分量的位置
int p_u=frame_size+(i >> 1) * width + (j& ~1) + 1; //当前像素点u分量的位置
int Y=(data[p_y]&0xff);
int V=(data[p_v]&0xff);
int U=(data[p_u]&0xff);
r=(int)((Y)+1.4075*((V)-128));
g=(int)((Y)-0.3455*((U)-128) - 0.7169*((V)-128));
b=(int)((Y)+1.779*((U)-128));
r=r>255?255:r;
r=r<0?0:r;
g=g>255?255:g;
g=g<0?0:g;
b=b>255?255:b;
b=b<0?0:b;
/*此时rgb就是得到这个像素位置的rgb值,下面我没有写处理函数,自己添加*/
}
}
DrawThread thread=new DrawThread(surfaceDraw.getHolder(),后面是自己添加的参数);
thread.start();
}
}
下面添加需要的DrawThread类,该类是我自己定义的,主要是在我们的预览层加入自己绘制的图像(覆盖掉相机原始图像),这里必须要另外开线程才能正常显示
thread.start()方法在需要开启线程的地方开启就行,因为我这里每来一帧画一次后线程结束,把它放在onPreviewFrame方法中
class DrawThread extends Thread{
private SurfaceHolder holder;
public DrawThread(SurfaceHolder holder,自己的其他参数) {
this.holder = holder;
/*其他参数初始化*/
}
@Override
public void run() {
super.run();
Canvas canvas = null;
int counter = 0,i,j;
try {
canvas = holder.lockCanvas();
/*下面就可以实现绘图了*/
canvas.drawColor(Color.BLACK);//这样就先覆盖掉了相机原始图像
holder.unlockCanvasAndPost(canvas);//提交画布
}catch (Exception e) {
e.printStackTrace();
}
}
}
需要用到的包
我这里就把我工程中的包全部贴上去了
import java.util.List;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.Paint;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;