阶段二
人工标注训练集数据
由于我们是通过图片分类算法来对启动各个阶段进行识别的,所以首先要定义启动的阶段都有哪些,这里我分为5个阶段:
- 0_desk:桌面阶段
- 1_start:点击icon图标的阶段
- 2_splash:闪屏页出现的阶段
- 3_loading:首页加载的阶段
- 4_stable:首页渲染稳定的阶段
这五个阶段的图片如下:
由于应用还会有广告页、业务弹框、首页动态变化等,这些暂时先忽略,不影响整体的测试思路。
特征提取与描述子生成
这里选择SIFT特征,SIFT特征具有缩放、旋转、光照不变性,同时对图像几何变形有一定程度的鲁棒性,使用Python OpenCV扩展模块中的SIFT特征提取接口,就可以提取图像的SIFT特征点与描述子。
词袋生成
词袋生成,是基于描述子数据的基础上,生成一系列的向量数据,最常见就是首先通过K-Means实现对描述子数据的聚类分析,一般会分成100个聚类、得到每个聚类的中心数据,就生成了100个词袋,根据每个描述子到这些聚类中心的距离,决定了它属于哪个聚类,这样就生成了它的直方图表示数据。
SVM分类训练与模型生成
使用SVM进行数据的分类训练,得到输出模型,这里通过sklearn的线性SVM实现了分类模型的训练与导出。
import cv2 import imutils import numpy as np import os from sklearn.svm import LinearSVC from sklearn.externals import joblib from scipy.cluster.vq import * from sklearn.preprocessing import StandardScaler # Get the training classes names and store them in a list train_path = "dataset/train/" training_names = os.listdir(train_path) # Get all the path to the images and save them in a list # image_paths and the corresponding label in image_paths image_paths = [] image_classes = [] class_id = 0 for training_name in training_names: dir = os.path.join(train_path, training_name) class_path = imutils.imlist(dir) image_paths += class_path image_classes += [class_id] * len(class_path) class_id += 1 # 创建SIFT特征提取器 sift = cv2.xfeatures2d.SIFT_create() # 特征提取与描述子生成 des_list = [] for image_path in image_paths: im = cv2.imread(image_path) im = cv2.resize(im, (300, 300)) kpts = sift.detect(im) kpts, des = sift.compute(im, kpts) des_list.append((image_path, des)) print("image file path : ", image_path) # 描述子向量 descriptors = des_list[0][1] for image_path, descriptor in des_list[1:]: descriptors = np.vstack((descriptors, descriptor)) # 100 聚类 K-Means k = 100 voc, variance = kmeans(descriptors, k, 1) # 生成特征直方图 im_features = np.zeros((len(image_paths), k), "float32") for i in range(len(image_paths)): words, distance = vq(des_list[i][1], voc) for w in words: im_features[i][w] += 1 # 实现动词词频与出现频率统计 nbr_occurences = np.sum((im_features > 0) * 1, axis=0) idf = np.array(np.log((1.0 * len(image_paths) + 1) / (1.0 * nbr_occurences + 1)), 'float32') # 尺度化 stdSlr = StandardScaler().fit(im_features) im_features = stdSlr.transform(im_features) # Train the Linear SVM clf = LinearSVC() clf.fit(im_features, np.array(image_classes)) # Save the SVM print("training and save model...") joblib.dump((clf, training_names, stdSlr, k, voc), "startup.pkl", compress=3)
预测验证
加载预先训练好的模型,使用模型在测试集上进行数据预测,测试结果表明,对于启动阶段的图像分类可以获得比较好的效果。
下面是预测方法的代码实现:
import cv2 as cvimport numpy as npfrom imutils import pathsfrom scipy.cluster.vq import *from sklearn.externals import joblib def predict_image(image_path, pkl): # Load the classifier, class names, scaler, number of clusters and vocabulary clf, classes_names, stdSlr, k, voc = joblib.load("eleme.pkl") # Create feature extraction and keypoint detector objects sift = cv.xfeatures2d.SIFT_create() # List where all the descriptors are stored des_list = [] im = cv.imread(image_path, cv.IMREAD_GRAYSCALE) im = cv.resize(im, (300, 300)) kpts = sift.detect(im) kpts, des = sift.compute(im, kpts) des_list.append((image_path, des)) descriptors = des_list[0][1] for image_path, descriptor in des_list[0:]: descriptors = np.vstack((descriptors, descriptor)) test_features = np.zeros((1, k), "float32") words, distance = vq(des_list[0][1], voc) for w in words: test_features[0][w] += 1 # Perform Tf-Idf vectorization nbr_occurences = np.sum((test_features > 0) * 1, axis=0) idf = np.array(np.log((1.0 + 1) / (1.0 * nbr_occurences + 1)), 'float32') # Scale the features test_features = stdSlr.transform(test_features) # Perform the predictions predictions = [classes_names[i] for i in clf.predict(test_features)] return predictions
阶段三
采集新的启动视频
和阶段一采用的方式一样。
用模型进行预测
和阶段二预测验证的做法一样。
计算启动时间
根据预测结果,确定点击应用icon阶段的图片和首页渲染稳定之后的图片,获取两个图片直接的帧数差值,如果前面以60帧抽取图片,那么总耗时 = 帧数差值 * 1/60,具体计算这部分的代码实现如下:
from airtest.core.api import *from dingtalkchatbot.chatbot import DingtalkChatbotfrom poco.drivers.android.uiautomation import AndroidUiautomationPoco webhook = 'https://oapi.dingtalk.com/robot/send?access_token='robot = DingtalkChatbot(webhook) def calculate(package_name, apk_path, pkl, device_name, app_name, app_version): sample = 'sample/screen.mp4' test_path = "dataset/test/" if not os.path.isdir('sample/'): os.makedirs('sample/') if not os.path.isdir(test_path): os.makedirs(test_path) try: os.system("adb uninstall {}".format(package_name)) os.system("adb install -r {}".format(apk_path)) poco = AndroidUiautomationPoco() poco.device.wake() time.sleep(2) poco(text='应用名').click() poco(text='下一步').click() poco(text='允许').click() poco(text='允许').click() poco(text='允许').click() os.system("adb shell am force-stop {}".format(package_name)) subprocess.Popen("adb shell screenrecord --time-limit 20 /sdcard/sample.mp4", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) poco(text="应用名").click() time.sleep(20) os.system("adb pull /sdcard/sample.mp4 {}".format(sample)) os.system("adb uninstall {}".format(package_name)) os.system("ffmpeg -i {} -r 60 {}%d.jpeg".format(sample, test_path)) image_paths = [] class_path = list(paths.list_images(test_path)) image_paths += class_path start = [] stable = [] for image_path in image_paths: predictions = predict_image(image_path, pkl) if predictions[0] == '1_start': start += [str(image_path.split('/')[2]).split('.')[0]] elif predictions[0] == '4_stable': stable += [str(image_path.split('/')[2]).split('.')[0]] start_time = int(sorted(start)[0]) stable_time = int(sorted(stable)[0]) print("耗时:%.2f 秒" % ((stable_time - start_time) / 60)) robot.send_text( msg="启动耗时自动化测试结果:\n被测设备:{}\n被测应用:{}\n被测版本:{}\n".format(device_name, app_name, app_version) + "启动耗时:%.2f 秒" % ( (stable_time - start_time) / 60), is_at_all=True) except: shutil.rmtree(test_path) if os.path.exists(sample): os.remove(sample) if __name__ == "__main__": calculate("package_name", "app/app-release.apk", "startup.pkl", "小米MIX3", "应用名", "10.1.1")
持续集成
根据上面测试方法提供的参数,通过Jenkins配置任务,训练好模型,将以上三个阶段通过Python脚本的形式封装好,另外再配置好WebHook跟打包平台关联好,即可实现自动验证分析计算最新包的首屏加载耗时。
效果
通过人工录屏,然后用QuickTime分帧查看时间轴,计算出的首屏加载耗时跟这套方案得到的结果误差基本在100毫秒以内,但这个过程一次取数需要15分钟左右,而现在这套方案一次取数只需要3分钟左右,效率明显提升,还避免了不同人操作采集标准不一致的问题。