本篇主要讲本科时做的一个应用,人脸识别相册。主要包含JNI和业务逻辑。最终代码会公布在github。
当时深度学习还没有很火,所以用的是经典的PCA方法,降维之后直接作为特征。人脸检测部分用的也是Opencv的Haar特征人脸检测。现在来看性能比较差了。这里就不介绍算法了,感兴趣的可以看看我的其他博文。
本次系统架构分成2部分,一是算法;二是界面和业务逻辑。算法部分由于是C++写的,所以需要用Java的JNI接口封装一下。界面方面采用瀑布流界面,三栏极简风格。
业务逻辑是:
1.用户打开相册,自动读取系统照片;
2.用户点击某一张图像,若此照片此前未检测,则检测人脸并提取特征;若此照片此前已经检测,则显示人脸位置。
3.用户点击人脸,手工标注。
4.在第三栏,按照人脸识别实现自动分类。
这个逻辑的缺点是一开始需要用户手动标记,现在看来大概可以用聚类算法代替。
因为我已经不做开发了,所以现在并不知道JNI怎么写了,已经不会写了。还是贴贴代码吧:
package cn.edu.zju.srtp.facemap.algorithm;import java.io.File;
import java.util.Vector;import org.opencv.core.Mat;import android.content.Context;
import android.util.Log;public class NativeFaceRecognizer {public static final int LBPH_FACERECOGNIZER =1;public static final int FISHER_FACERECOGNIZER =2;public static final int EIGEN_FACERECOGNIZER =3;private final static String TAG ="NativeFaceRecognizer";public final static String LBPH_FILENAME ="lbph_model.xml";public final static String FISHER_FILENAME ="fisher_model.xml";public final static String EIGEN_FILENAME ="eigen_model.xml";/**Note which faceRecognizer has been created* */private int mState =1;/**The pointer to faceRecognizer with the initial value -1(important)* */private long mNativeObj =-1;private Context mContext;static{Log.d(TAG, "NativeFaceRecognizer static initial block enter");System.loadLibrary("opencv_java");System.loadLibrary("face_rec");Log.d(TAG, "NativeFaceRecognizer static initial block exit");}public NativeFaceRecognizer(Context context,int state) {mContext=context;mState=state;createFaceRecognizer();load();}/**Destroy the model first and create a FisherFaceRecognizer model.* */public void createFisherFaceRecognizer(){destroy();mState=FISHER_FACERECOGNIZER;mNativeObj=nativeCreateFisherFaceRecognizer();}/**Destroy the model first and create a LBPHFaceRecognizer model.* */public void createLBPHFaceRecognizer(){destroy();mState=LBPH_FACERECOGNIZER;mNativeObj=nativeCreateLBPHFaceRecognizer();}/**Destroy the model first and create a EigenFaceRecognizer model.* */public void createEigenFaceRecognizer(){destroy();mState=EIGEN_FACERECOGNIZER;mNativeObj=nativeCreateEigenFaceRecognizer();}/**Train the model.The input image should be grayscale.* The number of input images and labels should be equal.* @param images* @param labels_vec*/public void train(Vector
依稀记得当时写这个碰到了一些问题。主要的问题是,我在C++里写的类,需要动态分配,然后把它的指针保存在Java里。每次Java把这个指针再传给C++。这种方式是以Java为主的编程方式。不知道还有没有其他更好的写法。
当时为了实现图像缩放,在图书馆写了一上午。主要觉得挺好玩的。年少。
package cn.edu.zju.srtp.facemap.album;import java.util.ArrayList;
import java.util.Calendar;import org.opencv.core.Rect;import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.text.format.Time;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import cn.edu.zju.srtp.facemap.R;
import cn.edu.zju.srtp.facemap.algorithm.Face;
import cn.edu.zju.srtp.facemap.algorithm.FaceRecManagerSingleInstance;public class ZoomImageView extends View{private static final String TAG ="ZoomImageView";/** * 初始化状态常量 */ public static final int STATUS_INIT = 1; /** * 图片放大状态常量 */ public static final int STATUS_ZOOM_OUT = 2; /** * 图片缩小状态常量 */ public static final int STATUS_ZOOM_IN = 3; /** * 图片拖动状态常量 */ public static final int STATUS_MOVE = 4; /** * 用于对图片进行移动和缩放变换的矩阵 */ private Matrix matrix = new Matrix(); /** * 待展示的Bitmap对象 */ private Bitmap sourceBitmap;/***图片边框 */private Bitmap borderBitmap;/** * 记录当前操作的状态,可选值为STATUS_INIT、STATUS_ZOOM_OUT、STATUS_ZOOM_IN和STATUS_MOVE */ private int currentStatus; private ArrayList
// mTime.setToNow();mStartTime&#61;Calendar.getInstance().getTimeInMillis();case MotionEvent.ACTION_MOVE: if (event.getPointerCount() &#61;&#61; 1) { // 只有单指按在屏幕上移动时&#xff0c;为拖动状态 float xMove &#61; event.getX(); float yMove &#61; event.getY(); if (lastXMove &#61;&#61; -1 && lastYMove &#61;&#61; -1) { lastXMove &#61; xMove; lastYMove &#61; yMove; } currentStatus &#61; STATUS_MOVE;movedDistanceX &#61; xMove - lastXMove; movedDistanceY &#61; yMove - lastYMove; // 进行边界检查&#xff0c;不允许将图片拖出边界 if (totalTranslateX &#43; movedDistanceX > 0) { movedDistanceX &#61; 0; } else if (width - (totalTranslateX &#43; movedDistanceX) > currentBitmapWidth) { movedDistanceX &#61; 0; } if (totalTranslateY &#43; movedDistanceY > 0) { movedDistanceY &#61; 0; } else if (height - (totalTranslateY &#43; movedDistanceY) > currentBitmapHeight) { movedDistanceY &#61; 0; } // 调用onDraw()方法绘制图片 invalidate(); lastXMove &#61; xMove; lastYMove &#61; yMove; }else if(event.getPointerCount() &#61;&#61; 2){// 有两个手指按在屏幕上移动时&#xff0c;为缩放状态 centerPointBetweenFingers(event); double fingerDis &#61; distanceBetweenFingers(event); if (fingerDis > lastFingerDis) { currentStatus &#61; STATUS_ZOOM_OUT; } else { currentStatus &#61; STATUS_ZOOM_IN; } // 进行缩放倍数检查&#xff0c;最大只允许将图片放大4倍&#xff0c;最小可以缩小到初始化比例 if ((currentStatus &#61;&#61; STATUS_ZOOM_OUT && totalRatio <4 * initRatio) || (currentStatus &#61;&#61; STATUS_ZOOM_IN && totalRatio > initRatio)) { scaledRatio &#61; (float) (fingerDis / lastFingerDis); totalRatio &#61; totalRatio * scaledRatio;if (totalRatio > 4 * initRatio) { totalRatio &#61; 4 * initRatio; } else if (totalRatio
}
瀑布流渲染似乎有点慢&#xff0c;要等很久的样子。有时候闪退。