#!/usr/bin/env python3 # coding=utf-8 # # Copyright (c) 2022 Huawei Device Co., Ltd. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # import enum import time try: from PIL import Image from PIL import ImageDraw except ImportError: pass from devicetest.utils.time_util import TS class ImgLoc(enum.Enum): ''' 图片所在的枚举类 1左上 (North_West) 2中上 (North) 3右上 (Nort_East) 4左中 (West) 5全图 (All) 6右中 (East) 7左下 (South_West) 8中下 (South) 9右下(South_East) ''' # 在大图的左上内 North_West = 1 # 代表子图部分全部位于大图的水平中线以上 North = 2 # 在大图的右上内 North_East = 3 # 代表子图部分全部位于大图的垂直中线左 West = 4 # 全图查找,当小图跟大图的水平中线, 且与锤子中线都有交集,就只能用该参数了 All = 5 # 代表子图部分全部位于大图的垂直中线右 East = 6 # 在大图的左下内 South_West = 7 # 代表子图部分全部位于大图的水平中线以下 South = 8 # 在大图的右下内 South_East = 9 class ImgUtils: @staticmethod def img2arr(arr_img, rect=None, convert=True): ''' @将位图流转化为二维二值数组 @param arr_img: instance of Image ''' if convert and arr_img.mode != 'L': arr_img = arr_img.convert('L') width, height = arr_img.size pix = arr_img.load() if rect: rect_l, rect_t, rect_r, rect_b = rect result_list = [] for pix_h in range(height): if not rect_t <= pix_h <= rect_b + 1: continue temp_list = [] for pix_w in range(width): if not rect_l <= pix_w <= rect_r + 1: continue temp_list.append(pix[pix_w, pix_h]) result_list.append(temp_list) return result_list result_list = [] for pix_h in range(height): temp_list = [] for pix_w in range(width): temp_list.append(pix[pix_w, pix_h]) result_list.append(temp_list) return result_list @staticmethod def get_rect(rect_width, rect_height, location): ''' 根据方位对象,获取图片的方位 ''' rect = (0, 0, rect_width, rect_height) if location == ImgLoc.East.value: rect = (int(rect_width >> 1), 0, rect_width, rect_height) elif location == ImgLoc.South.value: rect = (0, int(rect_height >> 1), rect_width, rect_height) elif location == ImgLoc.West.value: rect = (0, 0, int(rect_width >> 1), rect_height) elif location == ImgLoc.North.value: rect = (0, 0, rect_width, int(rect_height >> 1)) elif location == ImgLoc.North_East.value: rect = (int(rect_width >> 1), 0, rect_width, int(rect_height >> 1)) elif location == ImgLoc.South_East.value: rect = (int(rect_width >> 1), int(rect_height >> 1), rect_width, rect_height) elif location == ImgLoc.North_West.value: rect = (0, 0, int(rect_width >> 1), int(rect_height >> 1)) elif location == ImgLoc.South_West.value: rect = (0, int(rect_height >> 1), int(rect_width >> 1), rect_height) return rect @staticmethod def quick_find(fp1, fp2, similar=1, density=None, rect=None, loc=ImgLoc.All.value, debug=False): ''' 快速查找图片,指定的similar越大,速度越快 1. 如果similar不等于1,则使用density来加快查找速度 density(x,y) 表示对比的时候 每个横坐标上只对比 (width / 2^x)个点 每个纵坐标上只对比 height / 2^y个点,相当于只对比原来的 (width + height) / 2^(x+y) 个点 @param fp1: 大图片的绝对路径 @param fp2: 小图片的绝对路径 @param similar: 对比的相似度;如 0.7, 0.9, 1 @param density: 小图中对比的密度: (2,3) 表示每行对比 width >> 2个点; 没列对比 height >> 3个点 @param rect: 大图中指定区域内查找 (left,top,right,bottom) @param loc: 小图在大图中的什么部位,是一个枚举对象,ImgLoc,注意需要 加.value;如: ImgLoc.North.value 或者 直接输入 1 -9 的数字也行 @param debug: 是否打印debug信息 ''' if debug: TS.start() _m1 = Image.open(fp1) _m2 = Image.open(fp2) m1_w, m1_h = _m1.size if not rect: rect = ImgUtils.get_rect(m1_w, m1_h, loc) data1 = ImgUtils.img2arr(_m1.crop(rect) if rect else _m1) data2 = ImgUtils.img2arr(_m2) if debug: TS.stop("before find_arr") return ImgUtils.find_arr(data1, data2, similar, density, rect, debug) @staticmethod def find_arr(im1, im2, similar=1, density=None, rect=None, debug=False): ''' 在大图中查找小图 注意:如果density值为None,则系统自动设置,保证特征点在9 - 16个左右 (即 3 * 3 或 4 * 4之间) @param im1 大图的二维数组 @param im2 小图的二维数组 @param similar 相似度 @param density (x,y) x: 可以控制小图横坐标查找的点数 im2Width >> x 个点数 @param rect 在指定的区域中查找图片 (若指定,则可以大大节省时间) (leftX,topY,rihgtX,bottomY) @return (rect,similar) rect:找到的图片位置; similar:相似度 ''' if debug: TS.start() m2_width = len(im2[0]) m2_height = len(im2) arr_width = len(im1[0]) - m2_width + 1 arr_height = len(im1) - m2_height + 1 denx, deny = 0, 0 if not density: denx, deny = ImgUtils.get_density(m2_width, m2_height) else: denx, deny = density den_yy = int(m2_height >> deny) den_xx = int(m2_width >> denx) total = den_yy * den_xx if total == 0: total = 1 max_fail_num = (1 - similar) * total if debug: print("denXX: %i; denYY: %i; total: %i" % ( den_xx, den_yy, total)) print("maxFailNum %i" % max_fail_num) starttime = time.time() endtime = starttime + 5.0 * 60.0 for arr_h in range(arr_height): for arr_w in range(arr_width): # 对图片对比设置超时限制 if time.time() <= endtime: # 1. 对比当前位置的图片是否符合要求 fail_num = 0 found = True for _yy in range(den_yy): for _xx in range(den_xx): x_den = _xx << denx y_den = _yy << deny m2_val = im2[y_den][x_den] m1_val = im1[arr_h + y_den][x_den + arr_w] if m1_val != m2_val: fail_num += 1 if max_fail_num <= fail_num: found = False break if not found: break if found: if debug: TS.stop("find_arr") if rect: # @UnusedVariable rect_l, rect_t, rect_r, rect_b = rect return (1 - fail_num / total), ( arr_w + rect_l, arr_h + rect_t, arr_w + m2_width + rect_l, arr_h + m2_height + rect_t) return (1 - fail_num / total), ( arr_w, arr_h, arr_w + m2_width, arr_h + m2_height) else: return None, None if debug: TS.stop("find_arr") return None, None @staticmethod def img_filter(filter_img, *filters): last_img = filter_img for _filter in filters: last_img = last_img.filter(_filter) return last_img @staticmethod def get_density(width, height, maxWNum=4, maxHNum=4): denx, deny = 0, 0 while width > maxWNum: denx += 1 width = int(width >> 1) while height > maxHNum: deny += 1 height = int(height >> 1) return denx, deny