Android手指个数识别

一、在Android Studio导入opencv 库。

这里直接给个链接:

一个opencv的学习网站:

二、建立项目

1.整体思路:

(1):点击拍照,调用手机摄像头拍照并保存。

(2):调用刚才拍的照片进行处理。

(3):在界面显示结果。

2.具体步骤:

(1)调用手机摄像头拍照保存并读取的相关权限:

1
2
3
4
5
<uses-permission android:name="android.permission.CAMERA" />
//摄像头权限
<uses-feature android:name="android.hardware.camera" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

(2)界面:包含一个拍照按钮,一个图片展示框和一个文本输出显示手指的个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<Button
android:text="照相"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_gray_process"
android:layout_alignStart="@+id/btn_gray_process"
android:id="@+id/take_photo" />

<ImageView
android:id="@+id/picture"
android:layout_width="match_parent"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="16dp"
android:layout_height="wrap_content" />

<TextView
android:text="手指个数"
android:layout_width="100dp"
android:layout_height="50dp"
android:layout_alignBottom="@+id/take_photo"
android:layout_alignParentEnd="true"
android:layout_marginEnd="91dp"
android:id="@+id/textView"
android:layout_alignParentTop="true" />

(3)

三、具体实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
package com.example.yanhuojun.handgesture1;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.example.yanhuojun.handgesture1.R;

import org.opencv.android.BaseLoaderCallback;

import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfInt4;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.opencv.imgproc.Moments;


import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Vector;

import static java.lang.Math.abs;
import static org.opencv.core.CvType.CV_8UC3;
import static org.opencv.imgproc.Imgproc.THRESH_BINARY;
import static org.opencv.imgproc.Imgproc.circle;
import static org.opencv.imgproc.Imgproc.line;
import static org.opencv.imgproc.Imgproc.moments;

//public class MainActivity extends AppCompatActivity {
public class MainActivity extends Activity {

public static final int TAKE_PHOTO = 1;
//public static final int CROP_PHOTO = 2;
private TextView textView;
private Button takephoto;
private ImageView picture;
private Uri imageUri;
double treash=100;
Mat imageMat;
private static final String TAG = "OCVSample::Activity";

private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
{
Log.i(TAG, "OpenCV loaded successfully");
imageMat=new Mat();
} break;
default:
{
super.onManagerConnected(status);
} break;
}
}
};
@Override
public void onResume()
{
super.onResume();
if (!OpenCVLoader.initDebug()) {
Log.d("OpenCV", "Internal OpenCV library not found. Using OpenCV Manager for initialization");
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_10, this, mLoaderCallback);
} else {
Log.d("OpenCV", "OpenCV library found inside package. Using it!");
mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);
}
}



@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

