APP添加人脸识别功能

心血来潮想要给最近开发的“到了没”加一个人脸签到的新功能,于是便开干了!
采用的是科大讯飞的人脸识别接口。
集成过程还是和以前一样:
1.下载jdk,并且添加到项目中
这里写图片描述
2.在云端创建应用,并生成相关的appid等值。
这里写图片描述
3.读相关的开发文档,进行相关操作即可

人脸的注册,以及用户分组等功能,我写到一个独立的demo中,就不多展示,只展示“到了没” 1:N人脸验证的相关代码。
代码如下:

FaceRollcall.class

package com.example.FaceIdentify;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.database.DBHelper;
import com.example.database.Student;
import com.example.database.StudentBase;
import com.example.rollcall.MainActivity;
import com.example.rollcall.R;
import com.example.rollcall.Topbar;
import com.iflytek.cloud.ErrorCode;
import com.iflytek.cloud.IdentityListener;
import com.iflytek.cloud.IdentityResult;
import com.iflytek.cloud.IdentityVerifier;
import com.iflytek.cloud.InitListener;
import com.iflytek.cloud.SpeechConstant;
import com.iflytek.cloud.SpeechError;
import com.iflytek.cloud.SpeechUtility;

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

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import cn.bmob.v3.BmobQuery;
import cn.bmob.v3.listener.FindListener;

import static android.R.attr.id;

/**
 * Created by Cactus on 2018/2/10.
 */

public class FaceRollcall extends Activity implements View.OnClickListener {

    private final static String TAG = FaceRollcall.class.getSimpleName();

    // 用户输入的组ID
    private String mGroupId;
    // 身份鉴别对象
    private IdentityVerifier mIdVerifier;
    private ProgressDialog mProDialog;
    private Toast mToast;
    JSONArray candidates;
    private static DBHelper dbHelper;
    private static SQLiteDatabase database;

    // 选择图片后返回
    public static final int REQUEST_PICTURE_CHOOSE = 1;
    // 拍照后返回
    private final static int REQUEST_CAMERA_IMAGE = 2;
    // 裁剪图片成功后返回
    public static final int REQUEST_INTENT_CROP = 3;

