肥宅钓鱼网
当前位置: 首页 钓鱼百科

安卓手势提示线适配(Android人工智能应用-如何让手机能明白你的手势)

时间:2023-06-11 作者: 小编 阅读量: 1 栏目名: 钓鱼百科

先说下这个数字手势识别APP的功能:能够识别做出的0,1,2,3,4,5,6,7,8,9,10这11个手势。APP通过手机摄像头拍摄出来的照片,不同机型有差异,要统一。对图片的缩放不能简单的直接缩小尺寸,那样的话会失真严重。我这里使用了面积插值法。

这篇博客主要基于我做的一个数字手势识别APP,具体分享下如何一步步训练一个卷积神经网络模型(CNN)模型,然后把模型集成到Android Studio中,开发一个数字手势识别APP。整个project的源码已经开源在github上,github地址:Chinese-number-gestures-recognition,欢迎star,哈哈。先说下这个数字手势识别APP的功能:能够识别做出的 0,1,2,3,4,5,6,7,8,9,10这11个手势。

一、数据集的收集

这么点照片想训练模型简直天方夜谭,只能祭出 data augmentation(数据增强)神器了,通过旋转,平移,拉伸 等操作每张图片生成100张,这样图片就变成了21500张。下面是 data augmentation 的代码:

from keras.preprocessing.image import ImageDataGenerator, img_to_array, load_imgimport osdatagen = ImageDataGenerator( rotation_range=20, width_shift_range=0.15, height_shift_range=0.15, zoom_range=0.15, shear_range=0.2, horizontal_flip=True, fill_mode='nearest')dirs = os.listdir("picture")print(len(dirs))for filename in dirs: img = load_img("picture//{}".format(filename)) x = img_to_array(img) # print(x.shape) x = x.reshape((1,)x.shape) #datagen.flow要求rank为4 # print(x.shape) datagen.fit(x) prefix = filename.split('.')[0] print(prefix) counter = 0 for batch in datagen.flow(x, batch_size=4 , save_to_dir='generater_pic', save_prefix=prefix, save_format='jpg'): counter= 1 if counter > 100: break # 否则生成器会退出循环

二、数据集的处理

1.缩放图片

接下来对这21500张照片进行处理,首先要把每张照片缩放到64*64的尺寸,这么做的原因如下:

  • 不同手机拍出的照片的size各不相同,要统一
  • 如果手机拍出来的高分辨率图片,太大,GPU显存有限,要压缩下,减少体积。
  • APP通过手机摄像头拍摄出来的照片,不同机型有差异,要统一。

对图片的缩放不能简单的直接缩小尺寸,那样的话会失真严重。所以要用到一些缩放算法,TensorFlow中已经提供了四种缩放算法,分别为: 双线性插值法(Bilinear interpolation)、最近邻居法(Nearest neighbor interpolation)、双三次插值法(Bicubic interpolation)和面积插值法(area interpolation)。 我这里使用了面积插值法(area interpolation)。代码为:

#压缩图片,把图片压缩成64*64的def resize_img(): dirs = os.listdir("split_pic//6") for filename in dirs: im = tf.gfile.FastGFile("split_pic//6//{}".format(filename), 'rb').read() # print("正在处理第%d张照片"%counter) with tf.Session() as sess: img_data = tf.image.decode_jpeg(im) image_float = tf.image.convert_image_dtype(img_data, tf.float32) resized = tf.image.resize_images(image_float, [64, 64], method=3) resized_im = resized.eval() # new_mat = np.asarray(resized_im).reshape(1, 64, 64, 3) scipy.misc.imsave("resized_img6//{}".format(filename),resized_im)

2.把图片转成 .h5文件

h5文件的种种好处,这里不再累述。我们首先把图片转成RGB矩阵,即每个图片是一个64643的矩阵(因为是彩色图片,所以通道是3)。这里不做归一化,因为我认为归一化应该在你用到的时候自己代码归一化,如果直接把数据集做成了归一化,有点死板了,不灵活。在我们把矩阵存进h5文件时,此时标签一定要对应每一张图片(矩阵),直接上代码:

