孟大大 发表于 2022-3-29 13:41:30

400行代码通过python解数独并操作手机自动填入

import xml.dom.minidom, os, time, random
import cv2
from PIL import Image
import pytesseract
import matplotlib.pyplot as plt
import PIL, numpy, time
import pickle


def run_adb_way(adb_command):
    process = os.popen(adb_command)
    output = process.read()
    return output

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

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

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

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

    result=[]
    for y in range(0, 9):
      for x in range(0, 9):
            if x == 0:
                result.append([])
            result.append(shudu)
    return result

def pretreatment(ima):
    ima = ima.convert('L')
    im = numpy.array(ima)
    for i in range(im.shape):# 转化为二值矩阵
      for j in range(im.shape):
            if im == 105 or im == 160 or im == 126:
                im = 1
            else:
                im = 0
    return im

# 提取图片特征
def feature(A):
    midx = int(A.shape / 2) + 1
    midy = int(A.shape / 2) + 1
    A1 = A.mean()
    A2 = A, 0:midx].mean()
    A3 = A].mean()
    A4 = A, midx:A.shape].mean()
    A5 = A.mean()
    AF =
    return AF

# 切割图片并返回每个子图片特征
def incise(im):
    # 竖直切割并返回切割的坐标
    a = [];
    b = []
    if any(im[:, 0] == 1):
      a.append(0)
    for i in range(im.shape - 1):# 第i列
      if all(im[:, i] == 0) and any(im[:, i + 1] == 1):# 判断左边界并选择切割坐标
            a.append(i + 1)
      elif any(im[:, i] == 1) and all(im[:, i + 1] == 0):# 判断右边界
            b.append(i + 1)
    if any(im[:, im.shape - 1] == 1):
      b.append(im.shape)
    # 水平切割并返回分割图片特征
    names = locals();
    AF = []
    for i in range(len(a)):
      names['na%s' % i] = im[:, range(a, b)]
      if any(names['na%s' % i] == 1):# 如果第一行含1,则直接从第一行开始截取
            c = 0
      elif any(names['na%s' % i].shape - 1, :] == 1):# 如果最后一行含1,则直接从最后一行开始截取
            d = names['na%s' % i].shape - 1
      for j in range(names['na%s' % i].shape - 1):# 每行开始循环
            if all(names['na%s' % i] == 0) and any(names['na%s' % i] == 1):# 寻找上边界
                c = j + 1
            elif any(names['na%s' % i] == 1) and all(names['na%s' % i] == 0):# 寻找下边界
                d = j + 1
      names['na%s' % i] = names['na%s' % i]
      AF.append(feature(names['na%s' % i]))# 提取特征
    return AF

def training():
    train_set = {}
    for i in range(1, 10):
      value = []
      for j in range(1, 5):
            try:
                ima = PIL.Image.open('C:\\Users\\m\\Desktop\\shibie\\' + str(i) + '\\' + str(i) + '-' + str(j) + '.png')
                im = pretreatment(ima)
                AF = incise(im)
                value.append(AF)
            except:
                pass
      train_set = value
    # 把训练结果存为永久文件,以备下次使用
    # output=open(r'C:\Users\m\Desktop\train_set.pkl','wb')
    # pickle.dump(train_set,output)
    # output.close()

    return train_set

def distance(v1, v2):
    vector1 = numpy.array(v1)
    vector2 = numpy.array(v2)
    Vector = (vector1 - vector2) ** 2
    distance = Vector.sum() ** 0.5
    return distance

def knn(train_set, V, k):
    key_sort = * k
    value_sort = * k
    for key in range(1, 10):
      for value in train_set:
            d = distance(V, value)
            for i in range(k):
                if d < value_sort:
                  for j in range(k - 2, i - 1, -1):
                        key_sort = key_sort
                        value_sort = value_sort
                  key_sort = key
                  value_sort = d
                  break
    max_key_count = -1
    key_set = set(key_sort)
    for key in key_set:
      if max_key_count < key_sort.count(key):
            max_key_count = key_sort.count(key)
            max_key = key
    return max_key