takephoto = (Button)findViewById(R.id.take_photo);
textView =(TextView) findViewById(R.id.textView);
picture = (ImageView)findViewById(R.id.picture);
takephoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
File outputImage = new File(Environment.getExternalStorageDirectory(), "output_image.jpg");
try {
if (outputImage.exists()) {
outputImage.delete();
}
outputImage.createNewFile();
}catch (IOException e){
e.printStackTrace();
}
imageUri = Uri.fromFile(outputImage);
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");//调用系统相机拍照
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(intent, TAKE_PHOTO);
}
});
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
switch (requestCode){
case TAKE_PHOTO:
//if (resultCode == RESULT_OK){
//Intent intent = new Intent("com.android.camera.action.CROP");
//intent.setDataAndType(imageUri, "image/*");
// intent.putExtra("scale", true);
//intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
//startActivityForResult(intent, CROP_PHOTO);
//}
//break;
//case CROP_PHOTO:
if (resultCode == RESULT_OK){
try{
//获取原图
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
picture.setImageBitmap(bitmap);//设置显示图像
//图像处理
Mat mat_src = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC4);//获取原图(8位无符号的四通道,带透明的RGB图像)
Utils.bitmapToMat(bitmap, mat_src);//将bitmap转化成mat
Mat mat_gray = new Mat(mat_src.cols(), mat_src.rows(), CvType.CV_8UC1);//灰度图,单通道
Imgproc.cvtColor(mat_src, mat_gray, Imgproc.COLOR_BGRA2GRAY, 1);//转变颜色

//-------------------------------------------------------------------
Mat frame = new Mat(bitmap.getHeight(), bitmap.getWidth(), CvType.CV_8UC4);//取出原图
mat_src.copyTo(frame);//复制,不会牵连改变

Mat frameHSV = new Mat(mat_src.cols(), mat_src.rows(), CV_8UC3);//三通道的RGB图像
; // hsv空间(色调,饱和度,明度)
Mat mask = new Mat(mat_src.cols(), mat_src.rows(), CvType.CV_8UC1);
Mat dst = new Mat(mat_src.cols(), mat_src.rows(), CV_8UC3); // 输出图像
//dst.copyTo(frame);
// 中值滤波,去除椒盐噪声
Imgproc.medianBlur(frame, frame, 5);
Imgproc.cvtColor(frame, frameHSV, Imgproc.COLOR_RGB2HSV, 3);//把frame的颜色空间转换后复制到frameHSV
Mat dstTemp1 = new Mat(mat_src.cols(), mat_src.rows(), CvType.CV_8UC1);
Mat dstTemp2 = new Mat(mat_src.cols(), mat_src.rows(), CvType.CV_8UC1);
// 对HSV空间进行量化,得到二值图像,亮的部分为手的形状
Core.inRange(frameHSV, new Scalar(0, 30, 30), new Scalar(40, 170, 256), dstTemp1);//比较三个通道中的元素是否在相应的区间类,不在的画的则改成255
Core.inRange(frameHSV, new Scalar(156, 30, 30), new Scalar(180, 170, 256), dstTemp2);
Core.bitwise_or(dstTemp1, dstTemp2, mask);//对前面的两张图片(二值图像)进行异或处理,并将其结果给第三章图

// 形态学操作,去除噪声,并使手的边界更加清晰
Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));//定义一个合适大小的核
Imgproc.erode(mask, mask, element);//扩大暗区,腐蚀
Imgproc.morphologyEx(mask, mask, Imgproc.MORPH_OPEN, element);//对输入图像执行开运算
Imgproc.dilate(mask, mask, element);//扩大亮区,膨胀
Imgproc.morphologyEx(mask, mask, Imgproc.MORPH_CLOSE, element);//执行闭运算

frame.copyTo(dst, mask);

List<MatOfPoint> contourList = new ArrayList<MatOfPoint>();
Vector<MatOfPoint> contours=new Vector<MatOfPoint>();
Vector<Vector<Point>> con=new Vector<Vector<Point>>();