#图片转h5文件def image_to_h5(): dirs = os.listdir("resized_img") Y = [] #label X = [] #data print(len(dirs)) for filename in dirs: label = int(filename.split('_')[0]) Y.append(label) im = Image.open("resized_img//{}".format(filename)).convert('RGB') mat = np.asarray(im) #image 转矩阵 X.append(mat) file = h5py.File("dataset//data.h5","w") file.create_dataset('X', data=np.array(X)) file.create_dataset('Y', data=np.array(Y)) file.close() #test # data = h5py.File("dataset//data.h5","r") # X_data = data['X'] # print(X_data.shape) # Y_data = data['Y'] # print(Y_data[123]) # image = Image.fromarray(X_data[123]) #矩阵转图片并显示 # image.show()

训练模型

接下来就是训练模型了,首先把数据集划分为训练集和测试集,然后先坐下归一化,把标签转化为one-hot向量表示,代码如下:

#load datasetdef load_dataset(): #划分训练集、测试集 data = h5py.File("dataset//data.h5","r") X_data = np.array(data['X']) #data['X']是h5py._hl.dataset.Dataset类型,转化为array Y_data = np.array(data['Y']) # print(type(X_data)) X_train, X_test, y_train, y_test = train_test_split(X_data, Y_data, train_size=0.9, test_size=0.1, random_state=22) # print(X_train.shape) # print(y_train[456]) # image = Image.fromarray(X_train[456]) # image.show() # y_train = y_train.reshape(1,y_train.shape[0]) # y_test = y_test.reshape(1,y_test.shape[0]) print(X_train.shape) # print(X_train[0]) X_train = X_train / 255. # 归一化 X_test = X_test / 255. # print(X_train[0]) # one-hot y_train = np_utils.to_categorical(y_train, num_classes=11) print(y_train.shape) y_test = np_utils.to_categorical(y_test, num_classes=11) print(y_test.shape) return X_train, X_test, y_train, y_test

构建CNN模型,这里用了最简单的类LeNet-5,具体两层卷积层、两层池化层、一层全连接层,一层softmax输出。具体的小trick有:dropout、relu、regularize、mini-batch、adam。具体看代码吧:

