最近由于参加一个小小的创意比赛,用安卓做了一个小小的图片识别应用,主要是通过拍照识别图片中的菜品,还有对象位置查找的东西。之前没有做过安卓,都是拼拼凑凑多篇博客完成的,我也把这个项目的一些过程分享一下。先把功能贴一下,其实就是点击拍照,将照片保存在本地,然后识别出图中的菜品,然后用红色方框圈出来,并显示菜品种类。采用最新的Camera2的API,的确是比Camera好用。
1、界面
我采用了一个SurfaceVIEw用来显示摄像头的预览画面,重写了一个SurfaceVIEw来进行红色方框还有菜品名字的绘制。图片是一个ImageVIEw,相当于拍照按钮的功能。
<?xml version="1.0" enCoding="utf-8"?><relativeLayout xmlns:androID="http://schemas.androID.com/apk/res/androID" xmlns:tools="http://schemas.androID.com/tools" androID:layout_wIDth="match_parent" androID:layout_height="match_parent" tools:context="com.hd.hd.MainActivity"> <SurfaceVIEw androID:layout_wIDth="match_parent" androID:layout_height="match_parent" androID:ID="@+ID/surfaceVIEw" androID:layout_centerHorizontal="true" androID:layout_centerVertical="true"/> <com.hd.hd.SVDraw androID:layout_wIDth="match_parent" androID:layout_height="match_parent" androID:ID="@+ID/mySurfaceVIEw" androID:layout_centerHorizontal="true" androID:layout_centerVertical="true"/> <linearLayout androID:layout_wIDth="wrap_content" androID:layout_height="wrap_content" androID:orIEntation="horizontal" androID:layout_alignParentBottom="true" > <ImageVIEw androID:ID="@+ID/btngal" androID:layout_wIDth="wrap_content" androID:layout_height="wrap_content" androID:gravity="center_horizontal" androID:layout_alignParentBottom="true" androID:src="@drawable/s_8" androID:layout_alignParentleft="true" /> <TextVIEw androID:ID="@+ID/textvIEw" androID:layout_wIDth="wrap_content" androID:layout_height="wrap_content" androID:layout_gravity="center" androID:textcolor="@color/white" androID:textSize="20dp" androID:layout_toRightOf="@ID/btngal" androID:layout_alignParenttop="true" /> </linearLayout></relativeLayout>
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<String> keys,List<String> 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<String,Integer,String> { 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<String> keys = new ArrayList<String>(); List<String> values = new ArrayList<String>(); 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,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,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,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,mainHandler); } catch (CameraAccessException e) { e.printstacktrace(); } } else { Toast.makeText(this,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
<?xml version="1.0" enCoding="utf-8"?><manifest xmlns:androID="http://schemas.androID.com/apk/res/androID" package="com.hd.hd"> <uses-permission androID:name="androID.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission androID:name="androID.permission.READ_EXTERNAL_STORAGE"/> <uses-permission androID:name="androID.permission.CAMERA"/> <uses-feature androID:name="androID.harDWare.camera2.full" /> <uses-permission androID:name="androID.permission.INTERNET" /> <application androID:allowBackup="true" androID:icon="@mipmap/ic_launcher" androID:label="@string/app_name" androID:supportsRtl="true" androID:theme="@style/Apptheme"> <activity androID:name=".MainActivity"> <intent-filter> <action androID:name="androID.intent.action.MAIN" /> <category androID:name="androID.intent.category.LAUNCHER" /> </intent-filter> </activity> </application></manifest>
今天代码先分享到那么多,明天给大家分享一下Camera2的架构。有不懂的可以评论,一起讨论。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。
总结以上是内存溢出为你收集整理的Android图片识别应用详解全部内容,希望文章能够帮你解决Android图片识别应用详解所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)