    private Bitmap mImageBitmap = null;
    private byte[] mImageData = null;
    private File mPictureFile;
    private EditText et_meeting;
    List<StudentBase> studentList = new ArrayList<StudentBase>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_face_identify);
        Topbar topBar = (Topbar) findViewById(R.id.topbar_face);
        topBar.setOnLeftAndRightClickListener(new Topbar.OnLeftAndRightClickListener() {
            @Override
            public void OnLeftButtonClick() {
                Intent i = getIntent();
                i.setClass(getApplicationContext(),MainActivity.class);
                startActivity(i);
            }

            @Override
            public void OnRightButtonClick() {
            }
        });
        SpeechUtility.createUtility(this, SpeechConstant.APPID +"=输入自己的APPid");
        // 对象初始化监听器
        mIdVerifier = IdentityVerifier.createVerifier(FaceRollcall.this, new InitListener() {
            @Override
            public void onInit(int errorCode) {
                if (ErrorCode.SUCCESS == errorCode) {
                    showTip("引擎初始化成功");
                } else {
                    showTip("引擎初始化失败,错误码:" + errorCode);
                }
            }
        });

        // 初始化界面
        initUI();
        // 身份验证对象初始化
    }

    private void initUI() {
        findViewById(R.id.button_face).setOnClickListener(FaceRollcall.this);
        findViewById(R.id.button_face_rollcall).setOnClickListener(FaceRollcall.this);
        et_meeting = (EditText) findViewById(R.id.et_meeting);
        et_meeting.setOnClickListener(this);
        mProDialog = new ProgressDialog(FaceRollcall.this);
        mProDialog.setCancelable(true);
        mProDialog.setTitle("请稍候");
        // cancel进度框时,取消正在进行的操作
        mProDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {

            @Override
            public void onCancel(DialogInterface dialog) {
                if (null != mIdVerifier) {
                    mIdVerifier.cancel();
                }
            }
        });

        mToast = Toast.makeText(FaceRollcall.this, "", Toast.LENGTH_SHORT);
        mToast.setGravity(Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL, 0, 0);
    }

    /**
     * 人脸鉴别监听器
     */
    private IdentityListener mSearchListener = new IdentityListener() {

        @Override
        public void onResult(IdentityResult result, boolean islast) {
            Log.d(TAG, result.getResultString());

            dismissProDialog();

            handleResult(result);
        }

        @Override
        public void onEvent(int eventType, int arg1, int arg2, Bundle obj) {
        }

        @Override
        public void onError(SpeechError error) {
            dismissProDialog();

            showTip(error.getPlainDescription(true));
        }

    };

    @Override
    public void onClick(View view) {
        if( null == mIdVerifier ){
            // 创建单例失败,与 21001 错误为同样原因,参考 http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=9688
            showTip( "创建对象失败,请确认 libmsc.so 放置正确,且有调用 createUtility 进行初始化" );
            return;
        }

        switch (view.getId()) {
            case R.id.button_face:
                // 设置相机拍照后照片保存路径
                mPictureFile = new File(Environment.getExternalStorageDirectory(),
                        "picture" + System.currentTimeMillis()/1000 + ".jpg");
                // 启动拍照,并保存到临时文件
                Intent mIntent = new Intent();
                mIntent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
                mIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(mPictureFile));
                mIntent.putExtra(MediaStore.Images.Media.ORIENTATION, 0);
                startActivityForResult(mIntent, REQUEST_CAMERA_IMAGE);
                break;

            case R.id.button_face_rollcall:
                    mGroupId = et_meeting.getText().toString();
                    if (null != mImageData) {
                        mProDialog.setMessage("鉴别中...");
                        mProDialog.show();
                        // 清空参数
                        mIdVerifier.setParameter(SpeechConstant.PARAMS, null);
                        // 设置业务场景
                        mIdVerifier.setParameter(SpeechConstant.MFV_SCENES, "ifr");
                        // 设置业务类型
                        mIdVerifier.setParameter(SpeechConstant.MFV_SST, "identify");
                        // 设置监听器,开始会话
                        mIdVerifier.startWorking(mSearchListener);
                        // 子业务执行参数,若无可以传空字符传
                        StringBuffer params = new StringBuffer();
                        params.append(",group_id=" + mGroupId +",topc=3");
                        // 向子业务写入数据,人脸数据可以一次写入
                        mIdVerifier.writeData("ifr", params.toString(), mImageData, 0, mImageData.length);
                        // 写入完毕
                        mIdVerifier.stopWrite("ifr");
                    } else {
                        showTip("请选择图片后再鉴别");
                    }
                break;
            default:
                break;
        }
    }

    protected void handleResult(IdentityResult result) {
        if (null == result) {
            return;
        }

        try {
            String resultStr = result.getResultString();
            JSONObject resultJson = new JSONObject(resultStr);
            if(ErrorCode.SUCCESS == resultJson.getInt("ret"))
            {
                JSONObject ifv_result = resultJson.getJSONObject("ifv_result");
                candidates = ifv_result.getJSONArray("candidates");
                JSONObject obj = candidates.getJSONObject(0);
                String userId = obj.optString("user");
                studentList = query_name(this,userId);
                String stu_name = studentList.get(0).getName();
                int signFlag_add = studentList.get(0).getSignFlag();
                update(this,userId,signFlag_add+1);
                showTip(stu_name+" 签到成功");
            }
            else {
                showTip("鉴别失败!");
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode != RESULT_OK) {
            return;
        }

        String fileSrc = null;
        if (requestCode == REQUEST_PICTURE_CHOOSE ) {
            if ("file".equals(data.getData().getScheme())) {
                // 有些低版本机型返回的Uri模式为file
                fileSrc = data.getData().getPath();
            } else {
                // Uri模型为content
                String[] proj = {MediaStore.Images.Media.DATA};
                Cursor cursor = getContentResolver().query(data.getData(), proj,
                        null, null, null);
                cursor.moveToFirst();
                int idx = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
                fileSrc = cursor.getString(idx);
                cursor.close();
            }
            // 跳转到图片裁剪页面
            cropPicture(this,Uri.fromFile(new File(fileSrc)));
        } else if (requestCode == REQUEST_CAMERA_IMAGE) {
            if (null == mPictureFile) {
                showTip("拍照失败,请重试");
                return;
            }

            fileSrc = mPictureFile.getAbsolutePath();
            updateGallery(fileSrc);
            // 跳转到图片裁剪页面
            cropPicture(this,Uri.fromFile(new File(fileSrc)));
        } else if (requestCode == REQUEST_INTENT_CROP) {

            // 获取返回数据
            Bitmap bmp = data.getParcelableExtra("data");

            // 获取裁剪后图片保存路径
            fileSrc = getImagePath();

            // 若返回数据不为null,保存至本地,防止裁剪时未能正常保存
            if(null != bmp){
                saveBitmapToFile(bmp);
            }

            // 获取图片的宽和高
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            mImageBitmap = BitmapFactory.decodeFile(fileSrc, options);

            // 压缩图片
            options.inSampleSize = Math.max(1, (int) Math.ceil(Math.max(
                    (double) options.outWidth / 1024f,
                    (double) options.outHeight / 1024f)));
            options.inJustDecodeBounds = false;
            mImageBitmap = BitmapFactory.decodeFile(fileSrc, options);

            // 若mImageBitmap为空则图片信息不能正常获取
            if(null == mImageBitmap) {
                showTip("图片信息无法正常获取!");
                return;
            }

            // 部分手机会对图片做旋转,这里检测旋转角度
            int degree = readPictureDegree(fileSrc);
            if (degree != 0) {
                // 把图片旋转为正的方向
                mImageBitmap = rotateImage(degree, mImageBitmap);
            }

            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            //可根据流量及网络状况对图片进行压缩
            mImageBitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos);
            mImageData = baos.toByteArray();

            ((ImageView) findViewById(R.id.online_img)).setImageBitmap(mImageBitmap);
        }
    }

    @Override
    public void finish() {
        if (null != mProDialog) {
            mProDialog.dismiss();
        }
        setResult(RESULT_OK);
        super.finish();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (null != mIdVerifier) {
            mIdVerifier.destroy();
            mIdVerifier = null;
        }
    }

    /**
     * 读取图片属性:旋转的角度
     *
     * @param path 图片绝对路径
     * @return degree 旋转的角度
     */
    public static int readPictureDegree(String path) {
        int degree = 0;
        try {
            ExifInterface exifInterface = new ExifInterface(path);
            int orientation = exifInterface.getAttributeInt(
                    ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_NORMAL);
            switch (orientation) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    degree = 90;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    degree = 180;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    degree = 270;
                    break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return degree;
    }

    private void updateGallery(String filename) {
        MediaScannerConnection.scanFile(this, new String[] {filename}, null,
                new MediaScannerConnection.OnScanCompletedListener() {

                    @Override
                    public void onScanCompleted(String path, Uri uri) {

                    }
                });
    }

    /**
     * 旋转图片
     *
     * @param angle
     * @param bitmap
     * @return Bitmap
     */
    public static Bitmap rotateImage(int angle, Bitmap bitmap) {
        // 图片旋转矩阵
        Matrix matrix = new Matrix();
        matrix.postRotate(angle);
        // 得到旋转后的图片
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
                bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        return resizedBitmap;
    }

    /**
     * Toast弹出提示
     * @param str
     */
    private void showTip(final String str) {
        mToast.setText(str);
        mToast.show();
    }


    /***
     * 裁剪图片
     * @param activity Activity
     * @param uri 图片的Uri
     */
    public void cropPicture(Activity activity, Uri uri) {
        Intent innerIntent = new Intent("com.android.camera.action.CROP");
        innerIntent.setDataAndType(uri, "image/*");
        innerIntent.putExtra("crop", "true");// 才能出剪辑的小方框,不然没有剪辑功能,只能选取图片
        innerIntent.putExtra("aspectX", 1); // 放大缩小比例的X
        innerIntent.putExtra("aspectY", 1);// 放大缩小比例的X   这里的比例为:   1:1
        innerIntent.putExtra("outputX", 320);  //这个是限制输出图片大小
        innerIntent.putExtra("outputY", 320);
        innerIntent.putExtra("return-data", true);
        // 切图大小不足输出,无黑框
        innerIntent.putExtra("scale", true);
        innerIntent.putExtra("scaleUpIfNeeded", true);
        innerIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(getImagePath())));
        innerIntent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
        activity.startActivityForResult(innerIntent, REQUEST_INTENT_CROP);
    }

    /**
     * 设置保存图片路径
     * @return
     */
    private String getImagePath(){
        String path;
        if(!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            return null;
        }
        path =  Environment.getExternalStorageDirectory().getAbsolutePath() +"/MFVDemo/";
        File folder = new File(path);
        if (folder != null && !folder.exists()) {
            folder.mkdirs();
        }
        path += "mfvtest.jpg";
        return path;
    }

    /**
     * 保存Bitmap至本地
     * @param bmp
     */
    private void saveBitmapToFile(Bitmap bmp){
        String file_path = getImagePath();
        File file = new File(file_path);
        FileOutputStream fOut;
        try {
            fOut = new FileOutputStream(file);
            bmp.compress(Bitmap.CompressFormat.JPEG, 85, fOut);
            fOut.flush();
            fOut.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void dismissProDialog() {
        if (null != mProDialog) {
            mProDialog.dismiss();
        }
    }
    private void update(Context con, String stuNum , int signFlag_add)
    {
        dbHelper = new DBHelper(con, "rollcall.db");
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        db.execSQL("update student set signFlag=? where stunum=?", new Object[]{signFlag_add,stuNum});
        db.close();
        dbHelper.close();
    }
    private List<StudentBase> query_name(Context con, String stuNum)
    {
        List<StudentBase> stu = new ArrayList<StudentBase>();
        dbHelper = new DBHelper(con, "rollcall.db");
        database = dbHelper.getReadableDatabase();
        // 去除重复的班级列表
        String ss = "select * from student where stunum = '" + stuNum + "'";
        Cursor cursor1 = database.rawQuery(ss, null);
        while (cursor1.moveToNext())
        {
            stu.add(new StudentBase(cursor1.getInt(cursor1.getColumnIndex("_id")),
                    cursor1.getString(cursor1.getColumnIndex("name")),
                    cursor1.getString(cursor1.getColumnIndex("stunum")),
                    cursor1.getString(cursor1.getColumnIndex("coursename")),
                    cursor1.getInt(cursor1.getColumnIndex("signFlag")),
                    cursor1.getInt(cursor1.getColumnIndex("leaveFlag")),
                    cursor1.getInt(cursor1.getColumnIndex("truantFlag")),
                    cursor1.getInt(cursor1.getColumnIndex("countnum"))));
        }
        database.close();
        cursor1.close();
        dbHelper.close();
        return stu;
    }
    /**
     * 三个函数留个纪念,弄了一下午,没想到啊,竟然是数据的问题!!!数据溢出了吗?
     */
//    public StudentBase findBynum(Context con,String stunum)
//    {
//        dbHelper = new DBHelper(con, "rollcall.db");
//        SQLiteDatabase db=dbHelper.getReadableDatabase();
//        String[] columns={"stunum","name","signFlag"};
//        String[] selectionArgs = {stunum};
//        Cursor c= db.query(true,"student",columns,"Name = ?",selectionArgs,null,null,null,null);
//        StudentBase dog=null;
//        if(c.moveToNext())
//        {
//            dog=new StudentBase();
//            dog.setStuNum(c.getString(c.getColumnIndexOrThrow("stunum")));
//            dog.setSignFlag(c.getInt(c.getColumnIndexOrThrow("signFlag")));
//            dog.setName(c.getString(c.getColumnIndexOrThrow("name")));
//        }
//        c.close();
//        db.close();
//        return dog;
//    }
//    private void LoadContent(Context con,String course)
//    {
//        BmobQuery<Student> query =new BmobQuery<>();
//        query.addWhereEqualTo("stuNum", "s1005");
//        query.findObjects(con, new FindListener<Student>() {
//            @Override
//            public void onSuccess(List<Student> list) {
//                showTip(list.get(0).getName()+"签到成功");
//            }
//
//            @Override
//            public void onError(int i, String s) {
//                showTip("下载失败");
//            }
//        });
//    }
}

activity_face_identify

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="#ebebeb">

    <com.example.rollcall.Topbar
        android:id="@+id/topbar_face"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="#000"
        app:leftBackground="@drawable/leftselecter"
        app:titleText="人脸签到"
        app:titleTextColor="#FFF"
        app:titleTextSize="10sp"
        tools:layout_editor_absoluteX="8dp"
        tools:layout_editor_absoluteY="0dp" />

    <LinearLayout style="@style/Register_Input"
        android:layout_marginTop="60dp"
        android:id="@+id/linearLayout_meeting"
        android:layout_below="@+id/topbar_face"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true">

        <TextView
            android:id="@+id/textView4"
            style="@style/Black"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center_vertical"
            android:paddingLeft="10dp"
            android:text="会议ID:"
            android:textSize="18dp" />

        <EditText
            android:id="@+id/et_meeting"
            style="@style/Register_Edit"
            android:hint="请输入您的会议ID号"
            android:textColor="@color/gray" />

    </LinearLayout>

    <ImageView
        android:id="@+id/online_img"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:padding="4dp"
        android:src="@drawable/no_photo"
        android:layout_marginTop="25dp"
        android:layout_below="@+id/linearLayout_meeting"
        android:layout_centerHorizontal="true" />
    <Button
        android:id="@+id/button_face"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="点击扫脸"
        android:textSize="30dp"
        android:layout_below="@+id/online_img"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="18dp" />
    <Button
        android:id="@+id/button_face_rollcall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="确认签到"
        android:textSize="30dp"
        android:layout_marginTop="16dp"
        android:layout_below="@+id/button_face"
        android:layout_alignLeft="@+id/button_face"
        android:layout_alignStart="@+id/button_face" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/linearLayout_meeting"
        android:layout_alignStart="@+id/linearLayout_meeting"
        android:layout_below="@+id/button_face_rollcall"
        android:layout_marginTop="50dp"
        android:textColor="@color/gray"
        android:text="        由于技术原因,“到了没”人脸签到目前只能做到1:N人脸对比。故只能应用于人员到场较为松散的会议签到场景,您只需要输入'到了没'官方分配给您的会议ID即可进行刷脸签到。"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />


</RelativeLayout>

在工程 AndroidManifest.xml 文件中添加如下权限

<!--连接网络权限,用于执行云端语音能力 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!--获取手机录音机使用权限,听写、识别、语义理解需要用到此权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!--读取网络信息状态 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!--获取当前wifi状态 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<!--允许程序改变网络连接状态 -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<!--读取手机信息权限 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<!--读取联系人权限,上传联系人需要用到此权限 -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<!--外存储写权限,构建语法需要用到此权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--外存储读权限,构建语法需要用到此权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!--配置权限,用来记录应用配置信息 -->
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<!--手机定位信息,用来为语义等功能提供定位,提供更精准的服务-->
<!--定位信息是敏感信息,可通过Setting.setLocationEnable(false)关闭定位请求 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--如需使用人脸识别,还要添加:摄相头权限,拍照需要用到 -->
<uses-permission android:name="android.permission.CAMERA" />

注意:
jdk中的so文件,必须要放到名为jniLibs的文件夹下:
这里写图片描述

只要认真看相关的开发文档和demo,应该不成问题。
给“到了没”添加完人脸签到的功能以后,APP也就基本成型了。
很开心!
马上过年了,博友们新年快乐!

### App Inventor 中人脸识别插件的下载与使用教程 #### 一、概述 App Inventor 是一款基于云端开发的应用程序设计工具,支持开发者通过简单的拖拽操作构建功能丰富的移动应用。对于人脸识别功能实现,通常需要借助第三方扩展组件来完成复杂的图像处理任务。 在实际开发过程中,可以利用百度 AI 平台或其他类似的云服务提供商所提供的 API 接口[^3],并通过 App Inventor 的 HTTP 请求模块调用这些接口,从而实现人脸检测和识别的核心逻辑[^4]。 --- #### 二、具体步骤说明 ##### 1. 注册并获取必要的密钥 为了能够访问远程服务器上的人脸识别服务,首先需要注册一个账号,并申请相应的 APP_ID、API_KEY 和 SECRET_KEY。以下是常见的几个平台供参考: - 百度 AI 开放平台 (https://ai.baidu.com/) - 腾讯云人工智能开放平台 (https://cloud.tencent.com/) 以百度为例,在成功登录之后进入控制台页面创建属于自己的专属项目即可获得上述三个参数值。 ##### 2. 配置环境准备 确保已经安装好最新版本的 MIT AppInventor 客户端软件以及连接互联网正常工作状态下的电脑设备;另外还需要一台安卓手机用来测试最终效果或者也可以考虑采用虚拟机方式如 MUMU 模拟器来进行初步验证。 ##### 3. 创建新工程导入所需库文件 打开 AppInventor 后新建空白项目命名为 “FaceRecognitionDemo”。接着切换到 Blocks 编辑界面之前先回到 Designer 设计模式下加载额外所需的外部资源包 - aiwebrequest 扩展元件(如果当前系统尚未包含该选项则需手动上传对应 .aix 文件)。 ##### 4. 构建 UI 界面布局结构 按照需求规划合理的用户交互区域划分,比如添加按钮控件用于触发拍照动作捕捉实时画面数据流输入源;再设置 ImageView 显示裁剪后的目标对象轮廓边界框位置信息等等[^1]。 ##### 5. 实现核心算法逻辑部分 编写具体的业务流程代码片段如下所示: ```blocks when Screen1.Initialize do set Web1.Url to "https://aip.baidubce.com/rest/2.0/face/v3/detect?access_token=your_access_token_here" end when Button1.Click do call Camera.TakePicture with filename as picFile after PictureTaken(picFile) set Image1.Source to picFile call File.ReadAllBytes(picFile) and store result into byteData convert byteData using Base64.EncodeToString method storing output inside base64String variable make a JSON object containing image property whose value equals concatenated string of 'data:image/jpeg;base64,' followed by base64String content then assign it back again under same key name within another dictionary named postData finally sending this whole structure via POST request through AiWebRequest component towards specified endpoint URL mentioned earlier while attaching appropriate headers including Content-Type header specifying application/json MIME type accordingly. end ``` 注意替换掉占位符 `your_access_token_here` 成为自己实际取得的有效 token 字符串。 --- #### 三、注意事项 由于涉及到隐私保护政策方面的原因,在正式上线前务必仔细阅读相关法律法规条款确认无误后再对外发布应用程序成品[^2]。 ---
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喜鹊先生Richard

随缘~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值