下部分介绍图像识别涉及到的算法,主要用到的就是HOG+SVM,对二值图提取HOG特征向量,再用SVM多分类器训练相关模型,即可完成识别。
一、手腕去除及凸点计数
本阶段是对手势二值图再做进一步的处理,若存在裸露的手腕部分则将其去除掉并且计算手势最直观的特征手指数目将其作为一种分类特征 。
如果有手腕裸露时与手掌连为一体,之前的算法并不会去除手腕区域,但手腕区域对于之后的手势识别并无关系,所以可以将其去除精简要识别的手势二值图。
距离变换是针对二值图像的一种变换,计算图像中非零像素点到最近的零像素点的距离,然后用距离值作为该点的灰度像素值,将二值图像转换为灰度图像,通常用于细化轮廓和查找质心。通过遍历灰度图各像素点,可知像素值最高那个点是手掌的中心点即为质心,质心则可作为内切圆圆心,而其像素值则可作为内切圆半径,便可将内切圆以下部分当作手腕部分去除。
//手腕去除
public Mat wrist(Mat src){
Mat mat3=new Mat();
//距离变换
Imgproc.distanceTransform(src,mat3,Imgproc.CV_DIST_L1,3);
//寻找圆心和半径
Mat mat4=new Mat();
src.copyTo(mat4);
int channels=mat3.channels();
cr=0;
cx=0;
cy=0;
float[]data=new float[channels*mat3.cols()];
for (int i=0;i<mat3.rows();i++){
mat3.get(i,0,data);
for (int j=0;j<data.length;j++){
if (data[j]>cr){
cr=data[j];
cx=j;
cy=i;
}
}
}
//绘制内切圆
Imgproc.circle(mat4,new Point(cx,cy),(int) cr,new Scalar(0,0,0),1);
//手掌手腕分割
Mat mat5=new Mat();
src.copyTo(mat5);
int chann=src.channels();
int wi=src.cols();
int hi=src.rows();
byte[]data3=new byte[chann*wi];
for (int i=hi/2;i<hi;i++){
if (i>cy+cr){
mat5.get(i,0,data3);
for (int j=0;j<data3.length;j++){
data3[j]=(byte)0;
}
mat5.put(i,0,data3);
}
}
mat3.release();
mat4.release();
return mat5;
}
手指个数可直接作为数字手势显著的归类几何特征,即从0到5的手指数每个都包含一些数字手势,如0个手指代表0,1个手指代表1和9,2个手指代表2、6和8,3个手指代表3,7,4个手指代表4,5个手指代表5。而可通过相关算法计算二值图的手势凸点,进而根据其几何关系进行二次筛选排除求得相关手势的手指数。
先确定下手指计数可分担后面支持向量机多分类识别的工作量,针对每个手指数所包含的手势训练相对应的分类器(即训练3个分类器,4和5直接用凸点即可识别)。而不只是用一个分类机来进行10分类。
//计算手指个数(凸点)
public void findHull(Mat src){
Mat hierarchy=new Mat();
List<MatOfPoint> contours=new ArrayList<>();
Point center=new Point();
float[] radius={0};
//找寻轮廓
Imgproc.findContours(src,contours,hierarchy,Imgproc.RETR_LIST,Imgproc.CHAIN_APPROX_NONE,new Point(0,0));
MatOfPoint contour=contours.get(0);
MatOfPoint2f point2f=new MatOfPoint2f(contour.toArray());
Imgproc.minEnclosingCircle(point2f,center,radius);
//初始凸点集
ArrayList<Point> convexHullPointArrayList = new ArrayList<Point>();
MatOfInt convexHullMatOfInt = new MatOfInt();
Imgproc.convexHull( contour, convexHullMatOfInt, false);
for(int j=0; j < convexHullMatOfInt.toList().size(); j++){
convexHullPointArrayList.add(contour.toList().get(convexHullMatOfInt.toList().get(j)));
}
//筛除相邻凸点集
ArrayList<Point> convexHullPoint=new ArrayList<>();
convexHullPoint.add(convexHullPointArrayList.get(0));
for(int j=0;j<convexHullPointArrayList.size()-1;j++){
double x1=convexHullPointArrayList.get(j).x;
double y1=convexHullPointArrayList.get(j).y;
double x2=convexHullPointArrayList.get(j+1).x;
double y2=convexHullPointArrayList.get(j+1).y;
if(Math.abs(x2-x1)>20||Math.abs(y2-y1)>20){
convexHullPoint.add(convexHullPointArrayList.get(j+1));
}
}
//手指凸点集
ArrayList<Point> convexHull=new ArrayList<>();
//cx,cy为质心坐标
for(int j=0;j<convexHullPoint.size();j++){
double distancex=convexHullPoint.get(j).x-cx;
double distancey=convexHullPoint.get(j).y-cy;
double distance=cr/3*4;
if(distancex*distancex+distancey*distancey>distance*distance&&convexHullPoint.get(j).y-y<0){
convexHull.add(convexHullPoint.get(j));
}
}
int convexHullcount=convexHull.size();
if(convexHullcount>1){
double x1=convexHull.get(0).x;
double y1=convexHull.get(0).y;
double x2=convexHull.get(convexHullcount-1).x;
double y2=convexHull.get(convexHullcount-1).y;
if(Math.abs(x2-x1)<20||Math.abs(y2-y1)<20){
convexHull.remove(convexHullcount-1);
}
}
hullnum=convexHull.size();
Log.i("result","手指个数:" +hullnum );
}
二、轮廓二值图识别
本阶段即做最后的二值图分类识别工作,将上阶段处理后的手势二值图进行最大外接矩形裁剪,然后提取合适的特征算子向量,根据之前计算好的手指数目,再将其送入对应的SVM分类器识别。
根据手指数(凸点数)加载相应的XML模型(4和5不用加载直接返回结果就行)
float label=0;
SVM mClassifier=SVM.create();
try {
InputStream is1 = getResources().openRawResource(R.raw.svm1);
InputStream is2 = getResources().openRawResource(R.raw.svm2);
InputStream is3 = getResources().openRawResource(R.raw.svm3);
InputStream is;
File svm_modelDir = getDir("svm_model", Context.MODE_PRIVATE);
File mSvmModel = new File(svm_modelDir, "svm.xml");
FileOutputStream os = new FileOutputStream(mSvmModel);
byte[] buffer = new byte[4096];
int bytesRead;
if(recognition.hullnum==1||recognition.hullnum==0)
is=is1;
else if(recognition.hullnum==2)
is=is2;
else if(recognition.hullnum==3)
is=is3;
else if(recognition.hullnum==4)
return label=4;
else
return label=5;
while ((bytesRead = is.read(buffer)) != -1)
{
os.write(buffer, 0, bytesRead);
}
is.close();
os.close();
mClassifier=SVM.load(mSvmModel.getAbsolutePath());
svm_modelDir.delete();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Failed to load xml Exception thrown: " + e);
}
设置HOG相关参数 winSize blockSize blockStide cellSize bins
HOGDescriptor hog = new HOGDescriptor(new Size(96, 128), new Size(96, 64), new Size(96, 32), new Size(48, 32), 4);
MatOfFloat descriptor=new MatOfFloat();
手势二值图进行最大外接矩形裁剪
Mat hierarchy1=new Mat();
List<MatOfPoint> contours1=new ArrayList<MatOfPoint>();
Imgproc.findContours(src,contours1,hierarchy1, Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_NONE,new Point(0,0));
Mat imgRectROI;
try{
MatOfPoint contour=contours1.get(0);
org.opencv.core.Rect R= Imgproc.boundingRect(contour);
imgRectROI= new Mat(src, R);
}catch (Exception e){
imgRectROI = src.clone();
}
Imgproc.resize(imgRectROI,imgRectROI,new Size(96,128));
最后直接给出计算HOG特征参数,送入SVM进行识别,得出预测结果
//计算src图像的hog特征,放入descriptor数组中
hog.compute(imgRectROI,descriptor);
Mat testDescriptor = new Mat(1,descriptor.rows(),CvType. CV_32FC1);
//将hog特征列向量转换为行向量
for (int i = 0; i<descriptor.rows(); i++)
{
testDescriptor.put(0, i, descriptor.get(i,0));
}
label=mClassifier.predict(testDescriptor);
Log.i("result","识别结果:" +label );
好吧....这就算是完整的一个单帧图像识别过程。希望能给正在寻找思路的小伙伴有一些帮助!