Применение компьютерного зрения в покере

Не так давно я увлекся покером, а поскольку мне нравится работать с компьютерным зрением, я решил совместить бизнес с удовольствием.

Общее функционирование программы

Сразу отмечу, что в качестве игровой комнаты я выбрал PokerStars и самую популярную разновидность покера – Техасский Холдем. Программа запускает бесконечный цикл, который считывает определенную область экрана, где находится покерный стол. Когда наступает очередь нашего героя, появляется или обновляется окно со следующей информацией:

  • какими картами мы располагаем в настоящее время
  • какие карты сейчас на столе
  • общий банк
  • эквити
  • позиция и ставка каждого игрока

Визуально это выглядит следующим образом:

Определение хода героя

Сразу под картами героя есть небольшая область, которая может быть либо черной, либо серой:

Применение компьютерного зрения в покере

Если эта область выделена серым цветом, это наш ход. В противном случае это ход нашего противника. Поскольку наше изображение статично, мы вырезаем эту область по координатам. Затем мы используем функцию inRange(), которая используется для обнаружения пикселей на изображении, находящихся в определенном цветовом диапазоне, передавая туда вырезанное изображение. По количеству белых пикселей в бинарном изображении, возвращенном функцией, мы можем определить, наш это ход или нет.

res_img = self.img[self.cfg['hero_step_define']['y_0']:self.cfg['hero_step_define']['y_1'],
                           self.cfg['hero_step_define']['x_0']:self.cfg['hero_step_define']['x_1']]

hsv_img = cv2.cvtColor(res_img, cv2.COLOR_BGR2HSV_FULL)
mask = cv2.inRange(hsv_img, np.array(self.cfg['hero_step_define']['lower_gray_color']),
                            np.array(self.cfg['hero_step_define']['upper_gray_color']))
count_of_white_pixels = cv2.countNonZero(mask)

Теперь, когда мы определили, что настал наш черед, мы должны распознать карты героя и карты на столе. Для этого я предлагаю снова воспользоваться статичным изображением, вырезать его, а затем бинаризовать области с картами. В результате для таких изображений с картами:

Применение компьютерного зрения в покере

мы получаем следующее двоичное изображение:

Применение компьютерного зрения в покере

После этого мы находим внешние контуры значений и мастей с помощью функции findContours(), которые затем передаем в функцию boundingRect(), возвращающую ограничивающие поля каждого контура. Хорошо, теперь у нас есть поля всех карт, но как узнать, есть ли у нас, например, туз червей? Для этого я нашел и вручную обрезал каждое значение и каждую масть и поместил эти изображения в специальную папку в качестве эталонных. Далее мы вычисляем MSE между каждым из эталонных изображений и обрезанными изображениями карт с помощью этого кода:

err = np.sum((img.astype("float") - benchmark_img.astype("float")) ** 2)
err /= float(img.shape[0] * img.shape[1])

Мы присваиваем ячейке эталонное изображение с именем наименьшей ошибки. Довольно просто 🙂

Определение банка и ставки игрока. Поиск кнопки дилера

Чтобы определить банк, мы будем работать с шаблонным изображением этого вида:

Применение компьютерного зрения в покере

Мы передаем изображение шаблона и изображение всей таблицы в функцию matchTemplate(). Я писал об этом в одной из своих предыдущих статей. В качестве параметра она возвращает координаты левого верхнего угла изображения шаблона на изображении всей таблицы.

Зная эти координаты, мы можем, отступив на постоянное значение вправо, найти цифры банка. Затем, по знакомой схеме, находим контуры и ячейки каждой цифры. После этого мы сравниваем каждую из них с эталонными изображениями цифр и считаем MSE.

Все эти манипуляции, за исключением поиска шаблонного изображения, описаны в данном разделе. То же самое мы делаем со ставками каждого игрока и прописываем координаты ставок в конфигурационном файле. Кнопка дилера в покере – обязательный атрибут, определяющий порядок действий и торгов для всех участников игры.

Если вы должны действовать первым, вы находитесь в ранней позиции. Если вы находитесь в поздней позиции, то ваша очередь действовать одной из последних. Для стола six-max позиции выглядят следующим образом:

Применение компьютерного зрения в покере

Чтобы определить, кто является дилером, мы также берем изображение шаблона, как вы можете видеть:

Применение компьютерного зрения в покере

Находим координаты левого верхнего угла изображения на столе и используем формулу расстояния между двумя точками на плоскости. Прописываем в конфигурационном файле вторые координаты x и y (координаты центра игрока), чтобы определить, кто находится ближе к кнопке, тот и будет ее владельцем :).

Узнаваемость вакантных мест и отсутствующих игроков

Часто бывает, что за столом пять игроков вместо шести, поэтому пустое место отмечается таким образом:

Применение компьютерного зрения в покере

Под ником игрока, который в данный момент отсутствует, появляется следующая надпись:

Применение компьютерного зрения в покере

Чтобы определить наличие таких игроков, мы вводим эти изображения и изображение стола в качестве шаблонов и снова отдаем их функции matchTemplate(). На этот раз мы возвращаем не координаты, а вероятность того, насколько похожи эти два изображения. Если вероятность между первым изображением и таблицей высока, значит, в таблице не хватает игрока.

Расчет собственного капитала

Эквити – это вероятность выигрыша конкретной руки против двух конкретных карт или диапазона противника. Математически эквити рассчитывается как отношение числа возможных выигрышных комбинаций к общему числу возможных комбинаций. В Python этот алгоритм можно реализовать с помощью библиотеки eval7 (которая в данном случае помогает оценить, насколько сильна рука). Это выглядит следующим образом:

deck = [eval7.Card(card) for card in deck]
table_cards = [eval7.Card(card) for card in table_cards]
hero_cards = [eval7.Card(card) for card in hero_cards]
max_table_cards = 5
win_count = 0
for _ in range(iters):
    np.random.shuffle(deck)
    num_remaining = max_table_cards - len(table_cards)
    draw = deck[:num_remaining+2]
    opp_hole, remaining_comm = draw[:2], draw[2:]
    player_hand = hero_cards + table_cards + remaining_comm
    opp_hand = opp_hole + table_cards + remaining_comm
    player_strength = eval7.evaluate(player_hand)
    opp_strength = eval7.evaluate(opp_hand)

    if player_strength > opp_strength:
        win_count += 1

win_prob = (win_count / iters) * 100

Заключение

В этой статье я хотел показать, чего можно добиться, используя только классические методы компьютерного зрения. Я понимаю, что текущее решение вряд ли будет использоваться в покерных играх, но в будущем я планирую добавить аналитику, которая может оказаться полезной.

Всем хорошего дня!

+1
0
+1
0
+1
0
+1
0
+1
0

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *