这个项目是做了一个云便签
android代码python后端 核心要点:

  1. Android使用volley进行图片上传
  2. Python后台使用django+ face_recognition 进行人脸识别
  3. 我并没有把后端代码放进服务器,而是通过frp进行内网穿透

在使用Android使用人脸识别登录之前,我已经用过django+ face_recognitionhtml进行过了.点击查看 这一次在Android上实践.

android注册页面

MainActivity.class
package com.example.userregister;

import android.Manifest;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.StrictMode;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

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

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{


    // 密码
    private EditText et_password;
    // 注册按钮
    private Button btn_register;
    // 用户名
    private EditText et_name;
    // 头像获取按钮
    private Button btn_head;
    // 姓名字段
    private String name;
    // 密码
    private String password;
    private String str_picture;
    // onActivityResult() requestCode 值
    public static final int TAKE_PHOTO = 1;
    // 照片路径
    private Uri imageUri;

    private Button login;
    // 头像ImageView
    private ImageView picture;
    Bitmap bitmap;
    FileOutputStream fileOutputStream;

    private File file;

    private String image_send;

    public static final int CHOOSE_PHOTO = 2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
        { StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
            StrictMode.setVmPolicy(builder.build());
        }
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        et_name = (EditText) findViewById(R.id.et_name);
        et_password = (EditText) findViewById(R.id.et_password);
        btn_register = (Button) findViewById(R.id.btn_send);
        btn_head = (Button) findViewById(R.id.btn_head);
        picture = (ImageView) findViewById(R.id.picture);
        btn_head.setOnClickListener(this);

        login = (Button) findViewById(R.id.login);
        login.setOnClickListener(this);

        btn_register.setOnClickListener(this);

    }

    /**
     * 生成对应的按钮响应事件
     * @param v
     */
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_head:
//               getHead();
                getPhotoFromAlbum();
                break;
            case R.id.btn_send:
                Bitmap bitmap = ((BitmapDrawable)picture.getDrawable()).getBitmap();
                str_picture = bitmapToBase64(bitmap);
