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

安卓手势提示线适配(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视频教程,先转发 关注,然后

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

,
    推荐阅读
  • 关于师恩的诗句(关于师恩的经典诗句)

    关于师恩的诗句传道授业解惑心,沥血耕耘育树人。一日飞腾穷九域,三年得意垢尘清。鹏程万里凭师带,腹有诗书内涵升。风雨校园路,矢志在初衷。人生轨迹,尽在曲线弧度中;岁月印痕,都含加减乘除里。默默无闻三尺台,辛勤园丁在浇园。丝尽春蚕应体念,再谢恩师鞠躬忙。不计辛勤一砚寒,桃熟流丹,李熟枝残,种花容易树人难。无贵无贱,无长无少,道之所存,师之所存也。微燃蜡矩虽灰烬,照亮人生火一团。

  • 菊花的效果和作用(小小菊花的大大作用)

    4用于疮疡肿毒。1呼吸道和胃肠道感染菊花对金黄色葡萄球菌、乙型链球菌、多种致病性杆菌及皮肤真菌尊有一定抗菌作用。故对呼吸道咽炎、扁桃腺炎、气管炎等有治疗作用。对肠道感染、便秘等有辅助治疗作用。2心血管疾病应用菊甙有降压作用,可应用高血压的治疗。菊花能明显扩张冠状动脉,增加其血流量,可用于冠心病的辅助治疗。3眩晕的治疗由菊花为主药的方剂,天麻钩藤汤;羚羊钩藤汤对眩晕,头疼、头晕有明显效果。

  • 士不可以不弘毅任重而道远原文(士不可以不弘毅任重而道远解释)

    下面更多详细答案一起来看看吧!士不可以不弘毅,任重而道远的意思是作为一个士人,一个君子,必须要有宽广、坚韧的品质,因为自己责任重大,道路遥远。

  • iphone为什么不用装杀毒软件(为什么iPhone不需要杀毒软件)

    因为苹果的iOS平台在设计时就考虑到了安全问题,它可以防止应用程序获得进行任何破坏所需的权限。因为苹果以安全性为核心设计了iOS平台。这些钩子提供非常深入的访问以监视正在发生的事情并检测恶意软件,并可能导致杀毒软件成为攻击的目标。由于设计将应用程序与系统的其余部分深度隔离,因此iOS不允许将此类挂钩锁定到其系统中,这一过程通常称为沙盒。

  • 宽豆角焖炒面(长豆角快上市了)

    天气热了,应季的蔬菜也多了起来,如青菜,卷心菜,芹菜,辣椒,黄瓜,冬瓜,西红柿,茄子等,有些菜将作为人们最近几个月的常菜,比如:黄瓜,辣椒,西红柿,豆角。在这里天宇着重介绍下豆角,一款华山人喜爱的美食食材。当然不光产量大,还有它做法多样,尤其做饺子馅特别美味,刚好迎合了当地人喜爱饺子的口味。华山人爱吃饺子,韭菜馅全年盛行,冬季以莲菜为主,夏季则以豆角为主。

  • 我和你一样一样的坚强是什么歌(歌词里有我和你一样一样的坚强的是什么歌)

    我和你一样一样的坚强是什么歌《和你一样》——李宇春扩展资料:《和你一样》是李宇春演唱的歌曲,发行于2008年4月29日,收录于2008年12月16日李宇春发行的专辑《N+1Evolution》中。而一个星期后,“忽悠”就把歌曲《和你一样》的初稿带来了,后进行编曲与演唱。由10多位上海玉米自筹资金并合唱,之后将歌曲放入贴吧中获得关注。李宇春,1984年3月10日出生于成都,中国流行女歌手、演员、词曲创作人、演唱会导演。

  • 风行s500尊享(瑕不掩瑜测试风行S500)

    相比同级对手,拥有自动挡车型是风行S500的一大优势,使用更便利,更受消费者欢迎。我们这次测试的是顶配的1.6LCVT尊贵型,官方指导价为9.99万,下文简称风行S500。风行S500使用电动转向助力,方向盘转向力度不沉,大部分消费者都能接受,虚位也不大,整体调校挺合适的。真正要给好评的是风行S500的减振性能。风行S500最终加速成绩为13.99秒,表现一般有点慢。风行S500的最终成绩为39.65米,接近优秀水平。

  • 无烟煤和有烟煤的区别(烟煤的分类有哪些?)

    烟煤由褐煤经变质作用转变而成的煤种,煤化程度高于褐煤而低于无烟煤。无烟煤,俗称白煤或红煤。无烟煤固定碳含量高,挥发分产率低,密度大,硬度大,燃点高,燃烧时不冒烟。以脂摩擦不致染污,断口成介壳状,燃烧时火焰短而少烟。有时把挥发物含量特大的称做半无烟煤;特小的称做高无烟煤。挥发分为10%~40%,一般随煤化程度增高而降低。大部分烟煤具有粘结性,燃烧时火焰高而有烟,故名烟煤。烟煤燃烧多烟容易造成空气污染。

  • fla(al20是什么型号(FLA-AL10的介绍)

    接下来我们就一起去研究一下吧!fla背贴型号FLA-AL10是华为畅享8Plus全网通版。华为畅享8Plus是华为研发的智能手机,定位年轻人的千元双摄,搭载麒麟659处理器,提供4GB+64GB和4GB+128GB的存储组合,5.93英寸全面屏。2018年3月29日,华为在西安发布华为畅享8Plus手机。支持FHD+分辨率,提供面部识别功能。拍照方面,华为畅享8Plus提供了四镜头设计,前后都采用了双摄像头,拥有1600万像素+200万像素前置双摄和1300万像素和200万像素的后置双摄组合。

  • 湖南沅陵古巷故事(湖南小县沅陵在古代)

    尤其在金庸的小说当中。在《倚天屠龙记》当中,有一段明教神医“蝶谷医仙”胡青牛对张无忌说;“……有一个少年,他在贵州苗疆中了金蚕蛊毒,那是无比的剧毒,中者固然非死不可,而且临死之前身历天下诸般最难当的苦楚。我三日三晚不睡,耗尽心力救治了他……”