diff --git a/bot.py b/bot.py index 27fc925..032595f 100644 --- a/bot.py +++ b/bot.py @@ -1,18 +1,25 @@ -import tweepy, re, requests, random, traceback, time -from secrets import * -import tests as t +import random +import re +import requests +import time +import traceback + from bs4 import BeautifulSoup +import tweepy from tweepy.streaming import StreamListener -from myAPI import * + +from myAPI import myAPI #* +from secret import * +import tests as t class listener_tweeter(StreamListener): def _onconnect(self): - print 'Connected! Listening...' + print('Connected! Listening...') # override on_status to pass data from on_data method of tweepy's StreamListener def on_status(self, status): - print '{} said: "{}"'.format(status.user.screen_name, status.text) + print('{} said: "{}"'.format(status.user.screen_name, status.text)) # ignore retweets or tweets from self if status.retweeted or ( status.user.screen_name == 'ProfessorSet' ): @@ -25,18 +32,19 @@ def on_status(self, status): # without outlining specific sets try: tweet_url = re.search(r'https:.*\b', status.text).group(0) - with_sets_outlined = (False if re.search(r'sets or not', status.text) else True) + with_sets_outlined = not bool(re.search(r'sets or not', status.text)) # "sets or not" == no outlines text, img_str = solve_tweeted_set(tweet_url, with_sets_outlined=with_sets_outlined) #ignore tweets with no image - except AttributeError: + except AttributeError as e: + print(repr(e)) text = "You forgot a picture, {} #whoops #howembarrassing".format(status.user.name) n = random.randint(0, 140-len(text)) text += '!'*n response_text = '.@{} {}'.format(status.user.screen_name, text) - print response_text + print(response_text) try: response = api.retweet(id=status.id) @@ -45,22 +53,22 @@ def on_status(self, status): traceback.print_exc() # try to avoid posting tweets out of order (should be retweet, then response tweet) - for i in xrange(10): + for _ in range(10): if response: - send_tweet( response_text, response_id=status.id, media_str=img_str ) + send_tweet(response_text, response_id=status.id, media_str=img_str) return else: time.sleep(10) # but ultimately just send it anyway - send_tweet( response_text, response_id=status.id, media_str=img_str ) + send_tweet(response_text, response_id=status.id, media_str=img_str) def on_error(self, status_code): - print 'Error: {}'.format(status_code) + print('Error: {}'.format(status_code)) def on_exception(self, exception): - print 'Exception: {}'.format(exception) + print('Exception: {}'.format(exception)) return @@ -102,20 +110,23 @@ def solve_tweeted_set(tweet_url, with_sets_outlined=True): img_url = soup.find('meta', attrs={'property': 'og:image'})['content'] # find Sets - kwargs = {'path_is_url': True, 'pop_open': False} - kwargs['draw_contours'] = (True if with_sets_outlined else False) - kwargs['sets_or_no'] = (False if with_sets_outlined else True) + kwargs = { + 'path_is_url': True, + 'pop_open': False, + 'draw_contours': with_sets_outlined, + 'sets_or_no': not with_sets_outlined + } num_sets, initial_img_str = t.play_game(img_url, **kwargs) # send string with media_data (rather than media) tag because it is base64 encoded img_str = 'media_data={}'.format(initial_img_str) - text = ("Whoa! {} sets #craycray".format(num_sets) if num_sets \ - else "No sets #bummer #sadface") + text = ("Whoa! {} sets #craycray".format(num_sets) + if num_sets else "No sets #bummer #sadface") - img_str = (img_str if num_sets else None) + img_str = img_str if num_sets else None - return (text, img_str) + return text, img_str @@ -124,7 +135,8 @@ def send_tweet(text, response_id=None, media_str=None): text = text[:140] d_arguments = {'status': text} - if response_id: d_arguments['in_reply_to_status_id'] = response_id + if response_id: + d_arguments['in_reply_to_status_id'] = response_id if media_str: upload = api.media_upload(file=media_str, filename='filenames_are_for_dweebs') @@ -142,7 +154,8 @@ def send_tweet(text, response_id=None, media_str=None): def listen(): my_listener = listener_tweeter() stream = tweepy.Stream(auth, my_listener) - stream.userstream(_with='user') + #stream.userstream(_with='user') + stream.filter(follow=[BOT_ID]) diff --git a/constants.py b/constants.py index d2358a4..3eedec4 100644 --- a/constants.py +++ b/constants.py @@ -15,4 +15,4 @@ PROP_TEXTURE_STRIPED = 1 PROP_TEXTURE_EMPTY = 2 PROP_TEXTURE_SOLID = 3 -PROP_TEXTURE_MAP = ['_', 'STRIPED', 'EMPTY', 'SOLID'] \ No newline at end of file +PROP_TEXTURE_MAP = ['_', 'STRIPED', 'EMPTY', 'SOLID'] diff --git a/experiments.py b/experiments.py index 700bf47..397f442 100644 --- a/experiments.py +++ b/experiments.py @@ -1,10 +1,12 @@ -import cv2 -import cv2.cv as cv import sys + +import cv2 +#import cv2.cv as cv import numpy as np from util import show, destroy + CARD_SIZE_WIDTH = 64 CARD_SIZE_HEIGHT = 89 CARD_SIZE_RATIO = CARD_SIZE_WIDTH/CARD_SIZE_HEIGHT diff --git a/myAPI.py b/myAPI.py index c4c3be3..83973f2 100644 --- a/myAPI.py +++ b/myAPI.py @@ -1,6 +1,7 @@ +from tweepy import API #from tweepy.binder import bind_api from mybinder import my_bind_api -from tweepy import API + class myAPI(API): @staticmethod diff --git a/mybinder.py b/mybinder.py index edabf47..9eeb3b8 100644 --- a/mybinder.py +++ b/mybinder.py @@ -2,12 +2,13 @@ # Copyright 2009-2010 Joshua Roesslein # See LICENSE for details. -from __future__ import print_function +#from __future__ import print_function import time import re -from six.moves.urllib.parse import quote +#from six.moves.urllib.parse import quote +from urllib.parse import quote import requests import logging diff --git a/requirements.txt b/requirements.txt index a02a07d..330bf51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,128 @@ -cv2==1.0 -funcsigs==0.4 -matplotlib==1.4.3 -mock==1.3.0 -nose==1.3.7 -numpy==1.10.1 -pbr==1.8.1 -pyparsing==2.0.3 -python-dateutil==2.4.2 -pytz==2015.6 -requests==2.8.1 -six==1.10.0 -wheel==0.24.0 +# This file may be used to create an environment using: +# $ conda create --name --file +# platform: linux-64 +asn1crypto=0.24.0=py36_0 +atk=2.25.90=h05f3006_1 +backcall=0.1.0=py36_0 +beautifulsoup4=4.6.3=py36_0 +blas=1.0=mkl +blinker=1.4=py_1 +bzip2=1.0.6=h470a237_2 +ca-certificates=2018.03.07=0 +cairo=1.14.12=h276e583_5 +certifi=2018.11.29=py36_0 +cffi=1.11.5=py36he75722e_1 +chardet=3.0.4=py36_1 +cryptography=2.4.1=py36h1ba5d50_0 +cryptography-vectors=2.3.1=py36_1000 +cycler=0.10.0=py36_0 +dbus=1.13.2=h714fa37_1 +decorator=4.3.0=py36_0 +expat=2.2.5=hfc679d8_2 +ffmpeg=4.0.2=ha0c5888_2 +fontconfig=2.13.1=h65d0f4c_0 +freetype=2.9.1=h6debe1e_4 +funcsigs=1.0.2=py36_0 +gdk-pixbuf=2.36.12=h4ab6910_1 +gettext=0.19.8.1=h5e8e0c9_1 +giflib=5.1.4=h470a237_1 +glib=2.56.2=h464dc38_1 +gmp=6.1.2=hfc679d8_0 +gnutls=3.5.19=h2a4e5f8_1 +gobject-introspection=1.56.1=py36haaf12d0_1 +graphite2=1.3.12=hfc679d8_1 +gst-plugins-base=1.14.0=hbbd80ab_1 +gstreamer=1.14.0=hb453b48_1 +gtk2=2.24.31=hde048ef_0 +harfbuzz=1.9.0=h04dbb29_1 +hdf5=1.10.2=hba1933b_1 +icu=58.2=hfc679d8_0 +idna=2.7=py36_0 +intel-openmp=2019.1=144 +ipython=7.2.0=py36h39e3cac_0 +ipython_genutils=0.2.0=py36_0 +jasper=1.900.1=hff1ad4c_5 +jedi=0.13.1=py36_0 +jpeg=9c=h470a237_1 +kiwisolver=1.0.1=py36hf484d3e_0 +libffi=3.2.1=hfc679d8_5 +libgcc=7.2.0=h69d50b8_2 +libgcc-ng=8.2.0=hdf63c60_1 +libgfortran=3.0.0=1 +libgfortran-ng=7.3.0=hdf63c60_0 +libiconv=1.15=h470a237_3 +libpng=1.6.36=ha92aebf_0 +libstdcxx-ng=8.2.0=hdf63c60_1 +libtiff=4.0.10=he6b73bb_0 +libuuid=2.32.1=h470a237_2 +libwebp=0.5.2=7 +libxcb=1.13=h470a237_2 +libxml2=2.9.8=h422b904_5 +libxslt=1.1.32=h1312cb7_0 +lxml=4.2.5=py36hefd8a0e_0 +matplotlib=2.2.2=py36hb69df0a_2 +mkl=2018.0.3=1 +mkl_fft=1.0.6=py36h7dd41cf_0 +mkl_random=1.0.1=py36h4414c95_1 +mock=2.0.0=py36_0 +ncurses=6.1=hfc679d8_1 +nettle=3.3=0 +nose=1.3.7=py36_2 +numpy=1.15.4=py36h1d66e8a_0 +numpy-base=1.15.4=py36h81de0dd_0 +oauthlib=2.1.0=py_0 +olefile=0.46=py36_0 +openblas=0.2.20=8 +openh264=1.8.0=hd28b015_0 +openssl=1.1.1a=h7b6447c_0 +pango=1.40.14=he752989_2 +parso=0.3.1=py36_0 +pbr=5.1.1=py36_0 +pcre=8.42=h439df22_0 +pexpect=4.6.0=py36_0 +pickleshare=0.7.5=py36_0 +pillow=5.3.0=py36h34e0f95_0 +pip=18.1=py36_1000 +pixman=0.34.0=h470a237_3 +prompt_toolkit=2.0.7=py36_0 +pthread-stubs=0.4=h470a237_1 +ptyprocess=0.6.0=py36_0 +pycparser=2.19=py36_0 +pygments=2.2.0=py36_0 +pyjwt=1.6.4=py_0 +pyopenssl=18.0.0=py36_0 +pyparsing=2.3.0=py36_0 +pyqt=5.6.0=py36_2 +pysocks=1.6.8=py36_0 +python=3.6.7=h0371630_0 +python-dateutil=2.7.5=py36_0 +pytz=2018.7=py36_0 +qt=5.6.3=h8bf5577_3 +readline=7.0=haf1bffa_1 +requests=2.20.1=py36_0 +requests-oauthlib=1.0.0=py_1 +setuptools=40.6.2=py36_0 +sip=4.19.8=py36hf484d3e_0 +six=1.11.0=py36_1 +sqlite=3.26.0=hb1c47c0_0 +tk=8.6.9=ha92aebf_0 +tornado=5.1.1=py36h7b6447c_0 +traitlets=4.3.2=py36_0 +urllib3=1.23=py36_0 +wcwidth=0.1.7=py36_0 +wheel=0.32.3=py36_0 +x264=1!152.20180717=h470a237_1 +xorg-kbproto=1.0.7=h470a237_2 +xorg-libice=1.0.9=h470a237_4 +xorg-libsm=1.2.3=h8c8a85c_0 +xorg-libx11=1.6.6=h470a237_0 +xorg-libxau=1.0.8=h470a237_6 +xorg-libxdmcp=1.1.2=h470a237_7 +xorg-libxext=1.3.3=h470a237_4 +xorg-libxrender=0.9.10=h470a237_2 +xorg-libxt=1.1.5=h470a237_2 +xorg-renderproto=0.11.1=h470a237_2 +xorg-xextproto=7.3.0=h470a237_2 +xorg-xproto=7.0.31=h470a237_7 +xz=5.2.4=h470a237_1 +zlib=1.2.11=h470a237_3 diff --git a/secret.py b/secret.py new file mode 100644 index 0000000..abeb7a5 --- /dev/null +++ b/secret.py @@ -0,0 +1,6 @@ +C_KEY = "uULgOqxGCfDmG68grUFuH3JLa" +C_SECRET = "OY6y8IRXvt2qACY4Riq2OOB79Ez8LElINSVkC63dpNG9qigsz3" +A_TOKEN = "4054224353-suw7Yufiq0ZCBqoXE6jBIgvX2uHGSfNdP5Bu2pZ" +A_TOKEN_SECRET = "oByn1rEaCuyKUoJ7x8Vbf1iDJOUk1brsEkh82fzoHeRgT" + +BOT_ID = "4054224353" diff --git a/set_solver.py b/set_solver.py index a672c1c..f990148 100644 --- a/set_solver.py +++ b/set_solver.py @@ -1,13 +1,15 @@ -import cv2, sys, os -import cv2.cv as cv +import os import sys + +import cv2 +#from cv2 import cv import numpy as np + import util as util -import os import code import set_constants as sc -reload(util) +#reload(util) def resize_image(img, new_width=600): """Given cv2 image object and maximum dimension, returns resized image such that height or width (whichever is larger) == max dimension""" @@ -35,9 +37,9 @@ def get_card_properties(cards, training_set=None): def pretty_print_properties(properties): for p in properties: num, color, shape, texture = p - print '%d %s %s %s' % (num, sc.PROP_COLOR_MAP[color], + print('%d %s %s %s' % (num, sc.PROP_COLOR_MAP[color], sc.PROP_SHAPE_MAP[shape], - sc.PROP_TEXTURE_MAP[texture]) + sc.PROP_TEXTURE_MAP[texture])) def detect_cards(img, draw_rects=False, return_contours=False): if img is None: @@ -52,20 +54,21 @@ def detect_cards(img, draw_rects=False, return_contours=False): transformed_cards = transform_cards(img, contours, num_cards, draw_rects=draw_rects) if return_contours: - return (contours, transformed_cards) + return contours, transformed_cards else: - return (transformed_cards) + return transformed_cards def transform_cards(img, contours, num, draw_rects=False): cards = [] - for i in xrange(num): + for i in range(num): if i > len(contours) - 1: continue card = contours[i] # get bounding rectangle rect = cv2.minAreaRect(card) - r = cv.BoxPoints(rect) + #r = cv.BoxPoints(rect) + r = cv2.boxPoints(rect) # convert to ints r = [(int(x), int(y)) for x, y in r] @@ -76,7 +79,7 @@ def transform_cards(img, contours, num, draw_rects=False): try: transformed = transform_card(card, img) except: - # print 'Error processing card!! :o' + # print('Error processing card!! :o') continue if transformed is not None: @@ -134,7 +137,7 @@ def get_approx_poly(card, do_rectify=True, image=None): def find_contours(bin_img, num=-1, return_area=False): - contours, hierarchy = cv2.findContours( + _, contours, hierarchy = cv2.findContours( bin_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) contours = sorted(contours, key=cv2.contourArea, reverse=True) @@ -164,11 +167,13 @@ def get_card_color(card): if card is None: return None - blue = [pix[0] for row in card for pix in row] - green = [pix[1] for row in card for pix in row] - red = [pix[2] for row in card for pix in row] + #blue = [pix[0] for row in card for pix in row] + #green = [pix[1] for row in card for pix in row] + #red = [pix[2] for row in card for pix in row] + #blue, green, red = zip(*(pix for row in card for pix in row)) - bgr = (min(blue), min(green), min(red)) + bgr = [min(_) for _ in zip(*(pix for row in card for pix in row))] + #bgr = (min(blue), min(green), min(red)) b, g, r = bgr # if mostly green if max(bgr) == g: @@ -341,18 +346,18 @@ def get_binary_from_hsv(card): hsv = cv2.cvtColor(card, cv2.COLOR_BGR2HSV) # separate hue, saturation, and value into three images - hue, sat, val = [ np.array( [[col[i] for col in row] for row in hsv] ) \ - for i in xrange(3) ] + hue, sat, val = [np.array( [[col[i] for col in row] for row in hsv] ) + for i in range(3)] # get binary representation of saturation image # higher threshold = less white - _, bin_sat = cv2.threshold(np.array(sat), thresh=55, maxval=255, \ + _, bin_sat = cv2.threshold(np.array(sat), thresh=55, maxval=255, type=cv2.THRESH_BINARY) #bin_sat = cv2.GaussianBlur(bin_sat, ksize=(5, 5), sigmaX=0) # get binary representation of value image # higher threshold = more white - _, bin_val = cv2.threshold(np.array(val), thresh=140, maxval=255, \ + _, bin_val = cv2.threshold(np.array(val), thresh=140, maxval=255, type=cv2.THRESH_BINARY_INV) bin_sat_val = cv2.bitwise_or(bin_sat, bin_val) @@ -404,13 +409,13 @@ def get_contour_info(bin_img, remove_dups=True, discard=True, num=4): contour_boxes.pop(i) contour_centers.pop(i) else: - i = i+1 + i += 1 return contours, contour_areas, contour_boxes, contour_centers def get_color_from_hue(hue): # get histogram of hue values - hist,_ = np.histogram(hue, 15, (0, 255)) + hist, _ = np.histogram(hue, 15, (0, 255)) if hist[3]+hist[4] > 1200: return sc.PROP_COLOR_GREEN @@ -441,7 +446,8 @@ def get_texture_from_hue(hue, contour_box): hue[0:20, card_w-20:card_w], hue[card_h-20:card_h, 0:20]]) # get a 20x20 square from the center of the shape, average values - hue_center = np.mean(hue[y+h/2-10:y+h/2+10, x+w/2-10:x+w/2+10]) + hue_center = np.mean(hue[int(y+h/2-10):int(y+h/2+10), + int(x+w/2-10):int(x+w/2+10)]) # guess texture based on ratio of inside to outside hues hue_ratio = max(hue_bg, hue_center) / min(hue_bg, hue_center) @@ -516,4 +522,4 @@ def get_cards_properties(cards): if None not in p: properties.append(p) - return properties \ No newline at end of file + return properties diff --git a/set_test.py b/set_test.py index 895e026..484ccc8 100644 --- a/set_test.py +++ b/set_test.py @@ -1,10 +1,11 @@ import random import itertools + class game: def __init__(self, cards=None): if not cards: - self.cards = [self.random_card() for _ in xrange(12)] + self.cards = [self.random_card() for _ in range(12)] else: self.cards = cards @@ -13,7 +14,7 @@ def __repr__(self): def random_card(self): """Return random tuple (color, shape, number, texture)""" - return tuple( random.choice([1,2,3]) for _ in xrange(4) ) + return tuple( random.choice([1,2,3]) for _ in range(4) ) def is_set(self, x, y, z): """Returns true if card trio is a set at every position based on sum mod 3, which is only 0 IFF all same or all different""" @@ -27,9 +28,10 @@ def compare_all(self, idx=False): if self.is_set(x[1], y[1], z[1]): cards_idx = (x[0], y[0], z[0]) if idx: - sets.append( map(lambda x: (x, self.cards[x]), cards_idx) ) + sets.append( [(i, self.cards[i]) for i in cards_idx] ) else: - sets.append( map(lambda x: self.cards[x], cards_idx) ) + sets.append( [self.cards[i] for i in cards_idx] ) + return sets def play(self, idx=False): @@ -39,4 +41,5 @@ def play(self, idx=False): return False def test(n): - return '{}%'.format(sum( game().play() for _ in xrange(n) )/float(n) * 100.0) + #return '{}%'.format( sum( game().play() for _ in range(n) ) / n * 100.0) + return '{}%'.format( sum( bool(game().play()) for _ in range(n) ) / n * 100.0) diff --git a/set_video.py b/set_video.py index 7f526ae..6d3435e 100644 --- a/set_video.py +++ b/set_video.py @@ -1,7 +1,10 @@ -import set_solver as ss -import cv2, util +import cv2 import numpy as np +import set_solver as ss +import util + + # open video feed # solve for every frame @@ -70,4 +73,4 @@ def print_properties(props): ss.pretty_print_properties(props) print('----') -main() \ No newline at end of file +main() diff --git a/tests.py b/tests.py index b02a6f4..a679a4b 100644 --- a/tests.py +++ b/tests.py @@ -1,13 +1,18 @@ -import cv2, util, requests, os -import set_solver as s -import set_constants as sc -import cv2.cv as cv -from set_test import game +import base64 from collections import Counter +from io import BytesIO +import os +import requests + +import cv2 import numpy as np -import base64, StringIO from PIL import Image +import set_solver as s +import set_constants as sc +from set_test import game +import util + def test(): # 3 cards on flat table @@ -27,13 +32,16 @@ def test(): res3 = s.detect_cards(cards_3) assert res3 is not None and len(res3) == 3 - for i in range(len(res5)): - c = res5[i] + try: + os.mkdir('images/cards') + except(FileExistsError): + pass + + for i, c in enumerate(res5): # util.show(c, 'card') cv2.imwrite('images/cards/card-5-%d.jpg' % i, c) - for i in range(len(res3)): - c = res3[i] + for i, c in enumerate(res3): # util.show(c, 'card') cv2.imwrite('images/cards/card-3-%d.jpg' % i, c) @@ -41,22 +49,26 @@ def test(): for link in os.listdir('images/cards'): img = cv2.imread('images/cards/%s' % link) test_props(img) - print 'tests pass' + print('tests pass') -def test_props(img): +def test_props(img, pop_open=False, out='images/test_out.jpg'): color = sc.PROP_COLOR_MAP[s.get_card_color(img)] shape = sc.PROP_SHAPE_MAP[s.get_card_shape(img, s.get_training_set())] num = s.get_card_number(img) texture = sc.PROP_TEXTURE_MAP[s.get_card_texture(img)] - print '%d %s %s %s' % (num, color, shape, texture) + print('%d %s %s %s' % (num, color, shape, texture)) print('---') - util.show(img) + if pop_open: + util.show(img) + else: + cv2.imwrite(out, img) + -def main(): +def main(pop_open=False, out='images/test_out.jpg'): # 3 of the 12 set that's bad cards_3_bad = cv2.imread('images/set-3-bad.jpg') thresh_3bad = s.get_binary(cards_3_bad) @@ -64,16 +76,21 @@ def main(): assert res3bad is not None and len(res3bad) == 3 # 12 cards - cards_12 = cv2.imread('images/set-12-random-2sets-sm.jpg') + #cards_12 = cv2.imread('images/set-12-random-2sets-sm.jpg') + cards_12 = cv2.imread('images/set-12-random-2sets.jpg') thresh_12bad = s.get_binary(cards_12) res12bad = s.detect_cards(cards_12, draw_rects=False) - util.show(cards_12) + + if pop_open: + util.show(cards_12) + else: + cv2.imwrite(out, cards_12) + # Subset of 3, with the 1 problem card cards = res12bad - for i in range(len(cards)): - card = cards[i] + for i, card in enumerate(cards): # test_props(card) cv2.imwrite('images/cards/card-12-%02d.jpg' % i, card) @@ -84,39 +101,41 @@ def main(): sets = g.play(True) if sets: - print '\nFound sets:' + print('\nFound sets:') for st in sets: just_props = [e for i, e in st] - print just_props + print(just_props) s.pretty_print_properties(just_props) print('---') else: - print 'no sets :(' + print('no sets :(') - print 'tests pass' + print('tests pass') -def play_game(path_in, path_is_url=False, printall=False, \ - draw_contours=True, resize_contours=True, \ - draw_rects=False, sets_or_no=False, \ - pop_open=True): - """Takes in an image path (to local file or onlin), finds all sets, and pretty prints them to screen. +def play_game(path_in, path_is_url=False, printall=False, + draw_contours=True, resize_contours=True, + draw_rects=False, sets_or_no=False, + pop_open=True, save_as=None): + """ + Takes in an image path (to local file or online), finds all sets, and pretty prints them to screen. if printall - prints the identities of all cards in the image if draw_contours - outlines the cards belonging to each set if resize_contours - enlarges contours for cards belonging to multiple sets to avoid overlay if draw_rects - draws box rects around cards belonging to each set - if sets_or_no - outlines the image in green or red, depending on whether there are any sets present""" + if sets_or_no - outlines the image in green or red, depending on whether there are any sets present + if save_as - also saves to file + """ if path_is_url: # parse image string directly into numpy array img_str = requests.get(path_in).content nparr = np.fromstring(img_str, np.uint8) - img = cv2.imdecode(nparr, cv2.CV_LOAD_IMAGE_COLOR) - + img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) else: img = cv2.imread(path_in) img = s.resize_image(img, 600) - util.show(img) + if pop_open: util.show(img) contours, detected = s.detect_cards(img, draw_rects=False, return_contours=True) props = s.get_cards_properties(detected) @@ -145,7 +164,7 @@ def play_game(path_in, path_is_url=False, printall=False, \ color = COLORS[i] st_indices, st_props = zip(*st) s.pretty_print_properties(st_props) - print ('---') + print('---') if draw_contours or draw_rects: winning_contours = [ contours[c] for c in st_indices ] @@ -169,11 +188,11 @@ def play_game(path_in, path_is_url=False, printall=False, \ winning_rects = [ cv2.minAreaRect(c) for c in winning_contours ] for rect in winning_rects: # convert to ints - r = [ (int(x), int(y)) for x,y in cv.BoxPoints(rect) ] + r = [ (int(x), int(y)) for x,y in cv2.boxPoints(rect) ] cv2.rectangle(img, r[0], r[2], color) else: - print 'no sets :(' + print('no sets :(') if sets_or_no: height, width, _ = img.shape @@ -183,28 +202,30 @@ def play_game(path_in, path_is_url=False, printall=False, \ # indices 0 or 1 correspond to bool for if no sets (BGR for red) or sets (green) BORDER_COLORS = [ (19,19,214), (94,214,19) ] - img_outlined = cv2.copyMakeBorder(img, border_h, border_h, \ - border_w, border_w, \ + img_outlined = cv2.copyMakeBorder(img, + border_h, border_h, + border_w, border_w, cv2.BORDER_CONSTANT, value = BORDER_COLORS[bool(sets)]) - processed_img = (img_outlined if sets_or_no else img) + processed_img = img_outlined if sets_or_no else img final_img = processed_img #s.resize_image(processed_img, 800) if pop_open: util.show(final_img) - num_sets = ( len(sets) if sets else 0 ) + num_sets = len(sets) if sets else 0 # convert image array to string representing JPEG of image (in RGB) image = Image.fromarray( cv2.cvtColor(processed_img, cv2.COLOR_BGR2RGB) ) - output = StringIO.StringIO() + output = BytesIO() image.save(output, format='JPEG') mystr = output.getvalue() output.close() # don't write image to file, dude....because we are badass Tweepy hackers - #cv2.imwrite('tmp.jpeg', final_img) + if save_as is not None: + cv2.imwrite(save_as, final_img) # encode image string to base64 and safe-encode it for Twitter upload request final = requests.utils.quote(base64.b64encode(mystr), safe='') @@ -216,6 +237,4 @@ def play_game(path_in, path_is_url=False, printall=False, \ # length is 123932 # size is 89867.1532847 bytes - return (num_sets, final) - - + return num_sets, final diff --git a/util.py b/util.py index b4a96f8..e2d3bf1 100644 --- a/util.py +++ b/util.py @@ -1,6 +1,8 @@ +import random + import cv2 import numpy as np -import random + def show(img, window_name='main'): # destroy existing window @@ -40,7 +42,7 @@ def rectify(h): # draw contour on empty image def draw_contour(c, i, h=500, w=300): dest = np.zeros((h,w), np.float32) - cv2.drawContours(dest, c, i, 255, cv2.cv.CV_FILLED) + cv2.drawContours(dest, c, i, 255, cv2.FILLED) return dest def resize(src, shape): @@ -86,7 +88,7 @@ def hsv2bgr(hsv): hue = random.random() l_hues = [hue] - for i in xrange(n-1): + for i in range(n-1): # generate evenly distributed hues by random walk using the golden ratio! # (mod 1, to stay within hue space) hue += GOLDEN_RATIO_INVERSE