def weight_variable(shape): tf.set_random_seed(1) return tf.Variable(tf.truncated_normal(shape, stddev=0.1))def bias_variable(shape): return tf.Variable(tf.constant(0.0, shape=shape))def conv2d(x, W): return tf.nn.conv2d(x, W, strides=[1,1,1,1], padding='SAME')def max_pool_2x2(z): return tf.nn.max_pool(z, ksize=[1,2,2,1], strides=[1,2,2,1], padding='SAME')def random_mini_batches(X, Y, mini_batch_size=16, seed=0): """ Creates a list of random minibatches from (X, Y) Arguments: X -- input data, of shape (input size, number of examples) Y -- true "label" vector (containing 0 if cat, 1 if non-cat), of shape (1, number of examples) mini_batch_size - size of the mini-batches, integer seed -- this is only for the purpose of grading, so that you're "random minibatches are the same as ours. Returns: mini_batches -- list of synchronous (mini_batch_X, mini_batch_Y) """ m = X.shape[0] # number of training examples mini_batches = [] np.random.seed(seed) # Step 1: Shuffle (X, Y) permutation = list(np.random.permutation(m)) shuffled_X = X[permutation] shuffled_Y = Y[permutation,:].reshape((m, Y.shape[1])) # Step 2: Partition (shuffled_X, shuffled_Y). Minus the end case. num_complete_minibatches = math.floor(m / mini_batch_size) # number of mini batches of size mini_batch_size in your partitionning for k in range(0, num_complete_minibatches): mini_batch_X = shuffled_X[k * mini_batch_size: k * mini_batch_sizemini_batch_size] mini_batch_Y = shuffled_Y[k * mini_batch_size: k * mini_batch_sizemini_batch_size] mini_batch = (mini_batch_X, mini_batch_Y) mini_batches.append(mini_batch) # Handling the end case (last mini-batch < mini_batch_size) if m % mini_batch_size != 0: mini_batch_X = shuffled_X[num_complete_minibatches * mini_batch_size: m] mini_batch_Y = shuffled_Y[num_complete_minibatches * mini_batch_size: m] mini_batch = (mini_batch_X, mini_batch_Y) mini_batches.append(mini_batch) return mini_batchesdef cnn_model(X_train, y_train, X_test, y_test, keep_prob, lamda, num_epochs = 450, minibatch_size = 16): X = tf.placeholder(tf.float32, [None, 64, 64, 3], name="input_x") y = tf.placeholder(tf.float32, [None, 11], name="input_y") kp = tf.placeholder_with_default(1.0, shape=(), name="keep_prob") lam = tf.placeholder(tf.float32, name="lamda") #conv1 W_conv1 = weight_variable([5,5,3,32]) b_conv1 = bias_variable([32]) z1 = tf.nn.relu(conv2d(X, W_conv1)b_conv1) maxpool1 = max_pool_2x2(z1) #max_pool1完后maxpool1维度为[?,32,32,32] #conv2 W_conv2 = weight_variable([5,5,32,64]) b_conv2 = bias_variable([64]) z2 = tf.nn.relu(conv2d(maxpool1, W_conv2)b_conv2) maxpool2 = max_pool_2x2(z2) #max_pool2,shape [?,16,16,64] #conv3 效果比较好的一次模型是没有这一层,只有两次卷积层,隐藏单元100,训练20次 # W_conv3 = weight_variable([5, 5, 64, 128]) # b_conv3 = bias_variable([128]) # z3 = tf.nn.relu(conv2d(maxpool2, W_conv3)b_conv3) # maxpool3 = max_pool_2x2(z3) # max_pool3,shape [?,8,8,128] #full connection1 W_fc1 = weight_variable([16*16*64, 200]) b_fc1 = bias_variable([200]) maxpool2_flat = tf.reshape(maxpool2, [-1, 16*16*64]) z_fc1 = tf.nn.relu(tf.matmul(maxpool2_flat, W_fc1)b_fc1) z_fc1_drop = tf.nn.dropout(z_fc1, keep_prob=kp) #softmax layer W_fc2 = weight_variable([200, 11]) b_fc2 = bias_variable([11]) z_fc2 = tf.add(tf.matmul(z_fc1_drop, W_fc2),b_fc2, name="outlayer") prob = tf.nn.softmax(z_fc2, name="probability") #cost function regularizer = tf.contrib.layers.l2_regularizer(lam) regularization = regularizer(W_fc1)regularizer(W_fc2) cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=y, logits=z_fc2))regularization train = tf.train.AdamOptimizer().minimize(cost) # output_type='int32', name="predict" pred = tf.argmax(prob, 1, output_type="int32", name="predict") # 输出结点名称predict方便后面保存为pb文件 correct_prediction = tf.equal(pred, tf.argmax(y, 1, output_type='int32')) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) tf.set_random_seed(1) # to keep consistent results seed = 0 init = tf.global_variables_initializer() with tf.Session() as sess: sess.run(init) for epoch in range(num_epochs): seed = seed1 epoch_cost = 0. num_minibatches = int(X_train.shape[0] / minibatch_size) minibatches = random_mini_batches(X_train, y_train, minibatch_size, seed) for minibatch in minibatches: (minibatch_X, minibatch_Y) = minibatch _, minibatch_cost = sess.run([train, cost], feed_dict={X: minibatch_X, y: minibatch_Y, kp: keep_prob, lam: lamda}) epoch_cost= minibatch_cost / num_minibatches if epoch % 10 == 0: print("Cost after epoch %i: %f" % (epoch, epoch_cost)) print(str((time.strftime('%Y-%m-%d %H:%M:%S')))) # 这个accuracy是前面的accuracy,tensor.eval()和Session.run区别很小 train_acc = accuracy.eval(feed_dict={X: X_train[:1000], y: y_train[:1000], kp: 0.8, lam: lamda}) print("train accuracy", train_acc) test_acc = accuracy.eval(feed_dict={X: X_test[:1000], y: y_test[:1000], lam: lamda}) print("test accuracy", test_acc) #save model saver = tf.train.Saver({'W_conv1':W_conv1, 'b_conv1':b_conv1, 'W_conv2':W_conv2, 'b_conv2':b_conv2, 'W_fc1':W_fc1, 'b_fc1':b_fc1, 'W_fc2':W_fc2, 'b_fc2':b_fc2}) saver.save(sess, "model_500_200_c3//cnn_model.ckpt") #将训练好的模型保存为.pb文件,方便在Android studio中使用 output_graph_def = graph_util.convert_variables_to_constants(sess, sess.graph_def, output_node_names=['predict']) with tf.gfile.FastGFile('model_500_200_c3//digital_gesture.pb', mode='wb') as f: # ’wb’中w代表写文件,b代表将数据以二进制方式写入文件。 f.write(output_graph_def.SerializeToString())

