# -*- coding:utf-8 -*- import os, sys, time import numpy as np from ssat_sdk.tv_operator import * from ssat_sdk.video_capture import * from ssat_sdk.picture.feature_detect import * Area_Empty = [-1,-1,-1,-1] OneStepPolicy={ "left":"L", "right":"R", "up":"U", "down":"D" } class FocusModel(): def __init__(self): self.grid = None self.featureDetect = FeatureDetect() self.operator = TvOperator() self.vcap = VideoCapture() ''' 设定当前界面的聚焦框图标 :param focus_icon_file:选择框图标文件路径 :param focusDict: 可能出现icon的区域。 例如: { 'back': [152, 780, 432, 860], 'forgot': [120, 946, 486, 1010], 'gmain': [152, 618, 386, 672] } ''' def setUiFeature(self, focus_icon_file, focusDict): self.focus_icon_file = focus_icon_file self.focus_icon = cv.imread(focus_icon_file) self.focusDict = focusDict self.genFocusGridMode(self.focusDict) ''' #构建操作步骤模型(网格),用于两个焦点之间的路径确认。 #计算方式:已左上角点坐标为准,并假设每个焦点框相离。 :param focusDict : {u'tv': [43, 128, 380, 265], u'media': [43, 648, 380, 785]} ''' def genFocusGridMode(self, focusDict): print "genFocusGridMode:",focusDict #整理出所有列,x轴 x_dict = {} for item in focusDict.iteritems(): #item格式:['tv',[43, 128, 380, 265]] self.addDictList(x_dict, item[1][0], item[1][1]) x_list = sorted(x_dict.keys()) # print x_list #整理出所有行 y_dict = {} for item in focusDict.iteritems(): self.addDictList(y_dict, item[1][1], item[1][0]) y_list = sorted(y_dict.keys()) # print y_list #整理出网格,即二维数组,初始化值为None,然后把所有焦点依次填充到二维数组中 self.grid = [[Area_Empty]*x_list.__len__() for i in range(y_list.__len__())] # print self.grid for x in range (x_list.__len__()): for y in range(y_list.__len__()): # print "x,y", x,y isExist, area = self.findFocus(focusDict,x_list[x], y_list[y]) if isExist: self.grid[y][x] = area else: self.grid[y][x] = Area_Empty print "genFocusGridMode.Grid:" for row in self.grid: print row def findFocus(self,focusDict, pointX,pointY): for focus in focusDict: if focusDict[focus][0] == pointX and focusDict[focus][1] == pointY: return True, focusDict[focus] return False,Area_Empty ''' 根据icon,寻找在图片中icon的位置 :param icon:选择框图标文件路径 :param screen:整张图片文件路径 :param focusDict: 可能出现icon的区域。 例如: { 'back': [152, 780, 432, 860], 'forgot': [120, 946, 486, 1010], 'gmain': [152, 618, 386, 672] } :param border_wide:四周选择框的厚度 :return 聚焦区域 ''' def locateFocusArea(self, icon, screen, focusDict, border_wide): icon_img = cv.imread(icon) return self.locateFocusArea(icon_img, screen, focusDict, border_wide) def locateImgFocusArea(self, icon, screen, focusDict, border_wide): return self.featureDetect.locateImgFocusArea(icon, screen, focusDict, border_wide) ''' 在构建好网格数据模型后,从A移动焦点到B,每移动一步,进行下一步的判断 :param areaT 目标区域坐标 :param keyDict 例如:{'U':"up","D":"down","L":"left","R":"right"},up/down/left/right是实际操控电视的指令 :param wide 聚焦框的宽度 :return result:bool类型,True代表成功,False代表失败 ''' def goToArea(self,nameT, areaT, keyDict, wide): print "goTo Area:",nameT, areaT if not self.isSameArea(self.focusDict[nameT], areaT): print u"请检查函数setUiFeature的UI参数和目标是否匹配!" pic_tmp = os.path.join(getSATTmpDIR(), "focus_goToArea.png") count = 0 Max_Try = 100 while (True): if count > Max_Try: break count += 1 #1 截取当前电视画面 self.vcap.takePicture(pic_tmp) #2 获取当前焦点位置,判断是否到达目标区域 name, area = self.locateImgFocusArea(self.focus_icon, pic_tmp, self.focusDict, wide) print "goToArea name/location/target:", name, area, areaT if self.isSameArea(area,areaT): print u"选中目标",name, area return True #3 计算方位 direction = self.getTargetDirection(area, areaT) #4 根据计算的方位,进行移位操作 self.goOneStep(direction, keyDict) return False def goOneStep(self, direction, keyDict): print "goOneStep,direction:", direction directionArr = direction.split("-") for direct in directionArr: key = OneStepPolicy[direct] print "key:",key self.operator.sendKey(keyDict[key]) ''' 计算目标区域在当前焦点区域的方位. | | 左上角 | 上 | 右上角 | | ----------------------------------- | | | | 左 | 焦点区域 | 右 | | ----------------------------------- | | 左下角 | 下 | 右下角 | | 区域的方位,按上图,分成4面位方向和4角位方向。 4面位方向:上、下、左、右。只要目标区域,和4面位方向区域有交集,就属于面位方向。 4角位方向:左上角、右上角、左下角、右下角。目标区域全部在4角位方向区域,才是角位方向。 方位定义:up、down、left、right、left-up、left-down、right-up、right-down :param areaL:当前焦点所在区域 :param areaT:目标区域 :return direction。字符串类型,方位:up、down、left、right、left-up、left-down、right-up、right-down ''' ImgRectLimit = 10000 def getTargetDirection(self, areaL, areaT): print "getTargetDirection:",areaL,areaT #确定4个面位方位的区域的x,y轴范围 up = [areaL[0],0,areaL[2],areaL[1]] down = [areaL[0],areaL[3],areaL[2], FocusModel.ImgRectLimit] left = [0,areaL[1],areaL[0], areaL[3]] right = [areaL[2],areaL[1],FocusModel.ImgRectLimit, areaL[3]] #检测目标区域是否和4个面位方向区域有重叠部分 up_direct = self.hasSameArea(up, areaT) if up_direct: return "up" down_direct = self.hasSameArea(down, areaT) if down_direct: return "down" left_direct = self.hasSameArea(left, areaT) if left_direct: return "left" right_direct = self.hasSameArea(right, areaT) if right_direct: return "right" #计算目标区域坐上角点的方位,x,y差值确定角位方向 dL_Ux = areaT[0] - areaL[0] dL_Uy = areaT[1] - areaL[1] if (dL_Ux > 0) and (dL_Uy > 0): return "right-down" if (dL_Ux < 0) and (dL_Uy > 0): return "left-down" if (dL_Ux > 0) and (dL_Uy < 0): return "right-up" if (dL_Ux < 0) and (dL_Uy < 0): return "left-up" ''' 检测两个区域是否有重叠部分 ''' def hasSameArea(self, area1, area2): #计算各区域宽、高 area1_W = area1[2] - area1[0] area1_H = area1[3] - area1[1] area2_W = area2[2] - area2[0] area2_H = area2[3] - area2[1] # 计算目标区域坐上角点的方位 dL_Ux = area1[0] - area2[0] dL_Uy = area1[1] - area2[1] # 计算x坐标,判断是否有重叠可能。 x_enable = False if dL_Ux > 0: if (abs(dL_Ux) - area2_W) >= 0: x_enable = False else: x_enable = True else: if (abs(dL_Ux) - area1_W) >= 0: x_enable = False else: x_enable = True #计算y坐标,判断是否有重叠可能。 y_enable = False if dL_Uy > 0: if (abs(dL_Uy) - area2_H) > 0: y_enable = False else: y_enable = True else: if (abs(dL_Uy) - area1_H) > 0: y_enable = False else: y_enable = True #如果x坐标、y坐标都有可能重叠,则一定重叠。反之,没有重叠。 return x_enable and y_enable def indexByArea(self, target_area): for row in range(self.grid.__len__()): for col in range(self.grid[row].__len__()): area = self.grid[row][col] if self.isSameArea(area, target_area): return row,col return -1,-1 def isSameArea(self, areaA, areaB): if areaA[0] == areaB[0] \ and areaA[1] == areaB[1] \ and areaA[2] == areaB[2] \ and areaA[3] == areaB[3] : return True else: return False def addDictList(self, fdict, key, value): # print fdict, key, value pt = (key) if fdict.has_key(key): for index in range(fdict[key].__len__()): # print index, fdict[key][index] , value if (fdict[key][index] > value): fdict[key].insert(index, value) break if (index == (fdict[key].__len__()-1)): fdict[key].append(value) else: list = [] list.append(value) fdict[key] = list ''' 在构建好网格数据模型后,用于确定区域A到区域B的路径 :param areaA 起始区域坐标 :param areaB 目标区域坐标 :return path:数组类型,例如:['L','R'..] L:left,R:right,U:up,D:down ''' def getPathA2B(self, areaA,areaB): print "getPathA2B:", areaA,areaB aIndex = self.indexByArea(areaA) bIndex = self.indexByArea(areaB) print "area A , B:", aIndex,bIndex path = [] #x 轴,水平左右方向移动值. L 或 R for i in range(abs(bIndex[1] - aIndex[1])): if (bIndex[1] - aIndex[1]) > 0: path.append('R') else: path.append('L') # y 轴,纵向上下方向移动值. U 或 D for i in range(abs(bIndex[0] - aIndex[0])): if (bIndex[0] - aIndex[0]) > 0: path.append('D') else: path.append('U') return path ''' 在构建好网格数据模型后,用于确定区域A到区域B的路径 :param path getPathA2B函数返回的路径。例如:L-L-D-U-R :param keyDict 例如:{'U':"up","D":"down","L":"left","R":"right"},up/down/left/right是实际操控电视的指令。 :return path:数组类型,例如:['L','R'..] L:left,R:right,U:up,D:down ''' def executePath(self, path, keyDict): print "FocusModel,executePath:",path for step in path: self.operator.sendKey(keyDict[step]) ''' 在构建好网格数据模型后,用于确定区域A到区域B的路径,然后按路径一次性移动焦点。 :param areaL 起始区域坐标 :param name 目标区域的名字 :param keyDict 例如:{'U':"up","D":"down","L":"left","R":"right"},up/down/left/right是实际操控电视的指令。 ''' def gotoAreaByPath(self, areaL, name, keyDict): # key是否存在字典中; isKeyExist = name.lower() in self.focusDict.keys() if isKeyExist == True: path = self.getPathA2B(areaL, self.focusDict[name.lower()]) self.executePath(path, keyDict) # 如何键值存在返回true return isKeyExist ''' 根据icon,寻找在图片中icon的位置,知道icon所在位置的文本内容为参数textT值 :param textT: 选中区域的文本内容 :param direction: 方位,遥控器的上下左右操作,值范围:U、D、L、R,必须是keyDict中键。符合executePath函数返回值规范 :param keyDict 例如:{'U':"up","D":"down","L":"left","R":"right"},up/down/left/right是实际操控电视的指令。 :param maxMove 在指定方位移动的最大次数 :param icon:图标文件路径 :param uiDict: 可能出现icon的区域。 例如: #name的字符串,需为text_area区域显示的文字 { focus_tv : '{"name":"tv","text_area":[179,210,242,248,"english", 253],"focus_area":[43,128,380,265]}', focus_av : '{"name":"av","text_area":[180,339,242,381,"english", 253],"focus_area":[43,257,380,395]}', focus_hdmi1 : '{"name":"hdmi1","text_area":[156,466,269,510,"english", 2],"focus_area":[43,386,380,525]}', focus_hdmi2 : '{"name":"hdmi2","text_area":[159,599,265,641,"english", 2],"focus_area":[43,517,380,655]}', focus_usb : '{"name":"media","text_area":[160,730,260,771,"english", 2],"focus_area":[43,648,380,785]}' } :param border_wide:四周选择框的厚度 :return result:True代表成功,False代表失败 ''' def gotoAreaByDirection(self, textT,direction, keyDict, maxMove, uiDict, border_wide): pic_tmp = os.path.join(getSATTmpDIR(), "focus_gotoAreaByDirection.png") count = 0 while True: if count > maxMove: break count += 1 self.vcap.takePicture(pic_tmp) textL, text_area, focus_area = self.featureDetect.getFocusArea(self.focus_icon_file, pic_tmp, uiDict, border_wide) if textT.lower() == textL.lower(): return True self.operator.sendKey(keyDict[direction]) return False if __name__ == "__main__": focusModel = FocusModel() # 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]} 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], u'test':[400,500,600,800]} focusModel.genFocusGridMode(focusDict)