鱼C论坛

 找回密码
 立即注册
查看: 2831|回复: 36

[作品展示] 400行代码通过python解数独并操作手机自动填入

[复制链接]
发表于 2022-3-29 13:41:30 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
  1. import xml.dom.minidom, os, time, random
  2. import cv2
  3. from PIL import Image
  4. import pytesseract
  5. import matplotlib.pyplot as plt
  6. import PIL, numpy, time
  7. import pickle


  8. def run_adb_way(adb_command):
  9.     process = os.popen(adb_command)
  10.     output = process.read()
  11.     return output

  12. def go_to_input(x, y):
  13.     run_adb_way('adb shell input tap {} {}'.format(x, y))

  14. def get_shudu_png():
  15.     t1=time.time()
  16.     run_adb_way("adb shell screencap /sdcard/screen.png")  # adb shell screencap /sdcard/截图名称
  17.     run_adb_way("adb pull  /sdcard/screen.png {}\screen.png".format(os.getcwd()))

  18.     img = cv2.imread(r'{}\screen.png'.format(os.getcwd()))
  19.     x=[]
  20.     y=[]
  21.     #for i in range(len(img)):、
  22.         #for j in range(len(img[i])):
  23.             #if 208 in img[i][j] and 134 in img[i][j] and 42 in img[i][j]:#橙色线条的rgb是208,134,42
  24.                 #if i not in y:
  25.                     #y.append(i)
  26.                 #if j not in x:
  27.                     #x.append(j)
  28.     #zuobiao = (x[0],y[0],x[0]+len(x),y[0]+len(y))
  29.     zuobiao=(13, 464, 1069, 1520)
  30.     img = Image.open(r'{}\screen.png'.format(os.getcwd()))
  31.     cropped = img.crop(zuobiao)
  32.     cropped.save(r'{}\shudu.png'.format(os.getcwd()))
  33.     os.remove(r'{}\screen.png'.format(os.getcwd()))
  34.     print('将截屏中的数独单独裁剪出来,耗时{}秒'.format(time.time()-t1))

  35. def get_shuzi():
  36.     q=0
  37.     img = Image.open(r'{}\shudu.png'.format(os.getcwd()))
  38.     shudu=[]
  39.     print('正在识别数字中....请稍后')
  40.     for i in range(0, 1056, 117)[:-1]:
  41.         for j in range(0, 1056, 117)[:-1]:
  42.             q+=1
  43.             zuobiao = (35, 20, 86, 100)#将数独分割之后再切割
  44.             cropped = img.crop((j, i, j + 117, i + 117)).crop(zuobiao)
  45.             path=r'{}\shuduqiege\temp{}.png'.format(os.getcwd(),q)
  46.             cropped.save(path)
  47.             try:
  48.                 num = get_num(path)
  49.             except:
  50.                 num=0
  51.             shudu.append(num)

  52.     result=[]
  53.     for y in range(0, 9):
  54.         for x in range(0, 9):
  55.             if x == 0:
  56.                 result.append([])
  57.             result[y].append(shudu[x + y * 9])
  58.     return result

  59. def pretreatment(ima):
  60.     ima = ima.convert('L')
  61.     im = numpy.array(ima)
  62.     for i in range(im.shape[0]):  # 转化为二值矩阵
  63.         for j in range(im.shape[1]):
  64.             if im[i, j] == 105 or im[i, j] == 160 or im[i, j] == 126:
  65.                 im[i, j] = 1
  66.             else:
  67.                 im[i, j] = 0
  68.     return im

  69. # 提取图片特征
  70. def feature(A):
  71.     midx = int(A.shape[1] / 2) + 1
  72.     midy = int(A.shape[0] / 2) + 1
  73.     A1 = A[0:midy, 0:midx].mean()
  74.     A2 = A[midy:A.shape[0], 0:midx].mean()
  75.     A3 = A[0:midy, midx:A.shape[1]].mean()
  76.     A4 = A[midy:A.shape[0], midx:A.shape[1]].mean()
  77.     A5 = A.mean()
  78.     AF = [A1, A2, A3, A4, A5]
  79.     return AF

  80. # 切割图片并返回每个子图片特征
  81. def incise(im):
  82.     # 竖直切割并返回切割的坐标
  83.     a = [];
  84.     b = []
  85.     if any(im[:, 0] == 1):
  86.         a.append(0)
  87.     for i in range(im.shape[1] - 1):  # 第i列
  88.         if all(im[:, i] == 0) and any(im[:, i + 1] == 1):  # 判断左边界并选择切割坐标
  89.             a.append(i + 1)
  90.         elif any(im[:, i] == 1) and all(im[:, i + 1] == 0):  # 判断右边界
  91.             b.append(i + 1)
  92.     if any(im[:, im.shape[1] - 1] == 1):
  93.         b.append(im.shape[1])
  94.     # 水平切割并返回分割图片特征
  95.     names = locals();
  96.     AF = []
  97.     for i in range(len(a)):
  98.         names['na%s' % i] = im[:, range(a[i], b[i])]
  99.         if any(names['na%s' % i][0, :] == 1):  # 如果第一行含1,则直接从第一行开始截取
  100.             c = 0
  101.         elif any(names['na%s' % i][names['na%s' % i].shape[0] - 1, :] == 1):  # 如果最后一行含1,则直接从最后一行开始截取
  102.             d = names['na%s' % i].shape[0] - 1
  103.         for j in range(names['na%s' % i].shape[0] - 1):  # 每行开始循环
  104.             if all(names['na%s' % i][j, :] == 0) and any(names['na%s' % i][j + 1, :] == 1):  # 寻找上边界
  105.                 c = j + 1
  106.             elif any(names['na%s' % i][j, :] == 1) and all(names['na%s' % i][j + 1, :] == 0):  # 寻找下边界
  107.                 d = j + 1
  108.         names['na%s' % i] = names['na%s' % i][range(c, d), :]
  109.         AF.append(feature(names['na%s' % i]))  # 提取特征
  110.     return AF

  111. def training():
  112.     train_set = {}
  113.     for i in range(1, 10):
  114.         value = []
  115.         for j in range(1, 5):
  116.             try:
  117.                 ima = PIL.Image.open('C:\\Users\\m\\Desktop\\shibie\\' + str(i) + '\\' + str(i) + '-' + str(j) + '.png')
  118.                 im = pretreatment(ima)
  119.                 AF = incise(im)
  120.                 value.append(AF[0])
  121.             except:
  122.                 pass
  123.         train_set[i] = value
  124.     # 把训练结果存为永久文件,以备下次使用
  125.     # output=open(r'C:\Users\m\Desktop\train_set.pkl','wb')
  126.     # pickle.dump(train_set,output)
  127.     # output.close()

  128.     return train_set

  129. def distance(v1, v2):
  130.     vector1 = numpy.array(v1)
  131.     vector2 = numpy.array(v2)
  132.     Vector = (vector1 - vector2) ** 2
  133.     distance = Vector.sum() ** 0.5
  134.     return distance

  135. def knn(train_set, V, k):
  136.     key_sort = [9] * k
  137.     value_sort = [9] * k
  138.     for key in range(1, 10):
  139.         for value in train_set[key]:
  140.             d = distance(V, value)
  141.             for i in range(k):
  142.                 if d < value_sort[i]:
  143.                     for j in range(k - 2, i - 1, -1):
  144.                         key_sort[j + 1] = key_sort[j]
  145.                         value_sort[j + 1] = value_sort[j]
  146.                     key_sort[i] = key
  147.                     value_sort[i] = d
  148.                     break
  149.     max_key_count = -1
  150.     key_set = set(key_sort)
  151.     for key in key_set:
  152.         if max_key_count < key_sort.count(key):
  153.             max_key_count = key_sort.count(key)
  154.             max_key = key
  155.     return max_key

  156. def get_num(ima_path):
  157.     ima = PIL.Image.open(ima_path)
  158.     im = pretreatment(ima)  # 图片预处理
  159.     AF = incise(im)  # 切割图片并提取特征
  160.     train_set = {
  161.         1: [[0.16285714285714287, 0.0, 0.6928571428571428, 0.625, 0.3389694041867955],
  162.             [0.1746031746031746, 0.0, 0.6928571428571428, 0.625, 0.35720375106564367]],
  163.         2: [[0.16326530612244897, 0.28715728715728717, 0.30142857142857143, 0.21515151515151515, 0.24103299856527977],
  164.             [0.16054421768707483, 0.28851540616246496, 0.2985714285714286, 0.21029411764705883, 0.23895369388476492]],
  165.         3: [[0.1856060606060606, 0.1697860962566845, 0.33611111111111114, 0.31176470588235294, 0.24761904761904763],
  166.             [0.17803030303030304, 0.17914438502673796, 0.31805555555555554, 0.3191176470588235, 0.24523809523809523]],
  167.         4: [[0.13942857142857143, 0.2109090909090909, 0.27701863354037265, 0.31488801054018445, 0.23223039215686275],
  168.             [0.12738095238095237, 0.20955882352941177, 0.32919254658385094, 0.34782608695652173, 0.25131051495528833],
  169.             [0.13714285714285715, 0.21212121212121213, 0.31801242236024846, 0.35046113306982873, 0.2503063725490196],
  170.             [0.12738095238095237, 0.20955882352941177, 0.32919254658385094, 0.34782608695652173, 0.25131051495528833]],
  171.         5: [[0.44027777777777777, 0.20285714285714285, 0.21637426900584794, 0.28421052631578947, 0.28746840014445646],
  172.             [0.44027777777777777, 0.20285714285714285, 0.21637426900584794, 0.28421052631578947, 0.28746840014445646]],
  173.         6: [[0.34903381642512077, 0.30807453416149067, 0.24735449735449735, 0.28843537414965986, 0.29961587708066584],
  174.             [0.34782608695652173, 0.2979539641943734, 0.23677248677248677, 0.28851540616246496, 0.29415584415584417]],
  175.         7: [[0.11428571428571428, 0.20202020202020202, 0.37293233082706767, 0.07177033492822966, 0.1900735294117647],
  176.             [0.14285714285714285, 0.19624819624819625, 0.3894736842105263, 0.08133971291866028, 0.2025735294117647],
  177.             [0.11428571428571428, 0.20202020202020202, 0.37293233082706767, 0.07177033492822966, 0.1900735294117647],
  178.             [0.11428571428571428, 0.1976911976911977, 0.3744360902255639, 0.07655502392344497, 0.19044117647058822]],
  179.         8: [[0.30434782608695654, 0.30051150895140666, 0.3029100529100529, 0.28711484593837533, 0.29902597402597403],
  180.             [0.31313131313131315, 0.3155080213903743, 0.30687830687830686, 0.29411764705882354, 0.30764119601328904],
  181.             [0.31691919191919193, 0.3114973262032086, 0.31084656084656087, 0.2913165266106443, 0.3079734219269103],
  182.             [0.31691919191919193, 0.3114973262032086, 0.31084656084656087, 0.2913165266106443, 0.3079734219269103]],
  183.         9: [[0.2979591836734694, 0.2703081232492997, 0.3157894736842105, 0.3668730650154799, 0.31123188405797103]]}
  184.     #train_set = training()
  185.     num = knn(train_set, AF, 1)
  186.     return num





  187. class SuduKu():
  188.     def __init__(self, sudu_ku_data):
  189.         if not isinstance(sudu_ku_data, list):
  190.             raise TypeError(f'数独必须是一个列表, 但是{sudo_ku_data}却是 {type(sudo_ku_data)}')
  191.         if len(sudu_ku_data) != 9 or len(sudu_ku_data[0]) != 9:
  192.             raise TypeError(f'数独必须是 9*9 列表, 但是 {sudo_ku_data} 是一个{len(sudo_ku_data)}*{len(sudo_ku_data[0])}的列表')

  193.         self.sudo_ku = sudu_ku_data
  194.         # 存放每一行已有的数据
  195.         self.every_row_data = {}
  196.         # 存放每一列已有的数据
  197.         self.every_column_data = {}
  198.         # 存放每一个3*3已有的数字
  199.         self.every_three_to_three_data = {}
  200.         # 存放每一个空缺的位置
  201.         self.vacant_position = []
  202.         # 每一个空缺位置尝试了的数字
  203.         self.every_vacant_position_tried_values = {}
  204.         # 初始化数据
  205.         self._init()

  206.     def _add_row_data(self, row, value):
  207.         if row not in self.every_row_data:
  208.             self.every_row_data[row] = set()
  209.         if value in self.every_row_data[row]:
  210.             raise TypeError(f'{self.sudo_ku}是个无效数独')
  211.         self.every_row_data[row].add(value)

  212.     def _add_column_data(self, column, value):
  213.         if column not in self.every_column_data:
  214.             self.every_column_data[column] = set()
  215.         if value in self.every_column_data[column]:
  216.             raise TypeError(f'{self.sudo_ku}是个无效数独')
  217.         self.every_column_data[column].add(value)

  218.     def _get_three_to_three_key(self, row, column):
  219.         if row in [0, 1, 2]:
  220.             if column in [0, 1, 2]:
  221.                 key = 1
  222.             elif column in [3, 4, 5]:
  223.                 key = 2
  224.             else:
  225.                 key = 3
  226.         elif row in [3, 4, 5]:
  227.             if column in [0, 1, 2]:
  228.                 key = 4
  229.             elif column in [3, 4, 5]:
  230.                 key = 5
  231.             else:
  232.                 key = 6
  233.         elif row in [6, 7, 8]:
  234.             if column in [0, 1, 2]:
  235.                 key = 7
  236.             elif column in [3, 4, 5]:
  237.                 key = 8
  238.             else:
  239.                 key = 9
  240.         return key

  241.     def _add_three_to_three_data(self, row, column, value):
  242.         key = self._get_three_to_three_key(row, column)
  243.         if key not in self.every_three_to_three_data:
  244.             self.every_three_to_three_data[key] = set()
  245.         self.every_three_to_three_data[key].add(value)

  246.     def _init(self):
  247.         for row, row_datas in enumerate(self.sudo_ku):
  248.             for column, value in enumerate(row_datas):
  249.                 if value == 0:
  250.                     self.vacant_position.append((row, column))
  251.                 else:
  252.                     self._add_row_data(row, value)
  253.                     self._add_column_data(column, value)
  254.                     self._add_three_to_three_data(row, column, value)

  255.     def _judge_value_is_legal(self, row, column, value):  # 判断放置的数据是否合法
  256.         if value in self.every_row_data[row]:
  257.             return False
  258.         if value in self.every_column_data[column]:
  259.             return False
  260.         key = self._get_three_to_three_key(row, column)
  261.         if value in self.every_three_to_three_data[key]:
  262.             return False

  263.         return True

  264.     def _calculate(self, vacant_position):  # 计算,开始对数独进行放置值
  265.         row, column = vacant_position
  266.         values = set(range(1, 10))
  267.         key = str(row) + str(column)  # 对当前为位置创建一个唯一key,用来存放当前位置已经尝试了的数据
  268.         if key in self.every_vacant_position_tried_values:
  269.             values = values - self.every_vacant_position_tried_values[key]
  270.         else:
  271.             self.every_vacant_position_tried_values[key] = set()
  272.         for value in values:
  273.             self.every_vacant_position_tried_values[key].add(value)
  274.             if self._judge_value_is_legal(row, column, value):  # 如果当前value合法,可以放置
  275.                 #print(f"{vacant_position}空值的值是{value}")
  276.                 self.every_column_data[column].add(value)
  277.                 self.every_row_data[row].add(value)
  278.                 key = self._get_three_to_three_key(row, column)
  279.                 self.every_three_to_three_data[key].add(value)
  280.                 self.sudo_ku[row][column] = value  # 修改这个位置的值为value
  281.                 return True, value  # 返回True 和填充的 value

  282.         return False, None

  283.     def _backtrack(self, current_vacant_position, previous_vacant_position, previous_value):
  284.         """
  285.         回溯
  286.         :param current_vacant_position: 当前尝试失败的位置
  287.         :param previous_vacant_position: 上一次成功的位置
  288.         :param previous_value:上一次成功的值
  289.         :return:
  290.         """
  291.         #print(f"上一次成功的值是{previous_value},上一次成功的位置是{previous_vacant_position}")
  292.         row, column = previous_vacant_position

  293.         # 对上一次成功的值从需要用到的判断的数据中移除
  294.         self.every_column_data[column].remove(previous_value)
  295.         self.every_row_data[row].remove(previous_value)
  296.         key = self._get_three_to_three_key(row, column)
  297.         self.every_three_to_three_data[key].remove(previous_value)
  298.         self.sudo_ku[row][column] = ''  # 并且上一次改变的的值变回去
  299.         current_row, current_column = current_vacant_position
  300.         key = str(current_row) + str(current_column)
  301.         self.every_vacant_position_tried_values.pop(key)

  302.     def get_result(self):
  303.         print('正在计算数独中....请稍后')
  304.         q = 0
  305.         t1 = time.time()
  306.         length = len(self.vacant_position)  # 空缺位置的长度
  307.         index = 0  # 空缺位置的下标
  308.         tried_values = []  # 存放已经尝试了的数据
  309.         while index < length:
  310.             q += 1
  311.             vacant_position = self.vacant_position[index]
  312.             is_success, value = self._calculate(vacant_position)

  313.             if is_success:
  314.                 tried_values.append(value)
  315.                 index += 1
  316.             else:
  317.                 self._backtrack(vacant_position, self.vacant_position[index - 1], tried_values.pop())
  318.                 index -= 1
  319.             if index < 0:
  320.                 raise ValueError(f"{self.sudo_ku}是无效的数独")
  321.         self.show_sudu_ku()
  322.         print(time.time() - t1,q)
  323.         return self.sudo_ku
  324.     def show_sudu_ku(self):
  325.         for row in self.sudo_ku:
  326.             print(row)

  327. def judge_value_is_legal(row, column, value, sudo_ku):  # 判断值是否合法
  328.     # column
  329.     for i in range(0, 9):
  330.         if row == i:
  331.             continue
  332.         if value == sudo_ku[i][column]:
  333.             return False
  334.     # row
  335.     for i in range(0, 9):
  336.         if column == i:
  337.             continue
  338.         if value == sudo_ku[row][i]:
  339.             return False

  340.     # three_to_three
  341.     for i in range(row // 3 * 3, row // 3 * 3 + 3):
  342.         for j in range(column // 3 * 3, column // 3 * 3 + 3):
  343.             if i == row and j == column:
  344.                 continue
  345.             if value == sudo_ku[i][j]:
  346.                 return False

  347.     return True

  348. def judge_sudo_ku_is_legal(sudo_ku):  # 判断数独是否合法
  349.     for row, row_valus in enumerate(sudo_ku):
  350.         for column, value in enumerate(row_valus):
  351.             if not judge_value_is_legal(row, column, value, sudo_ku):
  352.                 return False
  353.     return True

  354. def shudu_to_screen(SuduKu):
  355.     num_xy_dict = dict(zip([num for num in range(1, 10)], [(x, 1982) for x in range(91, 1000, 112)]))#获得备选数字的坐标
  356.     num_screen_xy_dict = [(j + 99, i + 550) for i in range(0, 1056, 117)[:-1] for j in range(0, 1056, 117)[:-1]]#获得数独每个单元格的坐标
  357.     for i in range(9):
  358.         for j in range(9):
  359.             x1, y1 = num_xy_dict[SuduKu[i][j]]
  360.             x2, y2 = num_screen_xy_dict[i * 9 + j]
  361.             # time.sleep(0.1)
  362.             go_to_input(x2, y2)#先填数独需要填的单元格坐标
  363.             # time.sleep(0.1)
  364.             go_to_input(x1, y1)#后点该单元格的正确数字
  365.             print(x2, y2, x1, y1, "第{}行第{}列的数字是{}".format(i + 1, j + 1, SuduKu[i][j]))




  366. def main():
  367.     get_shudu_png()#获得数独图片
  368.     sudo_ku_data = get_shuzi()#数独图片识别未数字数独
  369.     shudu_answer = SuduKu(sudo_ku_data).get_result()#解数独
  370.     shudu_to_screen(shudu_answer)#将数独的结果传到手机上面

  371. if __name__=='__main__':
  372.     main()
复制代码

数独.zip

191.56 KB, 下载次数: 13

售价: 1 鱼币  [记录]

源码

评分

参与人数 1鱼币 +1 收起 理由
Passepartout + 1 无条件支持楼主!

查看全部评分

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2022-3-29 20:07:12 | 显示全部楼层

回帖奖励 +1 鱼币

感谢分享!
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-3-30 00:19:26 | 显示全部楼层
66666666666666666
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-3-30 08:02:54 | 显示全部楼层

回帖奖励 +1 鱼币

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2022-3-30 08:26:32 | 显示全部楼层

回帖奖励 +1 鱼币

厉害了大佬
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-3-30 09:30:43 | 显示全部楼层

回帖奖励 +1 鱼币

1
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2022-3-30 09:33:23 | 显示全部楼层
2
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2022-3-30 09:34:11 | 显示全部楼层

回帖奖励 +1 鱼币

3
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2022-3-30 09:42:03 | 显示全部楼层
大佬太强了
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-3-30 10:43:26 | 显示全部楼层
这是怎么回事,少了图片
FileNotFoundError: [Errno 2] No such file or directory: 'G:\\0\\数独\\screen.png'
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-3-30 13:16:30 | 显示全部楼层
5
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2022-3-30 13:25:30 | 显示全部楼层
感谢分享!
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-3-30 13:30:08 | 显示全部楼层

回帖奖励 +1 鱼币

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2022-3-30 13:34:09 | 显示全部楼层

回帖奖励 +1 鱼币

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2022-3-30 16:01:10 | 显示全部楼层
大家都会Python啊,我好废物啊,咋办?
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-3-30 16:01:45 | 显示全部楼层

回帖奖励 +1 鱼币

哎,我突然想学Python了,Python能不能做游戏啊?

小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2022-3-30 16:37:15 | 显示全部楼层

回帖奖励 +1 鱼币

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2022-3-30 17:16:45 | 显示全部楼层
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2022-3-30 19:28:22 | 显示全部楼层

回帖奖励 +1 鱼币

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2022-3-30 19:38:51 | 显示全部楼层

回帖奖励 +1 鱼币

感谢大佬
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-4-29 08:16

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表