Android图片识别应用做法

最近由于参加一个小小的创意比赛,用安卓做了一个小小的图片识别应用,主要是通过拍照识别图片中的菜品,还有对象位置查找的东西。之前没有做过安卓,都是拼拼凑凑多篇博客完成的,我也把这个项目的一些过程分享一下。先把功能贴一下,其实就是点击拍照,将照片保存在本地,然后识别出图中的菜品,然后用红色方框圈出来,并显示菜品种类。采用最新的Camera2的API,的确是比Camera好用。

Android图片识别应用做法_第1张图片

Android图片识别应用做法_第2张图片

1、界面

我采用了一个SurfaceView用来显示摄像头的预览画面,重写了一个SurfaceView来进行红色方框还有菜品名字的绘制。图片是一个ImageVIew,相当于拍照按钮的功能。



SVDraw,,继承SurfaceView,用于绘制红色方框
package com.hd.hd;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.util.List;

/*定义一个画矩形框的类*/
public class SVDraw extends SurfaceView implements SurfaceHolder.Callback{

    protected SurfaceHolder sh;
    private int mWidth;
    private int mHeight;
    public SVDraw(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub  
        sh = getHolder();
        sh.addCallback(this);
        sh.setFormat(PixelFormat.TRANSPARENT);
        setZOrderOnTop(true);
    }

    public void surfaceChanged(SurfaceHolder arg0, int arg1, int w, int h) {
        // TODO Auto-generated method stub  

    }

    public void surfaceCreated(SurfaceHolder sh) {
        // TODO Auto-generated method stub

        mWidth = this.getWidth();
        mHeight = this.getHeight();

    }

    public void surfaceDestroyed(SurfaceHolder arg0) {
        // TODO Auto-generated method stub  

    }
    void clearDraw()
    {
        Canvas canvas = sh.lockCanvas();
        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        sh.unlockCanvasAndPost(canvas);
    }


    public void drawLine(List keys, List values)
    {
        Canvas canvas = sh.lockCanvas();
        canvas.drawColor(Color.TRANSPARENT);
        Paint p = new Paint();
        p.setAntiAlias(true);
        p.setColor(Color.RED);
        p.setStrokeWidth(6);
        p.setStyle(Paint.Style.STROKE);//设置空心
        p.setTextSize(160);

        Paint p1 = new Paint();
        p1.setColor(Color.WHITE);
        p1.setTextSize(80);

        for(int i = 0;i < keys.size();i++){
            String v = values.get(i);
            v = v.replace("[","");
            v = v.replace("]","");
            String[] value = v.split(",");
            canvas.drawRect(mWidth - Integer.parseInt(value[3]), Integer.parseInt(value[0]), mHeight - Integer.parseInt(value[1]), Integer.parseInt(value[2]), p);// 正方形
            canvas.drawText(keys.get(i), mWidth - Integer.parseInt(value[3]), Integer.parseInt(value[0])-5, p1);

        }
        sh.unlockCanvasAndPost(canvas);

    }

}

2、上传图片到服务器,我没有采用JSon的格式,而是直接将图片文件转化为字节数组,发送给服务器。使用一个异步任务,完成后,直接在onPostExcute()方法里绘制。
package com.hd.hd;

import android.os.AsyncTask;
import android.util.Log;
import android.widget.TextView;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;

/**
 * Created by asus on 2017/8/13.
 */
public class MyTask extends AsyncTask {

    private static String TAG = "MainActivity";
    private File file;  //需要发送的图片
    private String result_content;  //服务器返回的结果
    private SVDraw surfaceView;     //需要绘制的surfaceview
    private TextView tv;        //显示文字
    private static final int TIME_OUT = 10 * 1000; // 超时时间
    private static final String CHARSET = "utf-8"; // 设置编码

    public MyTask(File f,SVDraw s,TextView tv){
        this.file = f;
        this.surfaceView = s;
        this.tv = tv;
    }


    @Override
    protected void onPreExecute() {

    }

    //doInBackground方法内部执行后台任务,不可在此方法内修改UI
    @Override
    protected String doInBackground(String... params) {
        //调用文件上传方法
        result_content = uploadFile(file,"http://13.76.211.62/");

        return null;
    }

    //onProgressUpdate方法用于更新进度信息
    @Override
    protected void onProgressUpdate(Integer... progresses) {

    }

