Android图片识别应用详解
程序员文章站
2023-11-26 18:19:04
最近由于参加一个小小的创意比赛,用安卓做了一个小小的图片识别应用,主要是通过拍照识别图片中的菜品,还有对象位置查找的东西。之前没有做过安卓,都是拼拼凑凑多篇博客完成的,我也...
最近由于参加一个小小的创意比赛,用安卓做了一个小小的图片识别应用,主要是通过拍照识别图片中的菜品,还有对象位置查找的东西。之前没有做过安卓,都是拼拼凑凑多篇博客完成的,我也把这个项目的一些过程分享一下。先把功能贴一下,其实就是点击拍照,将照片保存在本地,然后识别出图中的菜品,然后用红色方框圈出来,并显示菜品种类。采用最新的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, 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的架构。有不懂的可以评论,一起讨论。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。