Android图片识别应用详解

时间:2022-07-27
本文章向大家介绍Android图片识别应用详解,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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

<?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的架构。有不懂的可以评论,一起讨论。

以上就是本文的全部内容,希望对大家的学习有所帮助。