    //onPostExecute方法用于在执行完后台任务后更新UI,显示结果
    @Override
    protected void onPostExecute(String result) {
        //由于返回的是一个python的字典形式的字符串,用json来解析
        JSONObject obj = null;
        List keys = new ArrayList();
        List values = new ArrayList();
        try {
            obj = new JSONObject(result_content);
            //json对象的Key的迭代器,用来遍历json
            Iterator it = obj.keys();
            while (it.hasNext()) {
                String key = (String) it.next();
                String value = obj.getString(key);
                keys.add(key);
                values.add(value);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        //绘制图形
        surfaceView.clearDraw();
        surfaceView.drawLine(keys,values);
        tv.setText("搭配很赞哦");

    }

    //onCancelled方法用于在取消执行中的任务时更改UI
    @Override
    protected void onCancelled() {

    }

    /**
     * 上传图片文件到服务器
     * @param file
     * @param RequestURL
     * @return
     */
    public static String uploadFile(File file, String RequestURL) {
        String result = null;
        String BOUNDARY = UUID.randomUUID().toString(); // 边界标识 随机生成
        String PREFIX = "--", LINE_END = "\r\n";
        String CONTENT_TYPE = "multipart/form-data"; // 内容类型


        try {
            //创建URL连接,指明连接地址
            URL url = new URL(RequestURL);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

            //设置http请求的属性为POST
            conn.setReadTimeout(TIME_OUT);
            conn.setConnectTimeout(TIME_OUT);
            conn.setDoInput(true); // 允许输入流
            conn.setDoOutput(true); // 允许输出流
            conn.setUseCaches(false); // 不允许使用缓存
            conn.setRequestMethod("POST"); // 请求方式
            conn.setRequestProperty("Charset", CHARSET); // 设置编码
            conn.setRequestProperty("connection", "keep-alive");
            conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary=" + BOUNDARY);


            if (file != null) {
                /**
                 * 当文件不为空,把文件包装并且上传
                 */
                Log.i(TAG,"upload");
                DataOutputStream dos = new DataOutputStream(conn.getOutputStream());


                Log.e(TAG,"not null");
                /**
                 * 这里重点注意: name里面的值为服务端需要key 只有这个key 才可以得到对应的文件
                 * filename是文件的名字,包含后缀名的 比如:abc.png
                 */

                InputStream is = new FileInputStream(file);
                byte[] bytes = new byte[1024];
                int len;
                while ((len = is.read(bytes)) != -1) {
                    dos.write(bytes, 0, len);
                }
                is.close();
                dos.flush();
                Log.e(TAG,"sent");

                /**
                 * 获取响应码 200=成功 当响应成功,获取响应的流
                 */
                int res = conn.getResponseCode();
                Log.e(TAG, "response code:" + res);

                Log.e(TAG, "request success");
                InputStream input = conn.getInputStream();

                StringBuffer sb1 = new StringBuffer();
                int ss;
                while ((ss = input.read()) != -1) {
                    sb1.append((char) ss);
                }
                result = sb1.toString();
                Log.e(TAG, "result : " + result);

            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }
}

3、初始化界面、照相机,使得照相机能够实时预览,并实现拍照功能
package com.hd.hd;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.media.Image;
import android.media.ImageReader;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;

public class MainActivity extends AppCompatActivity{

    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
    private String TAG = "MainActivity";

    ///为了使照片竖直显示
    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    private SurfaceView mSurfaceView;
    private SurfaceHolder mSurfaceHolder;
    private CameraManager mCameraManager;//摄像头管理器
    private Handler childHandler, mainHandler;
    private String mCameraID;//摄像头Id 0 为后  1 为前
    private ImageReader mImageReader;
    private CameraCaptureSession mCameraCaptureSession;
    private CameraDevice mCameraDevice;
    private SVDraw hSurfaceView;
    private MyTask myTask;
    private CaptureRequest.Builder captureRequestBuilder;
    private TextView tv;
    private final int DRAW_ORDER = 10;
    private Handler myHandler;
    private ImageView imageView;
    private String dir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Healthy_d/";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //此步骤非常重要,安卓不用自动帮你创建文件夹来保存拍照的照片
        File dirFirstFolder = new File(dir);//方法二:通过变量文件来获取需要创建的文件夹名字
        if(!dirFirstFolder.exists())
        { //如果该文件夹不存在,则进行创建
            dirFirstFolder.mkdirs();//创建文件夹

        }

        //Android 6后有些敏感的权限不能随意分配,必须向用户发送请求赋予
        //这里请求用户赋予拍照,读写内存卡,连接网络的权限,其实只有拍照权限需要向用户请求,但是有备无患吧
        if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            Log.e(TAG,ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)+"");
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    43);
        }

        if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                    44);
        }

        if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.INTERNET},
                    45);
        }

        initVIew();

    }

    /**
     * 初始化视图
     */
    private void initVIew() {
        HandlerThread handlerThread = new HandlerThread("Camera2");
        handlerThread.start();
        childHandler = new Handler(handlerThread.getLooper());
        //mSurfaceView
        mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView);
        hSurfaceView = (SVDraw) findViewById(R.id.mySurfaceView);
        imageView = (ImageView) findViewById(R.id.btngal);
        tv = (TextView)findViewById(R.id.textview);

        //设置ImageView监听器,点击图片,拍照
        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "正在识别,请稍等", Toast.LENGTH_LONG).show();
                if (mCameraDevice == null) return;
                // 创建拍照需要的CaptureRequest.Builder
                try {
                    captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                    // 将imageReader的surface作为CaptureRequest.Builder的目标
                    captureRequestBuilder.addTarget(mImageReader.getSurface());
                    // 自动对焦
                    captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                    // 自动曝光
                    captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                    // 获取手机方向
                    int rotation = getWindowManager().getDefaultDisplay().getRotation();
                    // 根据设备方向计算设置照片的方向
                    captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
                    //拍照
                    CaptureRequest mCaptureRequest = captureRequestBuilder.build();
                    mCameraCaptureSession.capture(mCaptureRequest, mSessionCaptureCallback, childHandler);

                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            }
        });

        mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceHolder.setKeepScreenOn(true);
        // mSurfaceView添加回调
        mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) { //SurfaceView创建
                // 初始化Camera
                initCamera2();
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) { //SurfaceView销毁
                // 释放Camera资源
                if (null != mCameraDevice) {
                    mCameraDevice.close();
                    mCameraDevice = null;
                }
            }
        });
    }

    //拍照时,可以对照片进行操作,这里可以不写,因为我没对其进行操作
    private CameraCaptureSession.CaptureCallback mSessionCaptureCallback =
            new CameraCaptureSession.CaptureCallback() {

                @Override
                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
                                               TotalCaptureResult result) {}

                @Override
                public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
                                                CaptureResult partialResult){}};

    /**
     * 初始化Camera2
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void initCamera2() {
        HandlerThread handlerThread = new HandlerThread("Camera2");
        handlerThread.start();
        childHandler = new Handler(handlerThread.getLooper());
        mainHandler = new Handler(getMainLooper());
        mCameraID = "" + CameraCharacteristics.LENS_FACING_FRONT;//后摄像头
        mImageReader = ImageReader.newInstance(mSurfaceView.getWidth(), mSurfaceView.getHeight(), ImageFormat.JPEG,1);
        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { //可以在这里处理拍照得到的临时照片 例如,写入本地
            @Override
            public void onImageAvailable(ImageReader reader) {

                Image image = reader.acquireNextImage();
                ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);//由缓冲区存入字节数组
                Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
                String fileName = "test";
                File file = new File(dir + fileName + ".jpg");
                String state = Environment.getExternalStorageState();
                //如果状态不是mounted,无法读写
                if (!state.equals(Environment.MEDIA_MOUNTED)) {
                    return;
                }

                FileOutputStream out = null;
                try {
                    out = new FileOutputStream(file);
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);//转化为jpeg图片
                    out.flush();
                    out.close();
                    image.close();//一定要记得关,否则会出现程序崩溃
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                new MyTask(file,hSurfaceView,tv).execute();

            }
        }, mainHandler);
        //获取摄像头管理
        mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        try {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.CAMERA},
                        42);
            }
            //打开摄像头
            mCameraManager.openCamera(mCameraID, stateCallback, mainHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 当发送权限请求用户响应时,回调该函数
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == 42) {

            Toast.makeText(this, "CAMERA PERMISSION GRANTED", Toast.LENGTH_SHORT).show();
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //申请成功,可以拍照
                Log.i(TAG,"apply camera success");
                CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
                try {
                    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                        Toast.makeText(this, "CAMERA PERMISSION DENIED", Toast.LENGTH_SHORT).show();
                    }
                    mCameraManager.openCamera(mCameraID, stateCallback, mainHandler);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            } else {
                Toast.makeText(this, "CAMERA PERMISSION DENIED", Toast.LENGTH_SHORT).show();
            }
            return;
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }


    /**
     * 摄像头创建监听
     */
    private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {//打开摄像头
            mCameraDevice = camera;
            //开启预览
            takePreview();

        }

        @Override
        public void onDisconnected(CameraDevice camera) {//关闭摄像头
            if (null != mCameraDevice) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
        }

        @Override
        public void onError(CameraDevice camera, int error) {//发生错误
            Toast.makeText(MainActivity.this, "摄像头开启失败", Toast.LENGTH_SHORT).show();
        }
    };

    /**
     * 开始预览
     */
    private void takePreview() {
        try {

            // 创建预览需要的CaptureRequest.Builder
            final CaptureRequest.Builder previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            // 将SurfaceView的surface作为CaptureRequest.Builder的目标
            previewRequestBuilder.addTarget(mSurfaceHolder.getSurface());
//            previewRequestBuilder.addTarget(mImageReader.getSurface());
            // 创建CameraCaptureSession,该对象负责管理处理预览请求和拍照请求
            mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceHolder.getSurface(), mImageReader.getSurface()), new CameraCaptureSession.StateCallback() // ③
            {
                @Override
                public void onConfigured(CameraCaptureSession cameraCaptureSession) {
                    if (null == mCameraDevice) return;
                    // 当摄像头已经准备好时,开始显示预览
                    mCameraCaptureSession = cameraCaptureSession;
                    try {
                        // 自动对焦
                        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                        // 打开闪光灯
                        previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                        // 显示预览
                        CaptureRequest previewRequest = previewRequestBuilder.build();
                        mCameraCaptureSession.setRepeatingRequest(previewRequest, null, childHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
                    Toast.makeText(MainActivity.this, "配置失败", Toast.LENGTH_SHORT).show();
                }
            }, childHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }



}

4、AndroidManifest.xml


今天代码先分享到那么多,明天给大家分享一下Camera2的架构。有不懂的可以评论,一起讨论。







你可能感兴趣的:(java)