//                Log.e("picture",str_picture);
                name = et_name.getText().toString();
                password = et_password.getText().toString();
                login(name, password,str_picture,image_send);
                break;

            case R.id.login:
                tologin();
                break;
        }
    }





    public void tologin(){
        Intent intent = new Intent(this, record.class);
        startActivity(intent);
    }
    /**
     * 登录功能
     * @param accountNumber
     * @param password
     */
    public void login(final String accountNumber,final String password,final String str_picture,final String imagePath){

        //检验账号和密码是否正确
        if (TextUtils.isEmpty(accountNumber)) {
            Toast.makeText(this, "姓名不能为空", Toast.LENGTH_SHORT).show();
            return;
        }
        if (TextUtils.isEmpty(password)) {
            Toast.makeText(this, "密码不能为空", Toast.LENGTH_SHORT).show();
            return;
        }
        //请求地址
        String url = "***";    //注①
        String tag = "Register";    //注②

        //取得请求队列
        RequestQueue requestQueue = Volley.newRequestQueue(getApplicationContext());

        //防止重复请求,所以先取消tag标识的请求队列
        requestQueue.cancelAll(tag);

        //创建StringRequest,定义字符串请求的请求方式为POST(省略第一个参数会默认为GET方式)
        final StringRequest request = new StringRequest(Request.Method.POST, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        try {
                            String result = String.valueOf(new JSONObject(response).get("status"));
                            //JSONObject jsonObject = (JSONObject) new JSONObject(response).get("token");  //注③
                            //String result = jsonObject.getString();  //注④
                            if (result.equals("1")) {  //注⑤
//                                System.out.println("注册成功");
                                Toast.makeText(MainActivity.this, "注册成功", Toast.LENGTH_SHORT).show();
                                Intent intent = new Intent(MainActivity.this, record.class);
                                intent.putExtra("username", accountNumber);
                                intent.putExtra("imagePath", imagePath);
                                startActivity(intent);
                                //做自己的登录成功操作,如页面跳转
                            } if(result.equals("0")) {
                                Toast.makeText(MainActivity.this, "登录失败", Toast.LENGTH_SHORT).show();
                                //做自己的登录失败操作,如Toast提示
                            }
                        } catch (JSONException e) {
                            //做自己的请求异常操作,如Toast提示(“无网络连接”等)
                            Log.e("TAG", e.getMessage(), e);
                        }
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                //做自己的响应错误操作,如Toast提示(“请稍后重试”等)
                Log.e("TAG", error.getMessage(), error);
            }
        }) {
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String, String> params = new HashMap<>();
                params.put("username", accountNumber);  //注⑥
                params.put("password", password);
                if (str_picture != null) {
                    params.put("picture",str_picture );
                }
                return params;
            }
        };

        //设置Tag标签
        request.setTag(tag);

        //将请求添加到队列中
        requestQueue.add(request);

        // 注册成功, 跳转到登录页面
        // ... ... 待完成

    }

    /**
     * 返回图片结果
     * @param requestCode
     * @param resultCode
     * @param data
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

//        switch (requestCode)
//        {
//            case TAKE_PHOTO:
//            {
//                if(resultCode == RESULT_OK)
//                {
//                    //将拍摄的照片显示出来
//                    try
//                    {
//                        Bitmap bitmap= BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
//                        picture.setImageBitmap(bitmap);
//                    }
//                    catch (FileNotFoundException e)
//                    {
//                        e.printStackTrace();
//                    }
//                }
//            }
//            break;
//            default:
//                break;
//        }
        switch (requestCode) {
            case CHOOSE_PHOTO:
                if (resultCode == RESULT_OK) {
                    if (Build.VERSION.SDK_INT >= 19) {
                        handleImageOnKitKat(data);

                    } else {
                        handeleImageBeforeKitKat(data);
                    }
                    break;
                }
                default:
                    break;
        }
    }

    // 从相册中选择照片
    public void getPhotoFromAlbum() {
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},1);;
        } else {
            openAlbum();
        }
    }


    /**
     * 打开相册
     */
    private void openAlbum() {
        Intent intent = new Intent("android.intent.action.GET_CONTENT");
        intent.setType("image/*");
        startActivityForResult(intent, CHOOSE_PHOTO);  // 打开相册
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    openAlbum();
                } else {
                    Toast.makeText(this, "You denied the perssion", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
        }
    }

    /**
     * 安卓4.4以上版本的处理方法
     * @param data
     */
    @TargetApi(19)
    private void handleImageOnKitKat(Intent data) {
//    Toast.makeText(this,"到了handleImageOnKitKat(Intent data)方法了", Toast.LENGTH_LONG).show();
        String imagePath = null;
        Uri uri = data.getData();
        if(DocumentsContract.isDocumentUri(this, uri)){
            //如果是 document 类型的 Uri,则通过 document id 处理
            String docId = DocumentsContract.getDocumentId(uri);
            if("com.android.providers.media.documents".equals(uri.getAuthority())){
                String id = docId.split(":")[1];//解析出数字格式的 id
                String selection = MediaStore.Images.Media._ID + "=" + id;
                imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);
            }else if("com.android.providers.downloads.documents".equals(uri.getAuthority())){
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
                imagePath = getImagePath(contentUri, null);
            }
        }else if ("content".equalsIgnoreCase(uri.getScheme())){
            //如果是 content 类型的 uri , 则使用普通方式处理
            imagePath = getImagePath(uri, null);
        }else if("file".equalsIgnoreCase(uri.getScheme())){
            //如果是 file 类型的 Uri,直接获取图片路径即可
            imagePath = uri.getPath();
        }
        displayImage(imagePath);//显示选中的图片
    }

    /**
     *  安卓4.4一下版本的处理方法
     * @param data
     */
    private void handeleImageBeforeKitKat(Intent data){
        Uri uri = data.getData();
        String imagePath = getImagePath(uri, null);
        displayImage(imagePath);
    }

    /**
     * 获取相片的路径
     * @param uri
     * @param selection
     * @return
     */
    private String getImagePath(Uri uri, String selection) {
        String path = null;
        //通过 Uri 和 selection 来获取真实的图片路径
        Cursor cursor = getContentResolver().query(uri, null, selection, null, null);
        if(cursor != null){
            if(cursor.moveToFirst()){
                path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
            }
            cursor.close();
        }
        return path;
    }

    /**
     *  在主键picture中显示相片
     * @param imagePath
     */
    private void displayImage(String imagePath) {
        image_send = imagePath;
//        Log.e("picture","--------------------------------------"+imagePath);
        if(imagePath != null){
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            picture.setImageBitmap(bitmap);
        }else{
            Toast.makeText(this,"failed to get image", Toast.LENGTH_LONG).show();
        }
    }

     public static String bitmapToBase64(Bitmap bitmap) {

        String result = null;
        ByteArrayOutputStream baos = null;
        try {
            if (bitmap != null) {
                baos = new ByteArrayOutputStream();
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);

                baos.flush();
                baos.close();

                byte[] bitmapBytes = baos.toByteArray();
                result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.flush();
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }



    /**
     * 点击头像按钮时跳转到拍照
     */
    public void getHead() {

        ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
        //创建File对象,用于存储拍照后的图片
        File outputImage = new File(getExternalCacheDir(),"output_image.jpg");
        if(outputImage.exists())
        {
            outputImage.delete();
            try
            {
                outputImage.createNewFile();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }

        if (Build.VERSION.SDK_INT>=24)
        {
            imageUri = FileProvider.getUriForFile(MainActivity.this,"com.example.userregister.fileprovider",outputImage);
        }
        else
        {
            imageUri = Uri.fromFile(outputImage);
        }

        //启动相机程序
        Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
        intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
        startActivityForResult(intent,TAKE_PHOTO);



    }

    /**
     * 使用相机
     */
    private void useCamera() {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        file = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
                + "/test/" + System.currentTimeMillis() + ".jpg");
        file.getParentFile().mkdirs();

        //改变Uri  com.xykj.customview.fileprovider注意和xml中的一致
        Uri uri = FileProvider.getUriForFile(this, "com.example.userregistetr.fileprovider", file);
        //添加权限
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        startActivityForResult(intent, TAKE_PHOTO);
    }
}