这里有一个非常非常非常重要的事情,要注意,具体请参考上一篇博客中的 2. 模型训练注意事项 链接为:将TensorFlow训练好的模型迁移到Android APP上(TensorFlowLite)。整个模型训练几个小时即可,当然调参更是门艺术活,不多说了。

这里小小感慨下,i7-7700k跑一个epoch需要2分钟,750ti需要36秒,1070需要6秒。。。这里再次感谢宋俞璋的神机。。关于如何搭建TensorFlow GPU环境,请参见我的博客:ubuntu16.04 GTX750ti python3.6.5配置cuda9.0 cudnn7.05 TensorFlow-gpu1.8.0

训练完的模型性能:

但是在APP上因为面临的环境更加复杂,准备远没有这么高。

PC端随便实测的效果图:

4.在Android Studio中调用训练好的模型

关于如何把模型迁移到Android studio中,请参考我的上一篇博客:将TensorFlow训练好的模型迁移到Android APP上(TensorFlowLite)。这里面解释下为何会用到OpenCV,这一切都要源于那个图片缩放,还记得我们在上面提到的area interpolation吗,这个算法不像那些双线性插值法等,网上并没有java版本的实现,无奈去仔细翻了遍TensorFlow API文档,发现这么一段话:

Each output pixel is computed by first transforming the pixel’s footprint into the input tensor and then averaging the pixels that intersect the footprint. An input pixel’s contribution to the average is weighted by the fraction of its area that intersects the footprint. This is the same as OpenCV’s INTER_AREA.

这就是为什么会用OpenCV了,OpenCV在Android studio中的配置也是坑多,具体的配置请参见我的博客:Android Studio中配置OpenCV。这里只说下,TensorFlowLite只提供了几个简单的接口,虽然在我的博客将TensorFlow训练好的模型迁移到Android APP上(TensorFlowLite)也提过了,但是这里还是想提一下,提供的接口官网地址

// Load the model from disk.TensorFlowInferenceInterface inferenceInterface =new TensorFlowInferenceInterface(assetManager, modelFilename);// Copy the input data into TensorFlow.inferenceInterface.feed(inputName, floatValues, 1, inputSize, inputSize, 3);// Run the inference call.inferenceInterface.run(outputNames, logStats);// Copy the output Tensor back into the output array.inferenceInterface.fetch(outputName, outputs);

注释也都说明了各个接口的作用,就不多说了。

我也不知道是不是因为OpenCV里的area interpolation算法实现的和TensorFlow不一样还是其他什么原因,总感觉在APP上测得效果要比在PC上模型性能差。。也许有可能只是我感觉。。

关于Android APP代码也没啥好说的了,代码都放到github上了,地址:Chinese-number-gestures-recognition,欢迎star,哈哈。

下面上几张测试的效果图吧,更多的展示效果见github,:Chinese-number-gestures-recognition

看到这儿,学会了吗?

最后,小编为大家准备了 从零基础到大佬的Android视频教程,先转发 关注,然后

私信小编“资料”就可以啦!

