欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android图片识别应用详解

程序员文章站 2023-11-26 18:19:04
最近由于参加一个小小的创意比赛,用安卓做了一个小小的图片识别应用,主要是通过拍照识别图片中的菜品,还有对象位置查找的东西。之前没有做过安卓,都是拼拼凑凑多篇博客完成的,我也...

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

Android图片识别应用详解

Android图片识别应用详解

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

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