我这个是在虚拟机上运行的,事先要用adk/platform-tools/adb将图片通过./adb push /home/perfectman/图片/2.jpg /sdcard/Android/(这个是linux环境下的)命令传输图片,如果是手机上运行,可以考虑调用摄像头拍照进行识别登录.
在虚拟机调用摄像头,摄像头的画面也是虚拟的,并不是说能够调用电脑的摄像头.
大概流程就是先通过Bitmap bitmap = ((BitmapDrawable)picture.getDrawable()).getBitmap();获取图片,然后利用bitmapToBase64()方法进行转码,再利用login()volley把图片和密码传到后台.

Python后台部分
django的view.py
from django.contrib.auth import authenticate
from django.contrib.auth.hashers import make_password
from django.http import JsonResponse
from django.views.generic.base import View
from .forms import *
from .models import User
import face_recognition
import base64
import cv2
import numpy as np


# Create your views here.
class RegisterView(View):
    def post(self, request):
        # print(request)
        username = request.POST.get("username", "")
        password = request.POST.get("password", "")
        print(username, password)
        picture = request.POST.get("picture", '')
        print(picture)

        register_form = RegisterForm(request.POST)
        if register_form.is_valid():
            username = request.POST.get("username", "")
            if User.objects.filter(username=username):
                return JsonResponse({"status": 0})
            else:
                password = request.POST.get("password", "")
                user = User()
                # 图片存入np数组, 用于face_recognition的识别
                # imgdata = base64.b64decode(picture)  # b64解码
                # imgdata = np.fromstring(imgdata, np.uint8)  # 转化成np数组
                # imgdata = cv2.imdecode(imgdata, cv2.COLOR_BGR2RGB)   # 函数从指定的内存缓存中读取数据,并把数据转换(解码)成图像格式;主要用于从网络传输数据中恢复出图像。
                user.image = picture
                user.username = username
                # user.is_active =
                user.password = make_password(password)
                user.save()
                return JsonResponse({"status": 1})
        else:
            return JsonResponse({"status": 2})

            # 返回的状态 0 为用户已存在 1注册成功 2 为form验证失败


class LoginView(View):
    def post(self, request):
        login_form = LoginForm(request.POST)

        if login_form.is_valid():
            username = request.POST.get("username", '')
            password = request.POST.get("password", '')
            picture = request.POST.get("picture", '')
            if password == '':   # 利用图片进行登录
                passimg = User.objects.get(username=username).image  # 获取注册时的图片数据
                print(passimg)
                # 新的图片数据进行转码
                picture = base64.b64decode(picture)
                picture = np.fromstring(picture, np.uint8)
                picture = cv2.imdecode(picture, cv2.COLOR_BGR2RGB)

                # 旧的图片数据进行转码
                passimg = base64.b64decode(passimg)
                passimg = np.fromstring(passimg, np.uint8)
                passimg = cv2.imdecode(passimg, cv2.COLOR_BGR2RGB)
                # 再利用face_recognition进行人脸识别
                my_face_encoding = face_recognition.face_encodings(passimg)[0]
                unknown_face_encoding = face_recognition.face_encodings(picture)[0]
                results = face_recognition.compare_faces([my_face_encoding], unknown_face_encoding)
                if results[0] == True:
                    print("It's a picture of me!")
                    return JsonResponse({'status': 1})
                else:
                    print("It's not a picture of me!")
                    return JsonResponse({'status': 0})
            else:
                # 利用密码登录
                user = authenticate(username=username, password=password)

                if user is not None:
                    # login
                    return JsonResponse({"status": 1})
                else:
                    return JsonResponse({"status": 0})
        else:
            return JsonResponse({"status": 2})

这里大概流程也就是收到账号密码,图片64的字符串后,直接存到数据库里面,
然后在登录的时候,进行验证两张图片是否为同一个人.