focus_model.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. # -*- coding:utf-8 -*-
  2. import os, sys, time
  3. import numpy as np
  4. from ssat_sdk.tv_operator import *
  5. from ssat_sdk.video_capture import *
  6. from ssat_sdk.picture.feature_detect import *
  7. Area_Empty = [-1,-1,-1,-1]
  8. OneStepPolicy={
  9. "left":"L",
  10. "right":"R",
  11. "up":"U",
  12. "down":"D"
  13. }
  14. class FocusModel():
  15. def __init__(self):
  16. self.grid = None
  17. self.featureDetect = FeatureDetect()
  18. self.operator = TvOperator()
  19. self.vcap = VideoCapture()
  20. '''
  21. 设定当前界面的聚焦框图标
  22. :param focus_icon_file:选择框图标文件路径
  23. :param focusDict: 可能出现icon的区域。
  24. 例如:
  25. {
  26. 'back': [152, 780, 432, 860],
  27. 'forgot': [120, 946, 486, 1010],
  28. 'gmain': [152, 618, 386, 672]
  29. }
  30. '''
  31. def setUiFeature(self, focus_icon_file, focusDict):
  32. self.focus_icon_file = focus_icon_file
  33. self.focus_icon = cv.imread(focus_icon_file)
  34. self.focusDict = focusDict
  35. self.genFocusGridMode(self.focusDict)
  36. '''
  37. #构建操作步骤模型(网格),用于两个焦点之间的路径确认。
  38. #计算方式:已左上角点坐标为准,并假设每个焦点框相离。
  39. :param focusDict : {u'tv': [43, 128, 380, 265], u'media': [43, 648, 380, 785]}
  40. '''
  41. def genFocusGridMode(self, focusDict):
  42. print "genFocusGridMode:",focusDict
  43. #整理出所有列,x轴
  44. x_dict = {}
  45. for item in focusDict.iteritems():
  46. #item格式:['tv',[43, 128, 380, 265]]
  47. self.addDictList(x_dict, item[1][0], item[1][1])
  48. x_list = sorted(x_dict.keys())
  49. # print x_list
  50. #整理出所有行
  51. y_dict = {}
  52. for item in focusDict.iteritems():
  53. self.addDictList(y_dict, item[1][1], item[1][0])
  54. y_list = sorted(y_dict.keys())
  55. # print y_list
  56. #整理出网格,即二维数组,初始化值为None,然后把所有焦点依次填充到二维数组中
  57. self.grid = [[Area_Empty]*x_list.__len__() for i in range(y_list.__len__())]
  58. # print self.grid
  59. for x in range (x_list.__len__()):
  60. for y in range(y_list.__len__()):
  61. # print "x,y", x,y
  62. isExist, area = self.findFocus(focusDict,x_list[x], y_list[y])
  63. if isExist:
  64. self.grid[y][x] = area
  65. else:
  66. self.grid[y][x] = Area_Empty
  67. print "genFocusGridMode.Grid:"
  68. for row in self.grid:
  69. print row
  70. def findFocus(self,focusDict, pointX,pointY):
  71. for focus in focusDict:
  72. if focusDict[focus][0] == pointX and focusDict[focus][1] == pointY:
  73. return True, focusDict[focus]
  74. return False,Area_Empty
  75. '''
  76. 根据icon,寻找在图片中icon的位置
  77. :param icon:选择框图标文件路径
  78. :param screen:整张图片文件路径
  79. :param focusDict: 可能出现icon的区域。
  80. 例如:
  81. {
  82. 'back': [152, 780, 432, 860],
  83. 'forgot': [120, 946, 486, 1010],
  84. 'gmain': [152, 618, 386, 672]
  85. }
  86. :param border_wide:四周选择框的厚度
  87. :return 聚焦区域
  88. '''
  89. def locateFocusArea(self, icon, screen, focusDict, border_wide):
  90. icon_img = cv.imread(icon)
  91. return self.locateFocusArea(icon_img, screen, focusDict, border_wide)
  92. def locateImgFocusArea(self, icon, screen, focusDict, border_wide):
  93. return self.featureDetect.locateImgFocusArea(icon, screen, focusDict, border_wide)
  94. '''
  95. 在构建好网格数据模型后,从A移动焦点到B,每移动一步,进行下一步的判断
  96. :param areaT 目标区域坐标
  97. :param keyDict 例如:{'U':"up","D":"down","L":"left","R":"right"},up/down/left/right是实际操控电视的指令
  98. :param wide 聚焦框的宽度
  99. :return result:bool类型,True代表成功,False代表失败
  100. '''
  101. def goToArea(self,nameT, areaT, keyDict, wide):
  102. print "goTo Area:",nameT, areaT
  103. if not self.isSameArea(self.focusDict[nameT], areaT):
  104. print u"请检查函数setUiFeature的UI参数和目标是否匹配!"
  105. pic_tmp = os.path.join(getSATTmpDIR(), "focus_goToArea.png")
  106. count = 0
  107. Max_Try = 100
  108. while (True):
  109. if count > Max_Try:
  110. break
  111. count += 1
  112. #1 截取当前电视画面
  113. self.vcap.takePicture(pic_tmp)
  114. #2 获取当前焦点位置,判断是否到达目标区域
  115. name, area = self.locateImgFocusArea(self.focus_icon, pic_tmp, self.focusDict, wide)
  116. print "goToArea name/location/target:", name, area, areaT
  117. if self.isSameArea(area,areaT):
  118. print u"选中目标",name, area
  119. return True
  120. #3 计算方位
  121. direction = self.getTargetDirection(area, areaT)
  122. #4 根据计算的方位,进行移位操作
  123. self.goOneStep(direction, keyDict)
  124. return False
  125. def goOneStep(self, direction, keyDict):
  126. print "goOneStep,direction:", direction
  127. directionArr = direction.split("-")
  128. for direct in directionArr:
  129. key = OneStepPolicy[direct]
  130. print "key:",key
  131. self.operator.sendKey(keyDict[key])
  132. '''
  133. 计算目标区域在当前焦点区域的方位.
  134. | |
  135. 左上角 | 上 | 右上角
  136. | |
  137. -----------------------------------
  138. | |
  139. | |
  140. 左 | 焦点区域 | 右
  141. | |
  142. -----------------------------------
  143. | |
  144. 左下角 | 下 | 右下角
  145. | |
  146. 区域的方位,按上图,分成4面位方向和4角位方向。
  147. 4面位方向:上、下、左、右。只要目标区域,和4面位方向区域有交集,就属于面位方向。
  148. 4角位方向:左上角、右上角、左下角、右下角。目标区域全部在4角位方向区域,才是角位方向。
  149. 方位定义:up、down、left、right、left-up、left-down、right-up、right-down
  150. :param areaL:当前焦点所在区域
  151. :param areaT:目标区域
  152. :return direction。字符串类型,方位:up、down、left、right、left-up、left-down、right-up、right-down
  153. '''
  154. ImgRectLimit = 10000
  155. def getTargetDirection(self, areaL, areaT):
  156. print "getTargetDirection:",areaL,areaT
  157. #确定4个面位方位的区域的x,y轴范围
  158. up = [areaL[0],0,areaL[2],areaL[1]]
  159. down = [areaL[0],areaL[3],areaL[2], FocusModel.ImgRectLimit]
  160. left = [0,areaL[1],areaL[0], areaL[3]]
  161. right = [areaL[2],areaL[1],FocusModel.ImgRectLimit, areaL[3]]
  162. #检测目标区域是否和4个面位方向区域有重叠部分
  163. up_direct = self.hasSameArea(up, areaT)
  164. if up_direct:
  165. return "up"
  166. down_direct = self.hasSameArea(down, areaT)
  167. if down_direct:
  168. return "down"
  169. left_direct = self.hasSameArea(left, areaT)
  170. if left_direct:
  171. return "left"
  172. right_direct = self.hasSameArea(right, areaT)
  173. if right_direct:
  174. return "right"
  175. #计算目标区域坐上角点的方位,x,y差值确定角位方向
  176. dL_Ux = areaT[0] - areaL[0]
  177. dL_Uy = areaT[1] - areaL[1]
  178. if (dL_Ux > 0) and (dL_Uy > 0):
  179. return "right-down"
  180. if (dL_Ux < 0) and (dL_Uy > 0):
  181. return "left-down"
  182. if (dL_Ux > 0) and (dL_Uy < 0):
  183. return "right-up"
  184. if (dL_Ux < 0) and (dL_Uy < 0):
  185. return "left-up"
  186. '''
  187. 检测两个区域是否有重叠部分
  188. '''
  189. def hasSameArea(self, area1, area2):
  190. #计算各区域宽、高
  191. area1_W = area1[2] - area1[0]
  192. area1_H = area1[3] - area1[1]
  193. area2_W = area2[2] - area2[0]
  194. area2_H = area2[3] - area2[1]
  195. # 计算目标区域坐上角点的方位
  196. dL_Ux = area1[0] - area2[0]
  197. dL_Uy = area1[1] - area2[1]
  198. # 计算x坐标,判断是否有重叠可能。
  199. x_enable = False
  200. if dL_Ux > 0:
  201. if (abs(dL_Ux) - area2_W) >= 0:
  202. x_enable = False
  203. else:
  204. x_enable = True
  205. else:
  206. if (abs(dL_Ux) - area1_W) >= 0:
  207. x_enable = False
  208. else:
  209. x_enable = True
  210. #计算y坐标,判断是否有重叠可能。
  211. y_enable = False
  212. if dL_Uy > 0:
  213. if (abs(dL_Uy) - area2_H) > 0:
  214. y_enable = False
  215. else:
  216. y_enable = True
  217. else:
  218. if (abs(dL_Uy) - area1_H) > 0:
  219. y_enable = False
  220. else:
  221. y_enable = True
  222. #如果x坐标、y坐标都有可能重叠,则一定重叠。反之,没有重叠。
  223. return x_enable and y_enable
  224. def indexByArea(self, target_area):
  225. for row in range(self.grid.__len__()):
  226. for col in range(self.grid[row].__len__()):
  227. area = self.grid[row][col]
  228. if self.isSameArea(area, target_area):
  229. return row,col
  230. return -1,-1
  231. def isSameArea(self, areaA, areaB):
  232. if areaA[0] == areaB[0] \
  233. and areaA[1] == areaB[1] \
  234. and areaA[2] == areaB[2] \
  235. and areaA[3] == areaB[3] :
  236. return True
  237. else:
  238. return False
  239. def addDictList(self, fdict, key, value):
  240. # print fdict, key, value
  241. pt = (key)
  242. if fdict.has_key(key):
  243. for index in range(fdict[key].__len__()):
  244. # print index, fdict[key][index] , value
  245. if (fdict[key][index] > value):
  246. fdict[key].insert(index, value)
  247. break
  248. if (index == (fdict[key].__len__()-1)):
  249. fdict[key].append(value)
  250. else:
  251. list = []
  252. list.append(value)
  253. fdict[key] = list
  254. '''
  255. 在构建好网格数据模型后,用于确定区域A到区域B的路径
  256. :param areaA 起始区域坐标
  257. :param areaB 目标区域坐标
  258. :return path:数组类型,例如:['L','R'..] L:left,R:right,U:up,D:down
  259. '''
  260. def getPathA2B(self, areaA,areaB):
  261. print "getPathA2B:", areaA,areaB
  262. aIndex = self.indexByArea(areaA)
  263. bIndex = self.indexByArea(areaB)
  264. print "area A , B:", aIndex,bIndex
  265. path = []
  266. #x 轴,水平左右方向移动值. L 或 R
  267. for i in range(abs(bIndex[1] - aIndex[1])):
  268. if (bIndex[1] - aIndex[1]) > 0:
  269. path.append('R')
  270. else:
  271. path.append('L')
  272. # y 轴,纵向上下方向移动值. U 或 D
  273. for i in range(abs(bIndex[0] - aIndex[0])):
  274. if (bIndex[0] - aIndex[0]) > 0:
  275. path.append('D')
  276. else:
  277. path.append('U')
  278. return path
  279. '''
  280. 在构建好网格数据模型后,用于确定区域A到区域B的路径
  281. :param path getPathA2B函数返回的路径。例如:L-L-D-U-R
  282. :param keyDict 例如:{'U':"up","D":"down","L":"left","R":"right"},up/down/left/right是实际操控电视的指令。
  283. :return path:数组类型,例如:['L','R'..] L:left,R:right,U:up,D:down
  284. '''
  285. def executePath(self, path, keyDict):
  286. print "FocusModel,executePath:",path
  287. for step in path:
  288. self.operator.sendKey(keyDict[step])
  289. '''
  290. 在构建好网格数据模型后,用于确定区域A到区域B的路径,然后按路径一次性移动焦点。
  291. :param areaL 起始区域坐标
  292. :param name 目标区域的名字
  293. :param keyDict 例如:{'U':"up","D":"down","L":"left","R":"right"},up/down/left/right是实际操控电视的指令。
  294. '''
  295. def gotoAreaByPath(self, areaL, name, keyDict):
  296. # key是否存在字典中;
  297. isKeyExist = name.lower() in self.focusDict.keys()
  298. if isKeyExist == True:
  299. path = self.getPathA2B(areaL, self.focusDict[name.lower()])
  300. self.executePath(path, keyDict)
  301. # 如何键值存在返回true
  302. return isKeyExist
  303. '''
  304. 根据icon,寻找在图片中icon的位置,知道icon所在位置的文本内容为参数textT值
  305. :param textT: 选中区域的文本内容
  306. :param direction: 方位,遥控器的上下左右操作,值范围:U、D、L、R,必须是keyDict中键。符合executePath函数返回值规范
  307. :param keyDict 例如:{'U':"up","D":"down","L":"left","R":"right"},up/down/left/right是实际操控电视的指令。
  308. :param maxMove 在指定方位移动的最大次数
  309. :param icon:图标文件路径
  310. :param uiDict: 可能出现icon的区域。
  311. 例如:
  312. #name的字符串,需为text_area区域显示的文字
  313. {
  314. focus_tv : '{"name":"tv","text_area":[179,210,242,248,"english", 253],"focus_area":[43,128,380,265]}',
  315. focus_av : '{"name":"av","text_area":[180,339,242,381,"english", 253],"focus_area":[43,257,380,395]}',
  316. focus_hdmi1 : '{"name":"hdmi1","text_area":[156,466,269,510,"english", 2],"focus_area":[43,386,380,525]}',
  317. focus_hdmi2 : '{"name":"hdmi2","text_area":[159,599,265,641,"english", 2],"focus_area":[43,517,380,655]}',
  318. focus_usb : '{"name":"media","text_area":[160,730,260,771,"english", 2],"focus_area":[43,648,380,785]}'
  319. }
  320. :param border_wide:四周选择框的厚度
  321. :return result:True代表成功,False代表失败
  322. '''
  323. def gotoAreaByDirection(self, textT,direction, keyDict, maxMove, uiDict, border_wide):
  324. pic_tmp = os.path.join(getSATTmpDIR(), "focus_gotoAreaByDirection.png")
  325. count = 0
  326. while True:
  327. if count > maxMove:
  328. break
  329. count += 1
  330. self.vcap.takePicture(pic_tmp)
  331. textL, text_area, focus_area = self.featureDetect.getFocusArea(self.focus_icon_file, pic_tmp, uiDict, border_wide)
  332. if textT.lower() == textL.lower():
  333. return True
  334. self.operator.sendKey(keyDict[direction])
  335. return False
  336. if __name__ == "__main__":
  337. focusModel = FocusModel()
  338. # focusDict = {u'tv': [43, 128, 380, 265], u'media': [43, 648, 380, 785], u'hdmi1': [43, 386, 380, 525], u'hdmi2': [43, 517, 380, 655], u'av': [43, 257, 380, 395]}
  339. focusDict = {u'tv': [43, 128, 380, 265], u'media': [43, 648, 380, 785],
  340. u'hdmi1': [43, 386, 380, 525], u'hdmi2': [43, 517, 380, 655],
  341. u'av': [43, 257, 380, 395], u'test':[400,500,600,800]}
  342. focusModel.genFocusGridMode(focusDict)