def get_num(ima_path):
    ima = PIL.Image.open(ima_path)
    im = pretreatment(ima)# 图片预处理
    AF = incise(im)# 切割图片并提取特征
    train_set = {
      1: [,
            ],
      2: [,
            ],
      3: [,
            ],
      4: [,
            ,
            ,
            ],
      5: [,
            ],
      6: [,
            ],
      7: [,
            ,
            ,
            ],
      8: [,
            ,
            ,
            ],
      9: []}
    #train_set = training()
    num = knn(train_set, AF, 1)
    return num





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

      self.sudo_ku = sudu_ku_data
      # 存放每一行已有的数据
      self.every_row_data = {}
      # 存放每一列已有的数据
      self.every_column_data = {}
      # 存放每一个3*3已有的数字
      self.every_three_to_three_data = {}
      # 存放每一个空缺的位置
      self.vacant_position = []
      # 每一个空缺位置尝试了的数字
      self.every_vacant_position_tried_values = {}
      # 初始化数据
      self._init()

    def _add_row_data(self, row, value):
      if row not in self.every_row_data:
            self.every_row_data = set()
      if value in self.every_row_data:
            raise TypeError(f'{self.sudo_ku}是个无效数独')
      self.every_row_data.add(value)

    def _add_column_data(self, column, value):
      if column not in self.every_column_data:
            self.every_column_data = set()
      if value in self.every_column_data:
            raise TypeError(f'{self.sudo_ku}是个无效数独')
      self.every_column_data.add(value)

    def _get_three_to_three_key(self, row, column):
      if row in :
            if column in :
                key = 1
            elif column in :
                key = 2
            else:
                key = 3
      elif row in :
            if column in :
                key = 4
            elif column in :
                key = 5
            else:
                key = 6
      elif row in :
            if column in :
                key = 7
            elif column in :
                key = 8
            else:
                key = 9
      return key

    def _add_three_to_three_data(self, row, column, value):
      key = self._get_three_to_three_key(row, column)
      if key not in self.every_three_to_three_data:
            self.every_three_to_three_data = set()
      self.every_three_to_three_data.add(value)

    def _init(self):
      for row, row_datas in enumerate(self.sudo_ku):
            for column, value in enumerate(row_datas):
                if value == 0:
                  self.vacant_position.append((row, column))
                else:
                  self._add_row_data(row, value)
                  self._add_column_data(column, value)
                  self._add_three_to_three_data(row, column, value)

    def _judge_value_is_legal(self, row, column, value):# 判断放置的数据是否合法
      if value in self.every_row_data:
            return False
      if value in self.every_column_data:
            return False
      key = self._get_three_to_three_key(row, column)
      if value in self.every_three_to_three_data:
            return False

      return True

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

      return False, None

    def _backtrack(self, current_vacant_position, previous_vacant_position, previous_value):
      """
      回溯
      :param current_vacant_position: 当前尝试失败的位置
      :param previous_vacant_position: 上一次成功的位置
      :param previous_value:上一次成功的值
      :return:
      """
      #print(f"上一次成功的值是{previous_value},上一次成功的位置是{previous_vacant_position}")
      row, column = previous_vacant_position

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

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

            if is_success:
                tried_values.append(value)
                index += 1
            else:
                self._backtrack(vacant_position, self.vacant_position, tried_values.pop())
                index -= 1
            if index < 0:
                raise ValueError(f"{self.sudo_ku}是无效的数独")
      self.show_sudu_ku()
      print(time.time() - t1,q)
      return self.sudo_ku
    def show_sudu_ku(self):
      for row in self.sudo_ku:
            print(row)

def judge_value_is_legal(row, column, value, sudo_ku):# 判断值是否合法
    # column
    for i in range(0, 9):
      if row == i:
            continue
      if value == sudo_ku:
            return False
    # row
    for i in range(0, 9):
      if column == i:
            continue
      if value == sudo_ku:
            return False

    # three_to_three
    for i in range(row // 3 * 3, row // 3 * 3 + 3):
      for j in range(column // 3 * 3, column // 3 * 3 + 3):
            if i == row and j == column:
                continue
            if value == sudo_ku:
                return False

    return True

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

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




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

if __name__=='__main__':
    main()

hornwong 发表于 2022-3-29 20:07:12

感谢分享!

amazed 发表于 2022-3-30 00:19:26

66666666666666666

心驰神往 发表于 2022-3-30 08:02:54

{:10_277:}

隔壁老程呀 发表于 2022-3-30 08:26:32

厉害了大佬

a1372245671 发表于 2022-3-30 09:30:43

1

a1372245671 发表于 2022-3-30 09:33:23

2

a1372245671 发表于 2022-3-30 09:34:11

3

bhunht 发表于 2022-3-30 09:42:03

大佬太强了

Nate_2020 发表于 2022-3-30 10:43:26

这是怎么回事,少了图片
FileNotFoundError: No such file or directory: 'G:\\0\\数独\\screen.png'

pybaolilong 发表于 2022-3-30 13:16:30

5

1molHF 发表于 2022-3-30 13:25:30

感谢分享!

1molHF 发表于 2022-3-30 13:30:08

{:10_256:}

1molHF 发表于 2022-3-30 13:34:09

{:10_256:}

C丁洞杀O 发表于 2022-3-30 16:01:10

大家都会Python啊,我好废物啊,咋办?

C丁洞杀O 发表于 2022-3-30 16:01:45

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

零的零次方 发表于 2022-3-30 16:37:15

{:5_102:}

hy2009090003 发表于 2022-3-30 17:16:45

{:7_132:}

1050293757 发表于 2022-3-30 19:28:22

{:5_109:}

shiyouroc 发表于 2022-3-30 19:38:51

感谢大佬
页: [1] 2
查看完整版本: 400行代码通过python解数独并操作手机自动填入