MatOfInt4 hierarchy=new MatOfInt4();
//Imgproc.Canny(mat_src,mask,mask,treash*2,3);
// // 得到手的轮廓
Imgproc.findContours(mask, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
//List<Moments> mu=new ArrayList<Moments>(contourList.size()) ;
//mu.set(0,moments(contourList.get(0),true));
// for( int i = 0; i < contourList.size(); i++ )
//{ mu.set(i,moments( contourList.get(i), false )); }
/// 计算中心矩:


//Log.e(TAG, "index=:"+index+"  counter.size="+contours.size()+"  area="+area);
//**********************************************************8
// dst = Mat::zeros( mask.size(), CV_8UC3 );
//Random r = new Random();
//int nContoursNum = contours.size();
//for (int i = 0; i < nContoursNum; i++) {
// Imgproc.drawContours(dst, contours, i, new Scalar(r.nextInt(255), r.nextInt(255), r.nextInt(255)), -1);
// }
//Point center=mc.get(0);
// Mat mat_gray1 = new Mat(mat_src.cols(), mat_src.rows(), CvType.CV_8UC1);//灰度图
//Imgproc.cvtColor(dst, mat_gray1, Imgproc.COLOR_BGRA2GRAY, 1);//转变颜色
Moments moment = moments(mask,false);
Point center = new Point(moment.m10 / moment.m00, moment.m01 / moment.m00);//计算图形重心
circle(dst, center, 8, new Scalar(0, 0, 255), -1);//最后一位参数不确定*/
//-------------------------------------------------------------------
//图像显示
Vector<MatOfPoint2f> newcont=new Vector<MatOfPoint2f>();
for(MatOfPoint point : contours) {
MatOfPoint2f newPoint = new MatOfPoint2f(point.toArray());
newcont.add(newPoint);
}
Vector<MatOfPoint2f> cont=new Vector<>();
for(int i=0;i<newcont.size();i++)
{
MatOfPoint2f first=newcont.get(i);
MatOfPoint2f second=new MatOfPoint2f();
Imgproc.approxPolyDP(first,second, 80, true);
cont.add(second);
}
Vector<MatOfPoint> polyedges = new Vector<>();
for(MatOfPoint2f point : cont) {
MatOfPoint nPoint = new MatOfPoint(point.toArray());
polyedges.add(nPoint);
}
Vector<Point> poly=new Vector<Point>();
MatOfPoint aPoint = polyedges.get(0);
Point[] ab=aPoint.toArray();
//Iterator<Point> itp = poly.listIterator(0);
//Point aa=poly.get(0);
for(int j=0;j<polyedges.size();j++) {
for (int i = 0; i < polyedges.get(j).toArray().length - 1; i++) {
line(dst, polyedges.get(j).toArray()[i], polyedges.get(j).toArray()[i + 1], new Scalar(255, 255, 255), 5);
//aa=itp.next();
}
line(dst,polyedges.get(j).toArray()[polyedges.get(j).toArray().length - 1],polyedges.get(j).toArray()[0], new Scalar(255,255,255), 5);
}


//Imgproc.threshold( mat_gray, dst, 100, 255, THRESH_BINARY );

/*RNG rng(12345);
Vector<Vector<Point> >hull=new Vector<Vector<Point>>( contours.size() );
for( int i = 0; i < contours.size(); i++ )
{ Imgproc.convexHull(new Mat((MatOfPoint2f)(contours.get(i))), hull.get(i), false ); }

/// 绘出轮廓及其凸包
Mat drawing = Mat.zeros( dst.size(), CV_8UC3 );
for( int i = 0; i< contours.size(); i++ )
{
Scalar color = Scalar( rng.uniform(0, 255), rng.uniform(0,255), rng.uniform(0,255) );
Imgproc.drawContours( drawing, contours, i, color, 1, 8, Vector<Vec4i>(), 0, Point() );
drawContours( drawing, hull, i, color, 1, 8, vector<Vec4i>(), 0, Point() );
}*/
//**********************************************************************
int index = 0;
double area = 0, maxArea=0;
for (int i=0;i < polyedges.size(); i++)
{
area = Imgproc.contourArea(polyedges.get(i));
if (area > maxArea)
{
maxArea = area;
index = i;
}
}
MatOfPoint couPoint = polyedges.get(index);
Point[] a=couPoint.toArray();
// List<Point> couPoint = contours.get(nContoursNum);
Vector<Point> fingerTips = new Vector<Point>();
Point tmp = new Point();
double max = 0;
int count = 0, notice = 0;
for (int i = 0; i < a.length; i++) {
tmp = a[i];
double dist = (tmp.x - center.x) * (tmp.x - center.x) + (tmp.y - center.y) * (tmp.y - center.y);
if (dist > max) {
max = dist;
notice = i;
}
// 计算最大值保持的点数,如果大于40(这个值需要设置,本来想根据max值来设置,
// 但是不成功,不知道为何),那么就认为这个是指尖
if (dist != max) {
max = 0;
boolean flag = false;
// 低于手心的点不算
if (center.y < a[notice].y)
continue;
// 离得太近的不算
for (int j = 0; j < fingerTips.size(); j++) {
if (abs(a[notice].x - fingerTips.get(j).x) < 40) {
flag = true;
break;
}
}
if (flag) continue;
fingerTips.add(a[notice]);
circle(dst, a[notice], 6, new Scalar(255, 255, 255), -1);
line(dst, center, a[notice], new Scalar(0, 255, 255), 5);

}
}
textView.setText(String.valueOf(fingerTips.size()));
//*****************************************************
Bitmap bmp_gray = Bitmap.createBitmap(mat_gray.cols(), mat_gray.rows(), Bitmap.Config.ARGB_8888);
Utils.matToBitmap(dst, bmp_gray);
picture.setImageBitmap(bmp_gray);

}catch (FileNotFoundException e){
e.printStackTrace();
}
}
break;
default:
break;
}
}

}

四、程序效果:

五、不足:

1.程序鲁棒性不足,容易收到背景环境的干扰,尤其是与肤色相近的环境。

2.处理速度慢,要等待数秒左右。

3.弯曲手指不彻底就会影响结果。