凡哥OpenCV基础入门教程(跳一跳专题)-CH3.4-实战篇 手动模式玩跳一跳
2018-03-28 12:56
751 查看
原创文章,转载请注明出处!!
本文为【凡哥原创教程】VIP教程节选,欢迎加入技术交流群了解会员制度(QQ:627671914)
凡哥给大家做好了手动模式版本玩跳一跳的源代码与使用教程, 好好研读代码哦.
根据step1中选择的文件夹修改对应的值.
? 如何才能知道自己屏幕的分辨率
我在
你可以利用ADB命令行读入一张图片, 然后获取其属性.
启动adb server
手机端选择USB模式 这里我们要选择传输照片模式, 我这里是PTP, 你的手机可能是MTP
默认是USB供电
查看一下设备列表
说明正确连接手机.
详细步骤见CH3.1的使用说明.
进入代码主目录,执行
如果有些帧不是你想要的, 或者游戏过程中的中间画面, 按
接下来我们再标注盒子中心.
你可以选择撤销标注, 更新画面, 按
确定标注, 保存样本图片并跳跃按
中间你可能会遇到这种半截的图片.
按
更新后, 就会显示成这样, 往复执行.
人有多大胆, 地有多大产.
基本上我玩到300多就已经手抖了. 大家还是不要沉迷,以采集样本为根本目的, 见好就收.
关键代码如下
日志结构, 第一个元素是图片名称(采用时间戳生成), 接下来是棋子底部中心的x坐标与y坐标
接下来是跳一跳盒子的中心x坐标与y坐标.
2.1
2.2
2.3
你有什么办法可以读取文件夹中的图片,并且生成我们
这跟
这跟CH3.1的作业, 其实很像.
你可以在凡哥的代码
本文为【凡哥原创教程】VIP教程节选,欢迎加入技术交流群了解会员制度(QQ:627671914)
0. 概要
前阶段CH1一直到CH3, 我们学会了图像的基本操作,学会了HighGUI键盘事件与鼠标事件的监听, 然后学了绘图相关的操作. CH3前面的部分都是在介绍如何使用ADB以及用subprocess模块调用ADB对手机屏幕截图与按压屏幕模拟. 是时候实战一波了.凡哥给大家做好了手动模式版本玩跳一跳的源代码与使用教程, 好好研读代码哦.
1. 代码使用说明
1.1 创建一个文件夹用于存放样本
你可以在代码根目录创建一个文件夹名字叫做label或者起另外的名字.
1.2 修改代码中的样本存放路径
修改文件markSampleLabelFromPhone.py
根据step1中选择的文件夹修改对应的值.
# 图片保存路径 使用前先创建此文件夹/更新此参数 save_path = "./label/" # 样本标注信息文件名 使用前先创建此文件/更新此参数 label_filename = "./label/labels.txt" slabel = SampleLabel(save_path, label_filename)
1.3 设定手机屏幕分辨率
修改文件markSampleLabelFromPhone.py
# 初始化 ADBHelper, 填入手机屏幕宽度跟高度. (宽度,高度) adb = ADBHelper(1080, 1920)
? 如何才能知道自己屏幕的分辨率
我在
CH1.1中就讲解了如何获取图像属性. CH1.1_读入图片并显示图片的相关属性
你可以利用ADB命令行读入一张图片, 然后获取其属性.
1.4 修改距离转变为时间的ratio
你可以根据你的手机分辨率与实际跳跃效果调大或者调小ratio.
# 将距离转换成时间 def distance2time(distance): ratio = 1.53 # 时间必须是整数类型 return int(distance * ratio)
1.5 打开ADB 服务器
连接手机.启动adb server
scorpion@tl ~/D/f/CH3_图像采集与样本标注> sudo adb start-server [sudo] password for scorpion: * daemon not running; starting now at tcp:5037 * daemon started successfully
手机端选择USB模式 这里我们要选择传输照片模式, 我这里是PTP, 你的手机可能是MTP
默认是USB供电
查看一下设备列表
scorpion@tl ~/D/f/CH3_图像采集与样本标注> adb devices List of devices attached a9e0d12a device
说明正确连接手机.
详细步骤见CH3.1的使用说明.
1.6 运行主程序
手机端打开跳一跳的小程序.进入代码主目录,执行
python markSampleLabelFromPhone.py
如果有些帧不是你想要的, 或者游戏过程中的中间画面, 按
n键, 更新画面
1.7 查看帮助工具
点击窗口,按h键,就可以看到帮助信息.标注工具-帮助菜单 ================================== 键盘 n - next 下一张图片 需要手动更新!!! 键盘 c - cancel 撤销标注 键盘 s - save 保存样本标注与跳跃 键盘 h - help 帮助菜单 键盘 e - exit 保存标记并退出系统
1.8 标注两点然后跳跃
首先标注棋子底部,用红色点表示.接下来我们再标注盒子中心.
你可以选择撤销标注, 更新画面, 按
c键.
确定标注, 保存样本图片并跳跃按
s键.
中间你可能会遇到这种半截的图片.
按
nnext ,切换到下一张图片. 哈哈, 我知道你有更好的办法, 去改代码吧.
更新后, 就会显示成这样, 往复执行.
人有多大胆, 地有多大产.
基本上我玩到300多就已经手抖了. 大家还是不要沉迷,以采集样本为根本目的, 见好就收.
1.9 查看标注样本
我们可以看到文件夹中采集的图片, 然后还有对应的日志.关键代码如下
def label2string(self): ''' 将标签转换为字符串, 用于保存在label.txt中 ''' (x1, y1) = self.fchess (x2, y2) = self.cbox return ",".join([self.img_name, str(x1), str(y1), str(x2), str(y2)]) + '\n'
日志结构, 第一个元素是图片名称(采用时间戳生成), 接下来是棋子底部中心的x坐标与y坐标
接下来是跳一跳盒子的中心x坐标与y坐标.
2. 源代码
2.1 SampleLabel.py
'''
程序说明:
程序类用于通过鼠标手动标注两个关键点坐标
1. 棋子中心
2. 下一跳盒子的中心
'''
import cv2
import numpy as np
import datetime
import math
import copy
from ADBHelper import ADBHelper
# MP : Mark Process 标注过程
MP_UNMARK = 0 # 0 : 未进行标注
MP_MARKED_FCHESS = 1 # 1 : 标注了小人的底部
MP_MARKED_CBOX = 2 # 2 : 标注了box的中心点
'''
手动标注 两个标签
'''
def markChessAndBoxByHand(event,x,y,flags,sampleLabel):
if event == cv2.EVENT_LBUTTONDOWN:
print("click: x= {}, y={}".format(x, y))
sampleLabel.addLabel(x, y)
class SampleLabel:
def __init__(self, save_path='./', label_filename='label.txt'):
self.img = None # 原来的图片
self.canvas = None # 画布
self.img_name = None # 图片名称
self.mp = 0 # 标注的进程 mark process
self.fchess = (0, 0) # 棋子底部中心
self.cbox = (0, 0) # 下一条盒子的中心
self.save_path = save_path # 图像的保存路径
self.label_filename = label_filename # 标签记录文件的文件名
# self.label_file = open(label_filename, 'w+') # 文件对象
self.winname = 'label'
# 创建一个窗口
cv2.namedWindow(self.winname, flags= cv2.WINDOW_NORMAL | cv2.WINDOW_FREERATIO)
# 设置鼠标事件的回调函数
cv2.setMouseCallback(self.winname, markChessAndBoxByHand, self)
def updateImg(self, img, img_name = None):
# 更新当前源图像 - 深度拷贝
self.img = img.copy()
# 更新画布 - 深度拷贝
self.canvas = img.copy()
# 重置 棋子底部坐标
self.fchess = (0, 0)
# 重置盒子的中心
self.cbox = (0, 0)
if img_name == None:
# 根据时间戳 生成文件名
self.img_name = f"{datetime.datetime.now():%Y-%m-%d-%H-%M-%S-%f.png}"
else:
# 如果有名字的话就直接赋值
self.img_name = img_name
# 重置标注状态
self.mp = MP_UNMARK
# 更新画布
self.updateCanvas()
def printProcessOnCanvas(self, info):
'''
在画布上显示帮助信息
'''
# 首先更新画布
# self.updateCanvas()
self.canvas[50:150,:] = 255
# 选择字体
font = cv2.FONT_HERSHEY_SIMPLEX
fontScale = 1
cv2.putText(self.canvas, text=info, org=(100, 100), fontFace=font, fontScale=fontScale, thickness=1,
lineType=cv2.LINE_AA, color=(0, 0, 255))
cv2.imshow(self.winname, self.canvas)
def updateCanvas(self):
'''
根据状态更新画布 与文字提示
'''
# Use Deep Copy
self.canvas = self.img.copy()
rmarker = 10 # 标记半径
if self.mp >= MP_MARKED_FCHESS:
# 绘制chess中心
# 绘制棋子底部的中心点 红色实心
cv2.circle(img=self.canvas, center=self.fchess, radius=rmarker, color=(0, 0, 255), thickness=-1)
if self.mp >= MP_MARKED_CBOX:
# 绘制下一条盒子中心
cv2.circle(img=self.canvas, center=self.cbox, radius=rmarker, color=(0, 255, 0), thickness=-1)
if self.mp == MP_UNMARK:
self.printProcessOnCanvas("step-0 unmarked, mark chess footer first.")
elif self.mp == MP_MARKED_FCHESS:
self.printProcessOnCanvas("step-1 you need to mark next box center.")
elif self.mp == MP_MARKED_CBOX:
self.printProcessOnCanvas("step-2 mark done, save (s) or cancel (u)")
cv2.imshow(self.winname, self.canvas)
def addLabel(self, x, y):
'''
添加标签
'''
if self.mp == MP_UNMARK:
# 当前标注的是棋子脚底
self.fchess = (x, y)
# 更新状态码
self.mp = MP_MARKED_FCHESS
elif self.mp == MP_MARKED_FCHESS:
# 当前标注的是盒子的中心
self.cbox = (x, y)
# 更新状态码
self.mp = MP_MARKED_CBOX
else:
print("标注已完成")
'''
# 打印标注信息
print("fchess")
print(self.fchess)
print("cbox")
print(self.cbox)
print("mp")
print(self.mp)
'''
self.updateCanvas()
def isMarkDone(self):
'''
返回是否标注完成
'''
return self.mp == MP_MARKED_CBOX
def saveImg(self):
'''
保存图片
'''
# 保存样本素材
cv2.imwrite(self.save_path + self.img_name, self.img)
# 保存标注后的图片
cv2.imwrite(self.save_path + 'log/' + self.img_name, self.canvas)
def label2string(self): ''' 将标签转换为字符串, 用于保存在label.txt中 ''' (x1, y1) = self.fchess (x2, y2) = self.cbox return ",".join([self.img_name, str(x1), str(y1), str(x2), str(y2)]) + '\n'
def saveLabelInfo(self):
'''
添加标注信息 追加模式
'''
with open(self.label_filename, 'a+') as f:
f.write(self.label2string())
def onDestroy(self):
# 关闭窗口
cv2.destroyWindow(self.winname)
2.2 ADBHelper.py
''' 在Python中执行ADB的命令行, 实现对Android设备的操控 ''' from subprocess import Popen, PIPE import shlex import cv2 import random import subprocess class ADBHelper: def __init__(self, win_width, win_height, margin = 100): ''' 构造器函数 确认 窗口宽度高度与可按压范围. ''' self.win_width = win_width self.win_height = win_height # 可以按压的矩形区域 内缩 margin个像素点。 # 因为屏幕上的边缘 点击会触发别的事件 # region = (x, y, width, height) self.press_region = (margin, margin, win_width-margin, win_height-margin) @staticmethod def getScreenShotByADB(): ''' 在手机上截图并返回opencv格式的ndarray ''' # ADB截图的命令 cmd = "adb shell screencap -p" # 运行指令 p = Popen(shlex.split(cmd), stdin=PIPE, stdout=PIPE, stderr=PIPE) # output与err返回的均是字节 output, err_info = p.communicate() rc = p.returncode ''' Return Code: 0: 读取成功 1: 读取失败 ''' if rc == 1: # 截图读取失败 print("截图读取失败") print("ERROR INFO") # 打印报错信息 print(err_info) return None # 先写入文件 tmp.png f = open("tmp.png", "wb") f.write(output) # 使用opencv读取图片 返回ndarray return cv2.imread("tmp.png") @staticmethod def pressOnScreen(ptr1, delay=500): ''' 在屏幕上按压 带延时 ''' return ADBHelper.pressOnScreenAndMove(ptr1, ptr1, delay) @staticmethod def pressOnScreenAndMove(ptr1, ptr2, delay=500): ''' 按压屏幕并且移动 ''' # 命令样例: 'adb shell input touchscreen swipe 170 187 170 187 2000' cmd = 'adb shell input touchscreen swipe {} {} {} {} {}'.format(ptr1[0], ptr1[1], ptr2[0], ptr2[1], delay) # 运行指令 rc = subprocess.call(shlex.split(cmd)) return rc == 0 def randPressOnScreen(self, delay=500): ''' 在屏幕上的可按压区域随意选取一个区域, 进行按压 ''' (x, y, w, h) = self.press_region cx = random.randint(x, x + w) cy = random.randint(y, y + h) print("Press On x=%d y=%d , time=%.2f"%(cx, cy, delay)) return ADBHelper.pressOnScreen((cx, cy), delay)
2.3 markSampleLabelFromPhone.py
'''
程序说明:
从ADB中读取手机截图,并通过鼠标标注点, 保存标注文件
'''
import cv2
import numpy
import math
import os
# 样本标注
from SampleLabel import SampleLabel
# 凡哥写的ADBHelper类
from ADBHelper import ADBHelper
# 图片保存路径 使用前先创建此文件夹/更新此参数
save_path = "./label/"
# 样本标注信息文件名 使用前先创建此文件/更新此参数
label_filename = "./label/labels.txt"
slabel = SampleLabel(save_path, label_filename)
# 初始化 ADBHelper, 填入手机屏幕宽度跟高度.
adb = ADBHelper(1080, 1920)
# 将距离转换成时间 def distance2time(distance): ratio = 1.53 # 时间必须是整数类型 return int(distance * ratio)
# 计算两点之间的距离
def cal_distance(pt1, pt2):
'''
获取棋子与下一跳盒子的距离
'''
(x1, y1) = pt1
(x2, y2) = pt2
return math.sqrt(math.pow((x2 - x1), 2) + math.pow((y2 - y1), 2))
# 更新下一张图片 从ADB读入
def nextImg(slabel):
'''
使用迭代器, 遍历数组
'''
global adb
try:
# 从ADB读入图片
img = adb.getScreenShotByADB()
# 确认图片是否成功读入
if img is None:
return False
else:
slabel.updateImg(img, img_name=None)
# 读入就将原来 unlabel的文件删除
return True
except StopIteration:
print("遍历结束")
return False
# 初始读入第一个
nextImg(slabel)
while True:
keyValue = cv2.waitKey(0)
# slabel.responseToKeyEvent(k, img=img)
if keyValue == ord('e'):
print('销毁窗口并保存')
# 关闭窗口
slabel.onDestroy()
break
elif keyValue == ord('n'):
print("跳过,下一张图片")
if not nextImg(slabel):
# 如果获取失败, 退出
break
elif keyValue == ord('c'):
print("取消标注")
# update frame
slabel.updateImg(slabel.img)
elif keyValue == ord('s'):
print("保存")
if slabel.isMarkDone():
# 保存样本
slabel.saveImg()
# 保存样本标注信息
slabel.saveLabelInfo()
# 显示提示信息 : 保存完成
slabel.printProcessOnCanvas("Save Done")
# 随机点击屏幕, 设置对应的延时时间.
delay_time = distance2time(cal_distance(slabel.cbox, slabel.fchess))
# 这里可以添加个延时
# TODO
adb.randPressOnScreen(delay_time)
# 显示提示信息 : 自动载入下一张图片
if not nextImg(slabel):
# 如果获取失败, 退出
break
else:
# 标注未完成, 无法保存
slabel.printProcessOnCanvas("Error: mark undone, could not save")
elif keyValue == ord('h'):
print('''
标注工具-帮助菜单 ================================== 键盘 n - next 下一张图片 需要手动更新!!! 键盘 c - cancel 撤销标注 键盘 s - save 保存样本标注与跳跃 键盘 h - help 帮助菜单 键盘 e - exit 保存标记并退出系统
''')
3. 作业CH3.4
如果给你提供一系列没有标注的图片, 例如你搜集的样本图片, 搜集的时候并没有标注你有什么办法可以读取文件夹中的图片,并且生成我们
label.txt的标注文本么?
这跟
markSampleLabelFromPhone.py类似, 只不过文件读取来源由通过ADB读取截图变成了从文件中读取截图.
这跟CH3.1的作业, 其实很像.
你可以在凡哥的代码
SampleLabel.py基础上调用并实现, 也可以从0开始写自己的实现代码.
相关文章推荐
- 凡哥OpenCV基础入门教程(跳一跳专题)-CH2.3-几何图像绘制与文字绘制
- 凡哥OpenCV基础入门教程(跳一跳专题)-CH1.1-读入图片并显示图片的相关属性
- 凡哥OpenCV基础入门教程(跳一跳专题)-CH1.2_通过Matplotlib展示图片
- 凡哥OpenCV基础入门教程(跳一跳专题)-CH1.3-通过HighGUI展示图片
- 凡哥OpenCV基础入门教程(跳一跳专题)-CH1.4-图片保存imwrite
- 凡哥OpenCV基础入门教程(跳一跳专题)-CH2.1-花式创建空白画布
- 凡哥OpenCV基础入门教程(跳一跳专题)-CH3.1-ADB安装过程与ADB部分指令介绍
- 凡哥OpenCV基础入门教程(跳一跳专题)-CH3.2-进程与数据流重定向
- 凡哥OpenCV基础入门教程-跳一跳专题
- 凡哥OpenCV基础入门教程(跳一跳专题)-CH3.3-subprocess模块的使用说明
- 凡哥带你玩转OpenCV小班精品课第一期_OpenCV基础入门+跳一跳小程序项目实战 课程介绍
- Visual Studio Debug实战教程之基础入门
- Ruby On Rails系列从入门到精通实战教程 Ruby基础教程下载
- Android基础入门教程——8.3.2 绘图类实战示例
- Ruby On Rails系列从入门到精通实战教程 Ruby基础教程下载
- 零基础Swift实战开发视频教程_从入门到精通
- Ionic3.x/Ionic4.x从基础入门到项目实战视频教程
- OpenCV2/3基础入门高清视频教程