,
    推荐阅读
  • 稻盛和夫心法和活法(稻盛和夫生而为人)

    尺子可以对一切物体的长度做出正确的判断。天合联盟一方也提出,如果日航脱离原来的寰宇联盟,加入天合联盟,“达美”将给予巨额支援。日航内部同意加入天合联盟的意见,也占了绝大多数。第二年,在311九级大地震的情况下,日航的利润高达2049亿日元。就是因为日航干部员工共同拥有上述的判断基准,在这基础之上,大家团结一致,群策群力,改革改善。让坠落谷底的日航一跃而为全世界最优秀的航空公司,业绩持续遥遥领先。

  • 肺部长了几个小结节怎么办(彻底读懂肺部小结节)

    肺部发现小结节,决不可忽视!首先我们需要明确两点:一、肺部小结节和肺癌啥关系?“百度百科”对肺部小结节的界定为,“肺部小结节又称为小肺癌,周围型肺癌瘤体直径≤2cm者称为小肺癌,直径≤1cm者称为微小肺癌。”由此看来,良性恶性是有规律可循的。癌前病变或者已经是癌的肺部小结节:多数没有任何症状。

  • 中秋节你们公司都发了什么福利(老板问我中秋节发什么员工福利)

    企业中秋福利方案第一步:确定预算在度娘上搜搜,根据XX调查研究机构数据显示:中国企业中秋福利整体预算成上升趋势,预算在200元、100元和150元的企业居多,并且300元以上预算的企业正在逐年增加。阿里巴巴四个安全部门利用系统漏洞刷单抢月饼,结果……正当行政er用心评比着,中秋节员工福利方案时,提交的预算申请单下来了。“二斤月饼”和供应商疯狂砍砍价的话,还是可以有的。

  • 世界喜爱(世界喜爱香菜日)

    世界喜爱郁金香,英文学名Tulipa'WorldFavourite'。

  • 龟的读音(龟的读音是什么)

    跟着小编一起来看一看吧!龟的读音龟的读音[guī]爬行动物。有些种类的龟,头、尾和脚可缩入甲内。在水中或陆上都能生活,寿命很长。有的龟甲可供药用。[jūn]同“皲”。[qiū]〔龟兹〕古地名。唐贞观二十二年设置龟兹都督府。后并入安西都护府。唐代安西四镇(军镇)之一,先后受安西都护府及安西节度使统辖。在今新疆库车一带。

  • 普通电饭锅做魔芋方法(普通电饭锅怎样煮魔芋)

    普通电饭锅做魔芋方法五花肉入冷水小火焯水,焯好后用温水洗净。两次焯水可以减少很多油脂,吃起来不腻口。电饭煲放五花肉,小火榨出油脂来。可以盖上盖子防止迸溅。电饭锅放五花肉,八角,香叶,桂皮,料酒,加水没过五花肉,盒盖,选煮肉键。肉好后,把汤汁和料倒掉。把五花肉,魔芋,生抽,老抽,冰糖,五香粉,花椒粉倒入电饭锅,拌匀,盖盖子,选选个短一点的时间,比如煮菜。做好后,根据情况开锅再烧一会儿,收汁。

  • 家常芹菜炒肉的做法(怎么做芹菜炒肉)

    下面更多详细答案一起来看看吧!家常芹菜炒肉的做法主料:芹菜500g,猪肉300g。将猪肉切片备用。锅中放油,油烧热后放入肉片炒至肉片变色。肉变色后,加入料酒、酱油翻炒几下,放入芹菜。翻炒几分钟,加入盐和味精,翻炒几下即可出锅装盘,一盘美味的芹菜炒肉就成了!

  • 白水煮面条放什么调料(白水煮面条的做法)

    以下内容希望对你有帮助!等面条煮到半透明时候就可以捞出了,如果不把握,就捞出来一根尝尝即可。把捞出来的面条放入油调料汁的碗中。把准备好的蔬菜放入锅中,水开后,捞出蔬菜搬入面条中,放入辣椒油,一碗色香味俱全的懒人面条就做好啦。

  • 广西河池东兰县县城叫什么镇(河池东兰县一个镇)

    隘洞镇也成为了东兰县东部山货的集散地。隘洞镇陆运交通也较为便利,有高速公路穿过,并设有出入口。隘洞镇内山地众多,山货成了该镇的特色产业。隘洞镇素有“板栗之乡”的美誉,全镇板栗种植面积占了全县的四成,年板栗产量1500吨、产值600万元。隘洞镇俨然已经成了东兰县最大的板栗集散地,还是周边县市的主要板栗交易市场。隘洞镇的板栗果实色泽鲜艳、外观整齐、果肉细腻、风味独特。

  • 新手机第一次充电要充多长时间(给新手机充电充多久)

    自从2010年左右起,基本都是锂电池为主,现在的话各大品牌的手机基本都是锂电池,现在的不要将电量完全耗尽后充电,会影响电池使用寿命,就算第一次充电,电量不足时就可以充满了。现在铝电池不要完全放电,及充电过长,过度的充放电会导致锂电池的快速老化,另外我们的充电头有1A和2A的型号,手机一般配置的是1A,要用手机专用充电器,以免发生意外。