├── .gitignore ├── 001_intro ├── albion_cabbage.jpg ├── albion_farm.jpg ├── main.py ├── result.jpg └── result_CCOEFF_NORMED.jpg ├── 002_match_multiple ├── albion_cabbage.jpg ├── albion_copper.jpg ├── albion_copper_needle.jpg ├── albion_farm.jpg ├── main.py └── result.jpg ├── 003_group_rectangles ├── albion_cabbage.jpg ├── albion_farm.jpg ├── albion_turnip.jpg ├── main.py └── result_click_point.jpg ├── 004_window_capture ├── main.py └── windowcapture.py ├── 005_real_time ├── albion_limestone.jpg ├── gunsnbottle.jpg ├── main.py ├── vision.py └── windowcapture.py ├── 006_hsv_thresholding ├── albion_limestone.jpg ├── albion_limestone_processed.jpg ├── hsvfilter.py ├── main.py ├── vision.py └── windowcapture.py ├── 007_canny_edge ├── albion_limestone.jpg ├── albion_limestone_edges.jpg ├── albion_limestone_processed.jpg ├── edgefilter.py ├── hsvfilter.py ├── main.py ├── vision.py └── windowcapture.py ├── 008_cascade_classifier ├── cascadeutils.py ├── edgefilter.py ├── hsvfilter.py ├── limestone_model_final.xml ├── main.py ├── neg.txt ├── negative │ ├── 1596724705.5517466.jpg │ ├── 1596724746.4305139.jpg │ ├── 1596724758.379512.jpg │ ├── 1596724769.5195115.jpg │ ├── 1596724785.5650678.jpg │ ├── 1596724793.0220716.jpg │ ├── 1596724805.2095428.jpg │ ├── 1596724819.1929052.jpg │ ├── 1596724834.3249273.jpg │ ├── 1596724847.025956.jpg │ ├── 1596724898.553667.jpg │ ├── 1596724967.346105.jpg │ ├── 1596725038.330387.jpg │ ├── 1596725080.9118962.jpg │ ├── 1596725170.84625.jpg │ ├── 1596725187.592096.jpg │ ├── 1596725220.033521.jpg │ ├── 1596725264.334212.jpg │ ├── 1596725284.118124.jpg │ ├── 1596725349.903286.jpg │ ├── 1596725431.8746161.jpg │ ├── 1596725461.0213678.jpg │ ├── 1596725515.069354.jpg │ ├── 1596725582.226287.jpg │ ├── 1596725745.2315128.jpg │ ├── 1596725756.951403.jpg │ ├── 1596725765.261232.jpg │ ├── 1596725775.2572083.jpg │ ├── 1596725821.9031878.jpg │ ├── 1596725831.8041875.jpg │ ├── 1596725920.4239123.jpg │ ├── 1596725939.2339938.jpg │ ├── 1596725954.4924223.jpg │ ├── 1596726002.0213919.jpg │ ├── 1596726049.6788344.jpg │ ├── 1596726062.6242409.jpg │ ├── 1596726097.9022322.jpg │ ├── 1596726142.49426.jpg │ ├── 1596726155.9890828.jpg │ ├── 1596726182.5506525.jpg │ ├── 1596726217.5782723.jpg │ ├── 1596726234.8375776.jpg │ ├── 1596726246.9830372.jpg │ ├── 1596726292.729684.jpg │ ├── 1596726301.8822803.jpg │ ├── 1596726313.814556.jpg │ ├── 1596726332.8304229.jpg │ ├── 1596726345.7047927.jpg │ ├── 1596726465.2339375.jpg │ ├── 1596726487.8134801.jpg │ ├── 1596726513.185823.jpg │ ├── 1596726640.6113465.jpg │ ├── 1596726674.816247.jpg │ ├── 1596726689.225606.jpg │ ├── 1596726697.8552132.jpg │ ├── 1596726708.121546.jpg │ ├── 1596726717.2575495.jpg │ ├── 1596726724.903866.jpg │ ├── 1596726738.219499.jpg │ ├── 1596726750.0375574.jpg │ ├── 1596726757.5366132.jpg │ ├── 1596726822.4534585.jpg │ ├── 1596726831.802462.jpg │ ├── 1596726846.4050379.jpg │ ├── 1596726875.157068.jpg │ ├── 1596726888.3905616.jpg │ ├── 1596726918.442663.jpg │ ├── 1596726939.1568878.jpg │ ├── 1596726959.2844813.jpg │ ├── 1596726975.4137285.jpg │ ├── 1596727112.1766517.jpg │ ├── 1596727124.6547341.jpg │ ├── 1596727149.089741.jpg │ ├── 1596727157.397738.jpg │ ├── 1596727168.9037406.jpg │ ├── 1596727315.7115467.jpg │ ├── 1596727333.9426105.jpg │ ├── 1596727355.8319664.jpg │ ├── 1596727372.243553.jpg │ ├── 1596727384.199091.jpg │ ├── 1596727392.9071019.jpg │ ├── 1596727402.9000127.jpg │ ├── 1596727416.8854368.jpg │ ├── 1596727438.8985887.jpg │ ├── 1596727452.5146.jpg │ ├── 1596727470.7761557.jpg │ ├── 1596727485.4515324.jpg │ ├── 1596727494.702687.jpg │ ├── 1596727506.0782263.jpg │ ├── 1596727510.119744.jpg │ ├── 1596727522.5037456.jpg │ ├── 1596727529.4377453.jpg │ ├── 1596727538.2637434.jpg │ ├── 1596727547.951745.jpg │ ├── 1596727558.6709976.jpg │ ├── 1596727568.9909968.jpg │ ├── 1596727581.266752.jpg │ ├── 1596727595.2124581.jpg │ ├── 1596727609.8064606.jpg │ ├── 1596727646.9046378.jpg │ ├── 1596727657.9977558.jpg │ ├── 1596727863.9691815.jpg │ └── 1597344765.5637424.jpg ├── pos.txt ├── pos.vec ├── positive │ ├── 1596724594.7233658.jpg │ ├── 1596724628.6711628.jpg │ ├── 1596724646.3438368.jpg │ ├── 1596724654.9572363.jpg │ ├── 1596724663.8320026.jpg │ ├── 1596724674.324035.jpg │ ├── 1596724688.3025408.jpg │ ├── 1596724716.7686796.jpg │ ├── 1596724725.0338614.jpg │ ├── 1596724731.2288644.jpg │ ├── 1596724862.2278488.jpg │ ├── 1596724877.1428533.jpg │ ├── 1596724932.9526978.jpg │ ├── 1596724981.3780077.jpg │ ├── 1596725001.129405.jpg │ ├── 1596725013.2133152.jpg │ ├── 1596725024.112249.jpg │ ├── 1596725056.3050916.jpg │ ├── 1596725091.287897.jpg │ ├── 1596725112.003226.jpg │ ├── 1596725119.7605214.jpg │ ├── 1596725133.894812.jpg │ ├── 1596725145.224427.jpg │ ├── 1596725160.9995346.jpg │ ├── 1596725207.3700407.jpg │ ├── 1596725236.9265978.jpg │ ├── 1596725319.6801374.jpg │ ├── 1596725331.6399002.jpg │ ├── 1596725365.0860987.jpg │ ├── 1596725448.8122423.jpg │ ├── 1596725482.437604.jpg │ ├── 1596725504.2595575.jpg │ ├── 1596725531.0743005.jpg │ ├── 1596725547.959211.jpg │ ├── 1596725557.5062091.jpg │ ├── 1596725570.5192885.jpg │ ├── 1596725594.7959447.jpg │ ├── 1596725612.3280354.jpg │ ├── 1596725623.4045992.jpg │ ├── 1596725636.845524.jpg │ ├── 1596725656.2454305.jpg │ ├── 1596725673.0234246.jpg │ ├── 1596725683.22975.jpg │ ├── 1596725691.7093291.jpg │ ├── 1596725703.587616.jpg │ ├── 1596725715.009877.jpg │ ├── 1596725732.5384405.jpg │ ├── 1596725787.8061755.jpg │ ├── 1596725809.2862072.jpg │ ├── 1596725850.87147.jpg │ ├── 1596725863.4828084.jpg │ ├── 1596725874.1538086.jpg │ ├── 1596725886.3860302.jpg │ ├── 1596725967.164775.jpg │ ├── 1596725983.5232484.jpg │ ├── 1596726015.8447444.jpg │ ├── 1596726023.5497692.jpg │ ├── 1596726034.82377.jpg │ ├── 1596726085.3942282.jpg │ ├── 1596726109.9420922.jpg │ ├── 1596726116.478095.jpg │ ├── 1596726192.4596026.jpg │ ├── 1596726416.5257359.jpg │ ├── 1596726430.6089704.jpg │ ├── 1596726442.6422012.jpg │ ├── 1596726453.5657032.jpg │ ├── 1596726474.629971.jpg │ ├── 1596726526.9982874.jpg │ ├── 1596726536.5520952.jpg │ ├── 1596726552.1565392.jpg │ ├── 1596726566.051003.jpg │ ├── 1596726579.2008128.jpg │ ├── 1596726626.1602285.jpg │ ├── 1596726654.1444535.jpg │ ├── 1596726662.3032484.jpg │ ├── 1596726774.8108573.jpg │ ├── 1596726788.6514382.jpg │ ├── 1596726803.447783.jpg │ ├── 1596726860.5150664.jpg │ ├── 1596726901.542625.jpg │ ├── 1596726926.4286637.jpg │ ├── 1596726947.2468882.jpg │ ├── 1596726987.308508.jpg │ ├── 1596727004.7786236.jpg │ ├── 1596727013.9736161.jpg │ ├── 1596727037.3830843.jpg │ ├── 1596727055.9510057.jpg │ ├── 1596727069.049649.jpg │ ├── 1596727079.762559.jpg │ ├── 1596727099.340134.jpg │ ├── 1596727137.8729897.jpg │ ├── 1596727178.5962224.jpg │ ├── 1596727189.9316707.jpg │ ├── 1596727203.982804.jpg │ ├── 1596727229.6601653.jpg │ ├── 1596727241.441173.jpg │ ├── 1596727249.654685.jpg │ ├── 1596727259.3076847.jpg │ ├── 1596727272.6673214.jpg │ ├── 1596727293.8035266.jpg │ ├── 1596727755.6122937.jpg │ ├── 1596727802.904577.jpg │ ├── 1596727822.1659546.jpg │ ├── 1596727837.7473912.jpg │ ├── 1596727848.3987978.jpg │ └── 1597344669.960422.jpg ├── vision.py └── windowcapture.py ├── 009_bot ├── bot.py ├── detection.py ├── limestone_model_final.xml ├── limestone_tooltip.jpg ├── main.py ├── vision.py └── windowcapture.py ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /venv 2 | /.vscode 3 | __pycache__ 4 | /pydirectinput.egg-info 5 | /build 6 | /dist -------------------------------------------------------------------------------- /001_intro/albion_cabbage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/001_intro/albion_cabbage.jpg -------------------------------------------------------------------------------- /001_intro/albion_farm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/001_intro/albion_farm.jpg -------------------------------------------------------------------------------- /001_intro/main.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | import os 4 | 5 | 6 | # Change the working directory to the folder this script is in. 7 | # Doing this because I'll be putting the files from each video in their own folder on GitHub 8 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 9 | 10 | # Can use IMREAD flags to do different pre-processing of image files, 11 | # like making them grayscale or reducing the size. 12 | # https://docs.opencv.org/4.2.0/d4/da8/group__imgcodecs.html 13 | haystack_img = cv.imread('albion_farm.jpg', cv.IMREAD_UNCHANGED) 14 | needle_img = cv.imread('albion_cabbage.jpg', cv.IMREAD_UNCHANGED) 15 | 16 | # There are 6 comparison methods to choose from: 17 | # TM_CCOEFF, TM_CCOEFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_SQDIFF, TM_SQDIFF_NORMED 18 | # You can see the differences at a glance here: 19 | # https://docs.opencv.org/master/d4/dc6/tutorial_py_template_matching.html 20 | # Note that the values are inverted for TM_SQDIFF and TM_SQDIFF_NORMED 21 | result = cv.matchTemplate(haystack_img, needle_img, cv.TM_CCOEFF_NORMED) 22 | 23 | # You can view the result of matchTemplate() like this: 24 | #cv.imshow('Result', result) 25 | #cv.waitKey() 26 | # If you want to save this result to a file, you'll need to normalize the result array 27 | # from 0..1 to 0..255, see: 28 | # https://stackoverflow.com/questions/35719480/opencv-black-image-after-matchtemplate 29 | #cv.imwrite('result_CCOEFF_NORMED.jpg', result * 255) 30 | 31 | # Get the best match position from the match result. 32 | min_val, max_val, min_loc, max_loc = cv.minMaxLoc(result) 33 | # The max location will contain the upper left corner pixel position for the area 34 | # that most closely matches our needle image. The max value gives an indication 35 | # of how similar that find is to the original needle, where 1 is perfect and -1 36 | # is exact opposite. 37 | print('Best match top left position: %s' % str(max_loc)) 38 | print('Best match confidence: %s' % max_val) 39 | 40 | # If the best match value is greater than 0.8, we'll trust that we found a match 41 | threshold = 0.8 42 | if max_val >= threshold: 43 | print('Found needle.') 44 | 45 | # Get the size of the needle image. With OpenCV images, you can get the dimensions 46 | # via the shape property. It returns a tuple of the number of rows, columns, and 47 | # channels (if the image is color): 48 | needle_w = needle_img.shape[1] 49 | needle_h = needle_img.shape[0] 50 | 51 | # Calculate the bottom right corner of the rectangle to draw 52 | top_left = max_loc 53 | bottom_right = (top_left[0] + needle_w, top_left[1] + needle_h) 54 | 55 | # Draw a rectangle on our screenshot to highlight where we found the needle. 56 | # The line color can be set as an RGB tuple 57 | cv.rectangle(haystack_img, top_left, bottom_right, 58 | color=(0, 255, 0), thickness=2, lineType=cv.LINE_4) 59 | 60 | # You can view the processed screenshot like this: 61 | #cv.imshow('Result', haystack_img) 62 | #cv.waitKey() 63 | # Or you can save the results to a file. 64 | # imwrite() will smartly format our output image based on the extension we give it 65 | # https://docs.opencv.org/3.4/d4/da8/group__imgcodecs.html#gabbc7ef1aa2edfaa87772f1202d67e0ce 66 | cv.imwrite('result.jpg', haystack_img) 67 | 68 | else: 69 | print('Needle not found.') 70 | -------------------------------------------------------------------------------- /001_intro/result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/001_intro/result.jpg -------------------------------------------------------------------------------- /001_intro/result_CCOEFF_NORMED.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/001_intro/result_CCOEFF_NORMED.jpg -------------------------------------------------------------------------------- /002_match_multiple/albion_cabbage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/002_match_multiple/albion_cabbage.jpg -------------------------------------------------------------------------------- /002_match_multiple/albion_copper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/002_match_multiple/albion_copper.jpg -------------------------------------------------------------------------------- /002_match_multiple/albion_copper_needle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/002_match_multiple/albion_copper_needle.jpg -------------------------------------------------------------------------------- /002_match_multiple/albion_farm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/002_match_multiple/albion_farm.jpg -------------------------------------------------------------------------------- /002_match_multiple/main.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | import os 4 | 5 | 6 | # Change the working directory to the folder this script is in. 7 | # Doing this because I'll be putting the files from each video in their own folder on GitHub 8 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 9 | 10 | # Can use IMREAD flags to do different pre-processing of image files, 11 | # like making them grayscale or reducing the size. 12 | # https://docs.opencv.org/4.2.0/d4/da8/group__imgcodecs.html 13 | haystack_img = cv.imread('albion_farm.jpg', cv.IMREAD_UNCHANGED) 14 | needle_img = cv.imread('albion_cabbage.jpg', cv.IMREAD_UNCHANGED) 15 | 16 | # There are 6 comparison methods to choose from: 17 | # TM_CCOEFF, TM_CCOEFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_SQDIFF, TM_SQDIFF_NORMED 18 | # You can see the differences at a glance here: 19 | # https://docs.opencv.org/master/d4/dc6/tutorial_py_template_matching.html 20 | # Note that the values are inverted for TM_SQDIFF and TM_SQDIFF_NORMED 21 | result = cv.matchTemplate(haystack_img, needle_img, cv.TM_SQDIFF_NORMED) 22 | 23 | # I've inverted the threshold and where comparison to work with TM_SQDIFF_NORMED 24 | threshold = 0.17 25 | # The np.where() return value will look like this: 26 | # (array([482, 483, 483, 483, 484], dtype=int32), array([514, 513, 514, 515, 514], dtype=int32)) 27 | locations = np.where(result <= threshold) 28 | # We can zip those up into a list of (x, y) position tuples 29 | locations = list(zip(*locations[::-1])) 30 | print(locations) 31 | 32 | if locations: 33 | print('Found needle.') 34 | 35 | needle_w = needle_img.shape[1] 36 | needle_h = needle_img.shape[0] 37 | line_color = (0, 255, 0) 38 | line_type = cv.LINE_4 39 | 40 | # Loop over all the locations and draw their rectangle 41 | for loc in locations: 42 | # Determine the box positions 43 | top_left = loc 44 | bottom_right = (top_left[0] + needle_w, top_left[1] + needle_h) 45 | # Draw the box 46 | cv.rectangle(haystack_img, top_left, bottom_right, line_color, line_type) 47 | 48 | cv.imshow('Matches', haystack_img) 49 | cv.waitKey() 50 | #cv.imwrite('result.jpg', haystack_img) 51 | 52 | else: 53 | print('Needle not found.') 54 | -------------------------------------------------------------------------------- /002_match_multiple/result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/002_match_multiple/result.jpg -------------------------------------------------------------------------------- /003_group_rectangles/albion_cabbage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/003_group_rectangles/albion_cabbage.jpg -------------------------------------------------------------------------------- /003_group_rectangles/albion_farm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/003_group_rectangles/albion_farm.jpg -------------------------------------------------------------------------------- /003_group_rectangles/albion_turnip.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/003_group_rectangles/albion_turnip.jpg -------------------------------------------------------------------------------- /003_group_rectangles/main.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | import os 4 | 5 | 6 | # Change the working directory to the folder this script is in. 7 | # Doing this because I'll be putting the files from each video in their own folder on GitHub 8 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 9 | 10 | 11 | def findClickPositions(needle_img_path, haystack_img_path, threshold=0.5, debug_mode=None): 12 | 13 | # https://docs.opencv.org/4.2.0/d4/da8/group__imgcodecs.html 14 | haystack_img = cv.imread(haystack_img_path, cv.IMREAD_UNCHANGED) 15 | needle_img = cv.imread(needle_img_path, cv.IMREAD_UNCHANGED) 16 | # Save the dimensions of the needle image 17 | needle_w = needle_img.shape[1] 18 | needle_h = needle_img.shape[0] 19 | 20 | # There are 6 methods to choose from: 21 | # TM_CCOEFF, TM_CCOEFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_SQDIFF, TM_SQDIFF_NORMED 22 | method = cv.TM_CCOEFF_NORMED 23 | result = cv.matchTemplate(haystack_img, needle_img, method) 24 | 25 | # Get the all the positions from the match result that exceed our threshold 26 | locations = np.where(result >= threshold) 27 | locations = list(zip(*locations[::-1])) 28 | #print(locations) 29 | 30 | # You'll notice a lot of overlapping rectangles get drawn. We can eliminate those redundant 31 | # locations by using groupRectangles(). 32 | # First we need to create the list of [x, y, w, h] rectangles 33 | rectangles = [] 34 | for loc in locations: 35 | rect = [int(loc[0]), int(loc[1]), needle_w, needle_h] 36 | # Add every box to the list twice in order to retain single (non-overlapping) boxes 37 | rectangles.append(rect) 38 | rectangles.append(rect) 39 | # Apply group rectangles. 40 | # The groupThreshold parameter should usually be 1. If you put it at 0 then no grouping is 41 | # done. If you put it at 2 then an object needs at least 3 overlapping rectangles to appear 42 | # in the result. I've set eps to 0.5, which is: 43 | # "Relative difference between sides of the rectangles to merge them into a group." 44 | rectangles, weights = cv.groupRectangles(rectangles, groupThreshold=1, eps=0.5) 45 | #print(rectangles) 46 | 47 | points = [] 48 | if len(rectangles): 49 | #print('Found needle.') 50 | 51 | line_color = (0, 255, 0) 52 | line_type = cv.LINE_4 53 | marker_color = (255, 0, 255) 54 | marker_type = cv.MARKER_CROSS 55 | 56 | # Loop over all the rectangles 57 | for (x, y, w, h) in rectangles: 58 | 59 | # Determine the center position 60 | center_x = x + int(w/2) 61 | center_y = y + int(h/2) 62 | # Save the points 63 | points.append((center_x, center_y)) 64 | 65 | if debug_mode == 'rectangles': 66 | # Determine the box position 67 | top_left = (x, y) 68 | bottom_right = (x + w, y + h) 69 | # Draw the box 70 | cv.rectangle(haystack_img, top_left, bottom_right, color=line_color, 71 | lineType=line_type, thickness=2) 72 | elif debug_mode == 'points': 73 | # Draw the center point 74 | cv.drawMarker(haystack_img, (center_x, center_y), 75 | color=marker_color, markerType=marker_type, 76 | markerSize=40, thickness=2) 77 | 78 | if debug_mode: 79 | cv.imshow('Matches', haystack_img) 80 | cv.waitKey() 81 | #cv.imwrite('result_click_point.jpg', haystack_img) 82 | 83 | return points 84 | 85 | 86 | points = findClickPositions('albion_cabbage.jpg', 'albion_farm.jpg', debug_mode='points') 87 | print(points) 88 | points = findClickPositions('albion_turnip.jpg', 'albion_farm.jpg', 89 | threshold=0.70, debug_mode='rectangles') 90 | print(points) 91 | print('Done.') 92 | -------------------------------------------------------------------------------- /003_group_rectangles/result_click_point.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/003_group_rectangles/result_click_point.jpg -------------------------------------------------------------------------------- /004_window_capture/main.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | import os 4 | from time import time 5 | from windowcapture import WindowCapture 6 | 7 | # Change the working directory to the folder this script is in. 8 | # Doing this because I'll be putting the files from each video in their own folder on GitHub 9 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 10 | 11 | 12 | # initialize the WindowCapture class 13 | wincap = WindowCapture('Albion Online Client') 14 | 15 | loop_time = time() 16 | while(True): 17 | 18 | # get an updated image of the game 19 | screenshot = wincap.get_screenshot() 20 | 21 | cv.imshow('Computer Vision', screenshot) 22 | 23 | # debug the loop rate 24 | print('FPS {}'.format(1 / (time() - loop_time))) 25 | loop_time = time() 26 | 27 | # press 'q' with the output window focused to exit. 28 | # waits 1 ms every loop to process key presses 29 | if cv.waitKey(1) == ord('q'): 30 | cv.destroyAllWindows() 31 | break 32 | 33 | print('Done.') 34 | -------------------------------------------------------------------------------- /004_window_capture/windowcapture.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import win32gui, win32ui, win32con 3 | 4 | 5 | class WindowCapture: 6 | 7 | # properties 8 | w = 0 9 | h = 0 10 | hwnd = None 11 | cropped_x = 0 12 | cropped_y = 0 13 | offset_x = 0 14 | offset_y = 0 15 | 16 | # constructor 17 | def __init__(self, window_name): 18 | # find the handle for the window we want to capture 19 | self.hwnd = win32gui.FindWindow(None, window_name) 20 | if not self.hwnd: 21 | raise Exception('Window not found: {}'.format(window_name)) 22 | 23 | # get the window size 24 | window_rect = win32gui.GetWindowRect(self.hwnd) 25 | self.w = window_rect[2] - window_rect[0] 26 | self.h = window_rect[3] - window_rect[1] 27 | 28 | # account for the window border and titlebar and cut them off 29 | border_pixels = 8 30 | titlebar_pixels = 30 31 | self.w = self.w - (border_pixels * 2) 32 | self.h = self.h - titlebar_pixels - border_pixels 33 | self.cropped_x = border_pixels 34 | self.cropped_y = titlebar_pixels 35 | 36 | # set the cropped coordinates offset so we can translate screenshot 37 | # images into actual screen positions 38 | self.offset_x = window_rect[0] + self.cropped_x 39 | self.offset_y = window_rect[1] + self.cropped_y 40 | 41 | def get_screenshot(self): 42 | 43 | # get the window image data 44 | wDC = win32gui.GetWindowDC(self.hwnd) 45 | dcObj = win32ui.CreateDCFromHandle(wDC) 46 | cDC = dcObj.CreateCompatibleDC() 47 | dataBitMap = win32ui.CreateBitmap() 48 | dataBitMap.CreateCompatibleBitmap(dcObj, self.w, self.h) 49 | cDC.SelectObject(dataBitMap) 50 | cDC.BitBlt((0, 0), (self.w, self.h), dcObj, (self.cropped_x, self.cropped_y), win32con.SRCCOPY) 51 | 52 | # convert the raw data into a format opencv can read 53 | #dataBitMap.SaveBitmapFile(cDC, 'debug.bmp') 54 | signedIntsArray = dataBitMap.GetBitmapBits(True) 55 | img = np.fromstring(signedIntsArray, dtype='uint8') 56 | img.shape = (self.h, self.w, 4) 57 | 58 | # free resources 59 | dcObj.DeleteDC() 60 | cDC.DeleteDC() 61 | win32gui.ReleaseDC(self.hwnd, wDC) 62 | win32gui.DeleteObject(dataBitMap.GetHandle()) 63 | 64 | # drop the alpha channel, or cv.matchTemplate() will throw an error like: 65 | # error: (-215:Assertion failed) (depth == CV_8U || depth == CV_32F) && type == _templ.type() 66 | # && _img.dims() <= 2 in function 'cv::matchTemplate' 67 | img = img[...,:3] 68 | 69 | # make image C_CONTIGUOUS to avoid errors that look like: 70 | # File ... in draw_rectangles 71 | # TypeError: an integer is required (got type tuple) 72 | # see the discussion here: 73 | # https://github.com/opencv/opencv/issues/14866#issuecomment-580207109 74 | img = np.ascontiguousarray(img) 75 | 76 | return img 77 | 78 | # find the name of the window you're interested in. 79 | # once you have it, update window_capture() 80 | # https://stackoverflow.com/questions/55547940/how-to-get-a-list-of-the-name-of-every-open-window 81 | def list_window_names(self): 82 | def winEnumHandler(hwnd, ctx): 83 | if win32gui.IsWindowVisible(hwnd): 84 | print(hex(hwnd), win32gui.GetWindowText(hwnd)) 85 | win32gui.EnumWindows(winEnumHandler, None) 86 | 87 | # translate a pixel position on a screenshot image to a pixel position on the screen. 88 | # pos = (x, y) 89 | # WARNING: if you move the window being captured after execution is started, this will 90 | # return incorrect coordinates, because the window position is only calculated in 91 | # the __init__ constructor. 92 | def get_screen_position(self, pos): 93 | return (pos[0] + self.offset_x, pos[1] + self.offset_y) 94 | -------------------------------------------------------------------------------- /005_real_time/albion_limestone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/005_real_time/albion_limestone.jpg -------------------------------------------------------------------------------- /005_real_time/gunsnbottle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/005_real_time/gunsnbottle.jpg -------------------------------------------------------------------------------- /005_real_time/main.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | import os 4 | from time import time 5 | from windowcapture import WindowCapture 6 | from vision import Vision 7 | 8 | # Change the working directory to the folder this script is in. 9 | # Doing this because I'll be putting the files from each video in their own folder on GitHub 10 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 11 | 12 | 13 | # initialize the WindowCapture class 14 | wincap = WindowCapture('Albion Online Client') 15 | # initialize the Vision class 16 | vision_limestone = Vision('albion_limestone.jpg') 17 | 18 | ''' 19 | # https://www.crazygames.com/game/guns-and-bottle 20 | wincap = WindowCapture() 21 | vision_gunsnbottle = Vision('gunsnbottle.jpg') 22 | ''' 23 | 24 | loop_time = time() 25 | while(True): 26 | 27 | # get an updated image of the game 28 | screenshot = wincap.get_screenshot() 29 | 30 | # display the processed image 31 | points = vision_limestone.find(screenshot, 0.5, 'rectangles') 32 | #points = vision_gunsnbottle.find(screenshot, 0.7, 'points') 33 | 34 | # debug the loop rate 35 | print('FPS {}'.format(1 / (time() - loop_time))) 36 | loop_time = time() 37 | 38 | # press 'q' with the output window focused to exit. 39 | # waits 1 ms every loop to process key presses 40 | if cv.waitKey(1) == ord('q'): 41 | cv.destroyAllWindows() 42 | break 43 | 44 | print('Done.') 45 | -------------------------------------------------------------------------------- /005_real_time/vision.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | 4 | 5 | class Vision: 6 | 7 | # properties 8 | needle_img = None 9 | needle_w = 0 10 | needle_h = 0 11 | method = None 12 | 13 | # constructor 14 | def __init__(self, needle_img_path, method=cv.TM_CCOEFF_NORMED): 15 | # load the image we're trying to match 16 | # https://docs.opencv.org/4.2.0/d4/da8/group__imgcodecs.html 17 | self.needle_img = cv.imread(needle_img_path, cv.IMREAD_UNCHANGED) 18 | 19 | # Save the dimensions of the needle image 20 | self.needle_w = self.needle_img.shape[1] 21 | self.needle_h = self.needle_img.shape[0] 22 | 23 | # There are 6 methods to choose from: 24 | # TM_CCOEFF, TM_CCOEFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_SQDIFF, TM_SQDIFF_NORMED 25 | self.method = method 26 | 27 | def find(self, haystack_img, threshold=0.5, debug_mode=None): 28 | # run the OpenCV algorithm 29 | result = cv.matchTemplate(haystack_img, self.needle_img, self.method) 30 | 31 | # Get the all the positions from the match result that exceed our threshold 32 | locations = np.where(result >= threshold) 33 | locations = list(zip(*locations[::-1])) 34 | #print(locations) 35 | 36 | # You'll notice a lot of overlapping rectangles get drawn. We can eliminate those redundant 37 | # locations by using groupRectangles(). 38 | # First we need to create the list of [x, y, w, h] rectangles 39 | rectangles = [] 40 | for loc in locations: 41 | rect = [int(loc[0]), int(loc[1]), self.needle_w, self.needle_h] 42 | # Add every box to the list twice in order to retain single (non-overlapping) boxes 43 | rectangles.append(rect) 44 | rectangles.append(rect) 45 | # Apply group rectangles. 46 | # The groupThreshold parameter should usually be 1. If you put it at 0 then no grouping is 47 | # done. If you put it at 2 then an object needs at least 3 overlapping rectangles to appear 48 | # in the result. I've set eps to 0.5, which is: 49 | # "Relative difference between sides of the rectangles to merge them into a group." 50 | rectangles, weights = cv.groupRectangles(rectangles, groupThreshold=1, eps=0.5) 51 | #print(rectangles) 52 | 53 | points = [] 54 | if len(rectangles): 55 | #print('Found needle.') 56 | 57 | line_color = (0, 255, 0) 58 | line_type = cv.LINE_4 59 | marker_color = (255, 0, 255) 60 | marker_type = cv.MARKER_CROSS 61 | 62 | # Loop over all the rectangles 63 | for (x, y, w, h) in rectangles: 64 | 65 | # Determine the center position 66 | center_x = x + int(w/2) 67 | center_y = y + int(h/2) 68 | # Save the points 69 | points.append((center_x, center_y)) 70 | 71 | if debug_mode == 'rectangles': 72 | # Determine the box position 73 | top_left = (x, y) 74 | bottom_right = (x + w, y + h) 75 | # Draw the box 76 | cv.rectangle(haystack_img, top_left, bottom_right, color=line_color, 77 | lineType=line_type, thickness=2) 78 | elif debug_mode == 'points': 79 | # Draw the center point 80 | cv.drawMarker(haystack_img, (center_x, center_y), 81 | color=marker_color, markerType=marker_type, 82 | markerSize=40, thickness=2) 83 | 84 | if debug_mode: 85 | cv.imshow('Matches', haystack_img) 86 | #cv.waitKey() 87 | #cv.imwrite('result_click_point.jpg', haystack_img) 88 | 89 | return points 90 | -------------------------------------------------------------------------------- /005_real_time/windowcapture.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import win32gui, win32ui, win32con 3 | 4 | 5 | class WindowCapture: 6 | 7 | # properties 8 | w = 0 9 | h = 0 10 | hwnd = None 11 | cropped_x = 0 12 | cropped_y = 0 13 | offset_x = 0 14 | offset_y = 0 15 | 16 | # constructor 17 | def __init__(self, window_name=None): 18 | # find the handle for the window we want to capture. 19 | # if no window name is given, capture the entire screen 20 | if window_name is None: 21 | self.hwnd = win32gui.GetDesktopWindow() 22 | else: 23 | self.hwnd = win32gui.FindWindow(None, window_name) 24 | if not self.hwnd: 25 | raise Exception('Window not found: {}'.format(window_name)) 26 | 27 | # get the window size 28 | window_rect = win32gui.GetWindowRect(self.hwnd) 29 | self.w = window_rect[2] - window_rect[0] 30 | self.h = window_rect[3] - window_rect[1] 31 | 32 | # account for the window border and titlebar and cut them off 33 | border_pixels = 8 34 | titlebar_pixels = 30 35 | self.w = self.w - (border_pixels * 2) 36 | self.h = self.h - titlebar_pixels - border_pixels 37 | self.cropped_x = border_pixels 38 | self.cropped_y = titlebar_pixels 39 | 40 | # set the cropped coordinates offset so we can translate screenshot 41 | # images into actual screen positions 42 | self.offset_x = window_rect[0] + self.cropped_x 43 | self.offset_y = window_rect[1] + self.cropped_y 44 | 45 | def get_screenshot(self): 46 | 47 | # get the window image data 48 | wDC = win32gui.GetWindowDC(self.hwnd) 49 | dcObj = win32ui.CreateDCFromHandle(wDC) 50 | cDC = dcObj.CreateCompatibleDC() 51 | dataBitMap = win32ui.CreateBitmap() 52 | dataBitMap.CreateCompatibleBitmap(dcObj, self.w, self.h) 53 | cDC.SelectObject(dataBitMap) 54 | cDC.BitBlt((0, 0), (self.w, self.h), dcObj, (self.cropped_x, self.cropped_y), win32con.SRCCOPY) 55 | 56 | # convert the raw data into a format opencv can read 57 | #dataBitMap.SaveBitmapFile(cDC, 'debug.bmp') 58 | signedIntsArray = dataBitMap.GetBitmapBits(True) 59 | img = np.fromstring(signedIntsArray, dtype='uint8') 60 | img.shape = (self.h, self.w, 4) 61 | 62 | # free resources 63 | dcObj.DeleteDC() 64 | cDC.DeleteDC() 65 | win32gui.ReleaseDC(self.hwnd, wDC) 66 | win32gui.DeleteObject(dataBitMap.GetHandle()) 67 | 68 | # drop the alpha channel, or cv.matchTemplate() will throw an error like: 69 | # error: (-215:Assertion failed) (depth == CV_8U || depth == CV_32F) && type == _templ.type() 70 | # && _img.dims() <= 2 in function 'cv::matchTemplate' 71 | img = img[...,:3] 72 | 73 | # make image C_CONTIGUOUS to avoid errors that look like: 74 | # File ... in draw_rectangles 75 | # TypeError: an integer is required (got type tuple) 76 | # see the discussion here: 77 | # https://github.com/opencv/opencv/issues/14866#issuecomment-580207109 78 | img = np.ascontiguousarray(img) 79 | 80 | return img 81 | 82 | # find the name of the window you're interested in. 83 | # once you have it, update window_capture() 84 | # https://stackoverflow.com/questions/55547940/how-to-get-a-list-of-the-name-of-every-open-window 85 | @staticmethod 86 | def list_window_names(): 87 | def winEnumHandler(hwnd, ctx): 88 | if win32gui.IsWindowVisible(hwnd): 89 | print(hex(hwnd), win32gui.GetWindowText(hwnd)) 90 | win32gui.EnumWindows(winEnumHandler, None) 91 | 92 | # translate a pixel position on a screenshot image to a pixel position on the screen. 93 | # pos = (x, y) 94 | # WARNING: if you move the window being captured after execution is started, this will 95 | # return incorrect coordinates, because the window position is only calculated in 96 | # the __init__ constructor. 97 | def get_screen_position(self, pos): 98 | return (pos[0] + self.offset_x, pos[1] + self.offset_y) 99 | -------------------------------------------------------------------------------- /006_hsv_thresholding/albion_limestone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/006_hsv_thresholding/albion_limestone.jpg -------------------------------------------------------------------------------- /006_hsv_thresholding/albion_limestone_processed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/006_hsv_thresholding/albion_limestone_processed.jpg -------------------------------------------------------------------------------- /006_hsv_thresholding/hsvfilter.py: -------------------------------------------------------------------------------- 1 | 2 | # custom data structure to hold the state of an HSV filter 3 | class HsvFilter: 4 | 5 | def __init__(self, hMin=None, sMin=None, vMin=None, hMax=None, sMax=None, vMax=None, 6 | sAdd=None, sSub=None, vAdd=None, vSub=None): 7 | self.hMin = hMin 8 | self.sMin = sMin 9 | self.vMin = vMin 10 | self.hMax = hMax 11 | self.sMax = sMax 12 | self.vMax = vMax 13 | self.sAdd = sAdd 14 | self.sSub = sSub 15 | self.vAdd = vAdd 16 | self.vSub = vSub 17 | -------------------------------------------------------------------------------- /006_hsv_thresholding/main.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | import os 4 | from time import time 5 | from windowcapture import WindowCapture 6 | from vision import Vision 7 | from hsvfilter import HsvFilter 8 | 9 | # Change the working directory to the folder this script is in. 10 | # Doing this because I'll be putting the files from each video in their own folder on GitHub 11 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 12 | 13 | 14 | # initialize the WindowCapture class 15 | wincap = WindowCapture('Albion Online Client') 16 | # initialize the Vision class 17 | vision_limestone = Vision('albion_limestone_processed.jpg') 18 | # initialize the trackbar window 19 | vision_limestone.init_control_gui() 20 | 21 | # limestone HSV filter 22 | hsv_filter = HsvFilter(0, 180, 129, 15, 229, 243, 143, 0, 67, 0) 23 | 24 | loop_time = time() 25 | while(True): 26 | 27 | # get an updated image of the game 28 | screenshot = wincap.get_screenshot() 29 | 30 | # pre-process the image 31 | processed_image = vision_limestone.apply_hsv_filter(screenshot, hsv_filter) 32 | 33 | # do object detection 34 | rectangles = vision_limestone.find(processed_image, 0.46) 35 | 36 | # draw the detection results onto the original image 37 | output_image = vision_limestone.draw_rectangles(screenshot, rectangles) 38 | 39 | # display the processed image 40 | cv.imshow('Processed', processed_image) 41 | cv.imshow('Matches', output_image) 42 | 43 | # debug the loop rate 44 | print('FPS {}'.format(1 / (time() - loop_time))) 45 | loop_time = time() 46 | 47 | # press 'q' with the output window focused to exit. 48 | # waits 1 ms every loop to process key presses 49 | if cv.waitKey(1) == ord('q'): 50 | cv.destroyAllWindows() 51 | break 52 | 53 | print('Done.') 54 | -------------------------------------------------------------------------------- /006_hsv_thresholding/vision.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | from hsvfilter import HsvFilter 4 | 5 | 6 | class Vision: 7 | # constants 8 | TRACKBAR_WINDOW = "Trackbars" 9 | 10 | # properties 11 | needle_img = None 12 | needle_w = 0 13 | needle_h = 0 14 | method = None 15 | 16 | # constructor 17 | def __init__(self, needle_img_path, method=cv.TM_CCOEFF_NORMED): 18 | # load the image we're trying to match 19 | # https://docs.opencv.org/4.2.0/d4/da8/group__imgcodecs.html 20 | self.needle_img = cv.imread(needle_img_path, cv.IMREAD_UNCHANGED) 21 | 22 | # Save the dimensions of the needle image 23 | self.needle_w = self.needle_img.shape[1] 24 | self.needle_h = self.needle_img.shape[0] 25 | 26 | # There are 6 methods to choose from: 27 | # TM_CCOEFF, TM_CCOEFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_SQDIFF, TM_SQDIFF_NORMED 28 | self.method = method 29 | 30 | def find(self, haystack_img, threshold=0.5, max_results=10): 31 | # run the OpenCV algorithm 32 | result = cv.matchTemplate(haystack_img, self.needle_img, self.method) 33 | 34 | # Get the all the positions from the match result that exceed our threshold 35 | locations = np.where(result >= threshold) 36 | locations = list(zip(*locations[::-1])) 37 | #print(locations) 38 | 39 | # if we found no results, return now. this reshape of the empty array allows us to 40 | # concatenate together results without causing an error 41 | if not locations: 42 | return np.array([], dtype=np.int32).reshape(0, 4) 43 | 44 | # You'll notice a lot of overlapping rectangles get drawn. We can eliminate those redundant 45 | # locations by using groupRectangles(). 46 | # First we need to create the list of [x, y, w, h] rectangles 47 | rectangles = [] 48 | for loc in locations: 49 | rect = [int(loc[0]), int(loc[1]), self.needle_w, self.needle_h] 50 | # Add every box to the list twice in order to retain single (non-overlapping) boxes 51 | rectangles.append(rect) 52 | rectangles.append(rect) 53 | # Apply group rectangles. 54 | # The groupThreshold parameter should usually be 1. If you put it at 0 then no grouping is 55 | # done. If you put it at 2 then an object needs at least 3 overlapping rectangles to appear 56 | # in the result. I've set eps to 0.5, which is: 57 | # "Relative difference between sides of the rectangles to merge them into a group." 58 | rectangles, weights = cv.groupRectangles(rectangles, groupThreshold=1, eps=0.5) 59 | #print(rectangles) 60 | 61 | # for performance reasons, return a limited number of results. 62 | # these aren't necessarily the best results. 63 | if len(rectangles) > max_results: 64 | print('Warning: too many results, raise the threshold.') 65 | rectangles = rectangles[:max_results] 66 | 67 | return rectangles 68 | 69 | # given a list of [x, y, w, h] rectangles returned by find(), convert those into a list of 70 | # [x, y] positions in the center of those rectangles where we can click on those found items 71 | def get_click_points(self, rectangles): 72 | points = [] 73 | 74 | # Loop over all the rectangles 75 | for (x, y, w, h) in rectangles: 76 | # Determine the center position 77 | center_x = x + int(w/2) 78 | center_y = y + int(h/2) 79 | # Save the points 80 | points.append((center_x, center_y)) 81 | 82 | return points 83 | 84 | # given a list of [x, y, w, h] rectangles and a canvas image to draw on, return an image with 85 | # all of those rectangles drawn 86 | def draw_rectangles(self, haystack_img, rectangles): 87 | # these colors are actually BGR 88 | line_color = (0, 255, 0) 89 | line_type = cv.LINE_4 90 | 91 | for (x, y, w, h) in rectangles: 92 | # determine the box positions 93 | top_left = (x, y) 94 | bottom_right = (x + w, y + h) 95 | # draw the box 96 | cv.rectangle(haystack_img, top_left, bottom_right, line_color, lineType=line_type) 97 | 98 | return haystack_img 99 | 100 | # given a list of [x, y] positions and a canvas image to draw on, return an image with all 101 | # of those click points drawn on as crosshairs 102 | def draw_crosshairs(self, haystack_img, points): 103 | # these colors are actually BGR 104 | marker_color = (255, 0, 255) 105 | marker_type = cv.MARKER_CROSS 106 | 107 | for (center_x, center_y) in points: 108 | # draw the center point 109 | cv.drawMarker(haystack_img, (center_x, center_y), marker_color, marker_type) 110 | 111 | return haystack_img 112 | 113 | # create gui window with controls for adjusting arguments in real-time 114 | def init_control_gui(self): 115 | cv.namedWindow(self.TRACKBAR_WINDOW, cv.WINDOW_NORMAL) 116 | cv.resizeWindow(self.TRACKBAR_WINDOW, 350, 700) 117 | 118 | # required callback. we'll be using getTrackbarPos() to do lookups 119 | # instead of using the callback. 120 | def nothing(position): 121 | pass 122 | 123 | # create trackbars for bracketing. 124 | # OpenCV scale for HSV is H: 0-179, S: 0-255, V: 0-255 125 | cv.createTrackbar('HMin', self.TRACKBAR_WINDOW, 0, 179, nothing) 126 | cv.createTrackbar('SMin', self.TRACKBAR_WINDOW, 0, 255, nothing) 127 | cv.createTrackbar('VMin', self.TRACKBAR_WINDOW, 0, 255, nothing) 128 | cv.createTrackbar('HMax', self.TRACKBAR_WINDOW, 0, 179, nothing) 129 | cv.createTrackbar('SMax', self.TRACKBAR_WINDOW, 0, 255, nothing) 130 | cv.createTrackbar('VMax', self.TRACKBAR_WINDOW, 0, 255, nothing) 131 | # Set default value for Max HSV trackbars 132 | cv.setTrackbarPos('HMax', self.TRACKBAR_WINDOW, 179) 133 | cv.setTrackbarPos('SMax', self.TRACKBAR_WINDOW, 255) 134 | cv.setTrackbarPos('VMax', self.TRACKBAR_WINDOW, 255) 135 | 136 | # trackbars for increasing/decreasing saturation and value 137 | cv.createTrackbar('SAdd', self.TRACKBAR_WINDOW, 0, 255, nothing) 138 | cv.createTrackbar('SSub', self.TRACKBAR_WINDOW, 0, 255, nothing) 139 | cv.createTrackbar('VAdd', self.TRACKBAR_WINDOW, 0, 255, nothing) 140 | cv.createTrackbar('VSub', self.TRACKBAR_WINDOW, 0, 255, nothing) 141 | 142 | # returns an HSV filter object based on the control GUI values 143 | def get_hsv_filter_from_controls(self): 144 | # Get current positions of all trackbars 145 | hsv_filter = HsvFilter() 146 | hsv_filter.hMin = cv.getTrackbarPos('HMin', self.TRACKBAR_WINDOW) 147 | hsv_filter.sMin = cv.getTrackbarPos('SMin', self.TRACKBAR_WINDOW) 148 | hsv_filter.vMin = cv.getTrackbarPos('VMin', self.TRACKBAR_WINDOW) 149 | hsv_filter.hMax = cv.getTrackbarPos('HMax', self.TRACKBAR_WINDOW) 150 | hsv_filter.sMax = cv.getTrackbarPos('SMax', self.TRACKBAR_WINDOW) 151 | hsv_filter.vMax = cv.getTrackbarPos('VMax', self.TRACKBAR_WINDOW) 152 | hsv_filter.sAdd = cv.getTrackbarPos('SAdd', self.TRACKBAR_WINDOW) 153 | hsv_filter.sSub = cv.getTrackbarPos('SSub', self.TRACKBAR_WINDOW) 154 | hsv_filter.vAdd = cv.getTrackbarPos('VAdd', self.TRACKBAR_WINDOW) 155 | hsv_filter.vSub = cv.getTrackbarPos('VSub', self.TRACKBAR_WINDOW) 156 | return hsv_filter 157 | 158 | # given an image and an HSV filter, apply the filter and return the resulting image. 159 | # if a filter is not supplied, the control GUI trackbars will be used 160 | def apply_hsv_filter(self, original_image, hsv_filter=None): 161 | # convert image to HSV 162 | hsv = cv.cvtColor(original_image, cv.COLOR_BGR2HSV) 163 | 164 | # if we haven't been given a defined filter, use the filter values from the GUI 165 | if not hsv_filter: 166 | hsv_filter = self.get_hsv_filter_from_controls() 167 | 168 | # add/subtract saturation and value 169 | h, s, v = cv.split(hsv) 170 | s = self.shift_channel(s, hsv_filter.sAdd) 171 | s = self.shift_channel(s, -hsv_filter.sSub) 172 | v = self.shift_channel(v, hsv_filter.vAdd) 173 | v = self.shift_channel(v, -hsv_filter.vSub) 174 | hsv = cv.merge([h, s, v]) 175 | 176 | # Set minimum and maximum HSV values to display 177 | lower = np.array([hsv_filter.hMin, hsv_filter.sMin, hsv_filter.vMin]) 178 | upper = np.array([hsv_filter.hMax, hsv_filter.sMax, hsv_filter.vMax]) 179 | # Apply the thresholds 180 | mask = cv.inRange(hsv, lower, upper) 181 | result = cv.bitwise_and(hsv, hsv, mask=mask) 182 | 183 | # convert back to BGR for imshow() to display it properly 184 | img = cv.cvtColor(result, cv.COLOR_HSV2BGR) 185 | 186 | return img 187 | 188 | # apply adjustments to an HSV channel 189 | # https://stackoverflow.com/questions/49697363/shifting-hsv-pixel-values-in-python-using-numpy 190 | def shift_channel(self, c, amount): 191 | if amount > 0: 192 | lim = 255 - amount 193 | c[c >= lim] = 255 194 | c[c < lim] += amount 195 | elif amount < 0: 196 | amount = -amount 197 | lim = amount 198 | c[c <= lim] = 0 199 | c[c > lim] -= amount 200 | return c 201 | 202 | 203 | 204 | 205 | -------------------------------------------------------------------------------- /006_hsv_thresholding/windowcapture.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import win32gui, win32ui, win32con 3 | 4 | 5 | class WindowCapture: 6 | 7 | # properties 8 | w = 0 9 | h = 0 10 | hwnd = None 11 | cropped_x = 0 12 | cropped_y = 0 13 | offset_x = 0 14 | offset_y = 0 15 | 16 | # constructor 17 | def __init__(self, window_name=None): 18 | # find the handle for the window we want to capture. 19 | # if no window name is given, capture the entire screen 20 | if window_name is None: 21 | self.hwnd = win32gui.GetDesktopWindow() 22 | else: 23 | self.hwnd = win32gui.FindWindow(None, window_name) 24 | if not self.hwnd: 25 | raise Exception('Window not found: {}'.format(window_name)) 26 | 27 | # get the window size 28 | window_rect = win32gui.GetWindowRect(self.hwnd) 29 | self.w = window_rect[2] - window_rect[0] 30 | self.h = window_rect[3] - window_rect[1] 31 | 32 | # account for the window border and titlebar and cut them off 33 | border_pixels = 8 34 | titlebar_pixels = 30 35 | self.w = self.w - (border_pixels * 2) 36 | self.h = self.h - titlebar_pixels - border_pixels 37 | self.cropped_x = border_pixels 38 | self.cropped_y = titlebar_pixels 39 | 40 | # set the cropped coordinates offset so we can translate screenshot 41 | # images into actual screen positions 42 | self.offset_x = window_rect[0] + self.cropped_x 43 | self.offset_y = window_rect[1] + self.cropped_y 44 | 45 | def get_screenshot(self): 46 | 47 | # get the window image data 48 | wDC = win32gui.GetWindowDC(self.hwnd) 49 | dcObj = win32ui.CreateDCFromHandle(wDC) 50 | cDC = dcObj.CreateCompatibleDC() 51 | dataBitMap = win32ui.CreateBitmap() 52 | dataBitMap.CreateCompatibleBitmap(dcObj, self.w, self.h) 53 | cDC.SelectObject(dataBitMap) 54 | cDC.BitBlt((0, 0), (self.w, self.h), dcObj, (self.cropped_x, self.cropped_y), win32con.SRCCOPY) 55 | 56 | # convert the raw data into a format opencv can read 57 | #dataBitMap.SaveBitmapFile(cDC, 'debug.bmp') 58 | signedIntsArray = dataBitMap.GetBitmapBits(True) 59 | img = np.fromstring(signedIntsArray, dtype='uint8') 60 | img.shape = (self.h, self.w, 4) 61 | 62 | # free resources 63 | dcObj.DeleteDC() 64 | cDC.DeleteDC() 65 | win32gui.ReleaseDC(self.hwnd, wDC) 66 | win32gui.DeleteObject(dataBitMap.GetHandle()) 67 | 68 | # drop the alpha channel, or cv.matchTemplate() will throw an error like: 69 | # error: (-215:Assertion failed) (depth == CV_8U || depth == CV_32F) && type == _templ.type() 70 | # && _img.dims() <= 2 in function 'cv::matchTemplate' 71 | img = img[...,:3] 72 | 73 | # make image C_CONTIGUOUS to avoid errors that look like: 74 | # File ... in draw_rectangles 75 | # TypeError: an integer is required (got type tuple) 76 | # see the discussion here: 77 | # https://github.com/opencv/opencv/issues/14866#issuecomment-580207109 78 | img = np.ascontiguousarray(img) 79 | 80 | return img 81 | 82 | # find the name of the window you're interested in. 83 | # once you have it, update window_capture() 84 | # https://stackoverflow.com/questions/55547940/how-to-get-a-list-of-the-name-of-every-open-window 85 | @staticmethod 86 | def list_window_names(): 87 | def winEnumHandler(hwnd, ctx): 88 | if win32gui.IsWindowVisible(hwnd): 89 | print(hex(hwnd), win32gui.GetWindowText(hwnd)) 90 | win32gui.EnumWindows(winEnumHandler, None) 91 | 92 | # translate a pixel position on a screenshot image to a pixel position on the screen. 93 | # pos = (x, y) 94 | # WARNING: if you move the window being captured after execution is started, this will 95 | # return incorrect coordinates, because the window position is only calculated in 96 | # the __init__ constructor. 97 | def get_screen_position(self, pos): 98 | return (pos[0] + self.offset_x, pos[1] + self.offset_y) 99 | -------------------------------------------------------------------------------- /007_canny_edge/albion_limestone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/007_canny_edge/albion_limestone.jpg -------------------------------------------------------------------------------- /007_canny_edge/albion_limestone_edges.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/007_canny_edge/albion_limestone_edges.jpg -------------------------------------------------------------------------------- /007_canny_edge/albion_limestone_processed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/007_canny_edge/albion_limestone_processed.jpg -------------------------------------------------------------------------------- /007_canny_edge/edgefilter.py: -------------------------------------------------------------------------------- 1 | 2 | # custom data structure to hold the state of a Canny edge filter 3 | class EdgeFilter: 4 | 5 | def __init__(self, kernelSize=None, erodeIter=None, dilateIter=None, canny1=None, 6 | canny2=None): 7 | self.kernelSize = kernelSize 8 | self.erodeIter = erodeIter 9 | self.dilateIter = dilateIter 10 | self.canny1 = canny1 11 | self.canny2 = canny2 12 | -------------------------------------------------------------------------------- /007_canny_edge/hsvfilter.py: -------------------------------------------------------------------------------- 1 | 2 | # custom data structure to hold the state of an HSV filter 3 | class HsvFilter: 4 | 5 | def __init__(self, hMin=None, sMin=None, vMin=None, hMax=None, sMax=None, vMax=None, 6 | sAdd=None, sSub=None, vAdd=None, vSub=None): 7 | self.hMin = hMin 8 | self.sMin = sMin 9 | self.vMin = vMin 10 | self.hMax = hMax 11 | self.sMax = sMax 12 | self.vMax = vMax 13 | self.sAdd = sAdd 14 | self.sSub = sSub 15 | self.vAdd = vAdd 16 | self.vSub = vSub 17 | -------------------------------------------------------------------------------- /007_canny_edge/main.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | import os 4 | from time import time 5 | from windowcapture import WindowCapture 6 | from vision import Vision 7 | from hsvfilter import HsvFilter 8 | from edgefilter import EdgeFilter 9 | 10 | # Change the working directory to the folder this script is in. 11 | # Doing this because I'll be putting the files from each video in their own folder on GitHub 12 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 13 | 14 | 15 | # initialize the WindowCapture class 16 | wincap = WindowCapture('Albion Online Client') 17 | # initialize the Vision class 18 | vision_limestone = Vision('albion_limestone_edges.jpg') 19 | # initialize the trackbar window 20 | vision_limestone.init_control_gui() 21 | 22 | # limestone HSV filter 23 | hsv_filter = HsvFilter(0, 180, 129, 15, 229, 243, 143, 0, 67, 0) 24 | 25 | loop_time = time() 26 | while(True): 27 | 28 | # get an updated image of the game 29 | screenshot = wincap.get_screenshot() 30 | 31 | # pre-process the image 32 | processed_image = vision_limestone.apply_hsv_filter(screenshot) 33 | 34 | # do edge detection 35 | edges_image = vision_limestone.apply_edge_filter(processed_image) 36 | 37 | # do object detection 38 | #rectangles = vision_limestone.find(processed_image, 0.46) 39 | 40 | # draw the detection results onto the original image 41 | #output_image = vision_limestone.draw_rectangles(screenshot, rectangles) 42 | 43 | # keypoint searching 44 | keypoint_image = edges_image 45 | # crop the image to remove the ui elements 46 | x, w, y, h = [200, 1130, 70, 750] 47 | keypoint_image = keypoint_image[y:y+h, x:x+w] 48 | 49 | kp1, kp2, matches, match_points = vision_limestone.match_keypoints(keypoint_image) 50 | match_image = cv.drawMatches( 51 | vision_limestone.needle_img, 52 | kp1, 53 | keypoint_image, 54 | kp2, 55 | matches, 56 | None) 57 | 58 | if match_points: 59 | # find the center point of all the matched features 60 | center_point = vision_limestone.centeroid(match_points) 61 | # account for the width of the needle image that appears on the left 62 | center_point[0] += vision_limestone.needle_w 63 | # drawn the found center point on the output image 64 | match_image = vision_limestone.draw_crosshairs(match_image, [center_point]) 65 | 66 | # display the processed image 67 | cv.imshow('Keypoint Search', match_image) 68 | cv.imshow('Processed', processed_image) 69 | cv.imshow('Edges', edges_image) 70 | #cv.imshow('Matches', output_image) 71 | 72 | # debug the loop rate 73 | print('FPS {}'.format(1 / (time() - loop_time))) 74 | loop_time = time() 75 | 76 | # press 'q' with the output window focused to exit. 77 | # waits 1 ms every loop to process key presses 78 | if cv.waitKey(1) == ord('q'): 79 | cv.destroyAllWindows() 80 | break 81 | 82 | print('Done.') 83 | -------------------------------------------------------------------------------- /007_canny_edge/vision.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | from hsvfilter import HsvFilter 4 | from edgefilter import EdgeFilter 5 | 6 | 7 | class Vision: 8 | # constants 9 | TRACKBAR_WINDOW = "Trackbars" 10 | 11 | # properties 12 | needle_img = None 13 | needle_w = 0 14 | needle_h = 0 15 | method = None 16 | 17 | # constructor 18 | def __init__(self, needle_img_path, method=cv.TM_CCOEFF_NORMED): 19 | # load the image we're trying to match 20 | # https://docs.opencv.org/4.2.0/d4/da8/group__imgcodecs.html 21 | self.needle_img = cv.imread(needle_img_path, cv.IMREAD_UNCHANGED) 22 | 23 | # Save the dimensions of the needle image 24 | self.needle_w = self.needle_img.shape[1] 25 | self.needle_h = self.needle_img.shape[0] 26 | 27 | # There are 6 methods to choose from: 28 | # TM_CCOEFF, TM_CCOEFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_SQDIFF, TM_SQDIFF_NORMED 29 | self.method = method 30 | 31 | def find(self, haystack_img, threshold=0.5, max_results=10): 32 | # run the OpenCV algorithm 33 | result = cv.matchTemplate(haystack_img, self.needle_img, self.method) 34 | 35 | # Get the all the positions from the match result that exceed our threshold 36 | locations = np.where(result >= threshold) 37 | locations = list(zip(*locations[::-1])) 38 | #print(locations) 39 | 40 | # if we found no results, return now. this reshape of the empty array allows us to 41 | # concatenate together results without causing an error 42 | if not locations: 43 | return np.array([], dtype=np.int32).reshape(0, 4) 44 | 45 | # You'll notice a lot of overlapping rectangles get drawn. We can eliminate those redundant 46 | # locations by using groupRectangles(). 47 | # First we need to create the list of [x, y, w, h] rectangles 48 | rectangles = [] 49 | for loc in locations: 50 | rect = [int(loc[0]), int(loc[1]), self.needle_w, self.needle_h] 51 | # Add every box to the list twice in order to retain single (non-overlapping) boxes 52 | rectangles.append(rect) 53 | rectangles.append(rect) 54 | # Apply group rectangles. 55 | # The groupThreshold parameter should usually be 1. If you put it at 0 then no grouping is 56 | # done. If you put it at 2 then an object needs at least 3 overlapping rectangles to appear 57 | # in the result. I've set eps to 0.5, which is: 58 | # "Relative difference between sides of the rectangles to merge them into a group." 59 | rectangles, weights = cv.groupRectangles(rectangles, groupThreshold=1, eps=0.5) 60 | #print(rectangles) 61 | 62 | # for performance reasons, return a limited number of results. 63 | # these aren't necessarily the best results. 64 | if len(rectangles) > max_results: 65 | print('Warning: too many results, raise the threshold.') 66 | rectangles = rectangles[:max_results] 67 | 68 | return rectangles 69 | 70 | # given a list of [x, y, w, h] rectangles returned by find(), convert those into a list of 71 | # [x, y] positions in the center of those rectangles where we can click on those found items 72 | def get_click_points(self, rectangles): 73 | points = [] 74 | 75 | # Loop over all the rectangles 76 | for (x, y, w, h) in rectangles: 77 | # Determine the center position 78 | center_x = x + int(w/2) 79 | center_y = y + int(h/2) 80 | # Save the points 81 | points.append((center_x, center_y)) 82 | 83 | return points 84 | 85 | # given a list of [x, y, w, h] rectangles and a canvas image to draw on, return an image with 86 | # all of those rectangles drawn 87 | def draw_rectangles(self, haystack_img, rectangles): 88 | # these colors are actually BGR 89 | line_color = (0, 255, 0) 90 | line_type = cv.LINE_4 91 | 92 | for (x, y, w, h) in rectangles: 93 | # determine the box positions 94 | top_left = (x, y) 95 | bottom_right = (x + w, y + h) 96 | # draw the box 97 | cv.rectangle(haystack_img, top_left, bottom_right, line_color, lineType=line_type) 98 | 99 | return haystack_img 100 | 101 | # given a list of [x, y] positions and a canvas image to draw on, return an image with all 102 | # of those click points drawn on as crosshairs 103 | def draw_crosshairs(self, haystack_img, points): 104 | # these colors are actually BGR 105 | marker_color = (255, 0, 255) 106 | marker_type = cv.MARKER_CROSS 107 | 108 | for (center_x, center_y) in points: 109 | # draw the center point 110 | cv.drawMarker(haystack_img, (center_x, center_y), marker_color, marker_type) 111 | 112 | return haystack_img 113 | 114 | # create gui window with controls for adjusting arguments in real-time 115 | def init_control_gui(self): 116 | cv.namedWindow(self.TRACKBAR_WINDOW, cv.WINDOW_NORMAL) 117 | cv.resizeWindow(self.TRACKBAR_WINDOW, 350, 700) 118 | 119 | # required callback. we'll be using getTrackbarPos() to do lookups 120 | # instead of using the callback. 121 | def nothing(position): 122 | pass 123 | 124 | # create trackbars for bracketing. 125 | # OpenCV scale for HSV is H: 0-179, S: 0-255, V: 0-255 126 | cv.createTrackbar('HMin', self.TRACKBAR_WINDOW, 0, 179, nothing) 127 | cv.createTrackbar('SMin', self.TRACKBAR_WINDOW, 0, 255, nothing) 128 | cv.createTrackbar('VMin', self.TRACKBAR_WINDOW, 0, 255, nothing) 129 | cv.createTrackbar('HMax', self.TRACKBAR_WINDOW, 0, 179, nothing) 130 | cv.createTrackbar('SMax', self.TRACKBAR_WINDOW, 0, 255, nothing) 131 | cv.createTrackbar('VMax', self.TRACKBAR_WINDOW, 0, 255, nothing) 132 | # Set default value for Max HSV trackbars 133 | cv.setTrackbarPos('HMax', self.TRACKBAR_WINDOW, 179) 134 | cv.setTrackbarPos('SMax', self.TRACKBAR_WINDOW, 255) 135 | cv.setTrackbarPos('VMax', self.TRACKBAR_WINDOW, 255) 136 | 137 | # trackbars for increasing/decreasing saturation and value 138 | cv.createTrackbar('SAdd', self.TRACKBAR_WINDOW, 0, 255, nothing) 139 | cv.createTrackbar('SSub', self.TRACKBAR_WINDOW, 0, 255, nothing) 140 | cv.createTrackbar('VAdd', self.TRACKBAR_WINDOW, 0, 255, nothing) 141 | cv.createTrackbar('VSub', self.TRACKBAR_WINDOW, 0, 255, nothing) 142 | 143 | # trackbars for edge creation 144 | cv.createTrackbar('KernelSize', self.TRACKBAR_WINDOW, 1, 30, nothing) 145 | cv.createTrackbar('ErodeIter', self.TRACKBAR_WINDOW, 1, 5, nothing) 146 | cv.createTrackbar('DilateIter', self.TRACKBAR_WINDOW, 1, 5, nothing) 147 | cv.createTrackbar('Canny1', self.TRACKBAR_WINDOW, 0, 200, nothing) 148 | cv.createTrackbar('Canny2', self.TRACKBAR_WINDOW, 0, 500, nothing) 149 | # Set default value for Canny trackbars 150 | cv.setTrackbarPos('KernelSize', self.TRACKBAR_WINDOW, 5) 151 | cv.setTrackbarPos('Canny1', self.TRACKBAR_WINDOW, 100) 152 | cv.setTrackbarPos('Canny2', self.TRACKBAR_WINDOW, 200) 153 | 154 | # returns an HSV filter object based on the control GUI values 155 | def get_hsv_filter_from_controls(self): 156 | # Get current positions of all trackbars 157 | hsv_filter = HsvFilter() 158 | hsv_filter.hMin = cv.getTrackbarPos('HMin', self.TRACKBAR_WINDOW) 159 | hsv_filter.sMin = cv.getTrackbarPos('SMin', self.TRACKBAR_WINDOW) 160 | hsv_filter.vMin = cv.getTrackbarPos('VMin', self.TRACKBAR_WINDOW) 161 | hsv_filter.hMax = cv.getTrackbarPos('HMax', self.TRACKBAR_WINDOW) 162 | hsv_filter.sMax = cv.getTrackbarPos('SMax', self.TRACKBAR_WINDOW) 163 | hsv_filter.vMax = cv.getTrackbarPos('VMax', self.TRACKBAR_WINDOW) 164 | hsv_filter.sAdd = cv.getTrackbarPos('SAdd', self.TRACKBAR_WINDOW) 165 | hsv_filter.sSub = cv.getTrackbarPos('SSub', self.TRACKBAR_WINDOW) 166 | hsv_filter.vAdd = cv.getTrackbarPos('VAdd', self.TRACKBAR_WINDOW) 167 | hsv_filter.vSub = cv.getTrackbarPos('VSub', self.TRACKBAR_WINDOW) 168 | return hsv_filter 169 | 170 | # returns a Canny edge filter object based on the control GUI values 171 | def get_edge_filter_from_controls(self): 172 | # Get current positions of all trackbars 173 | edge_filter = EdgeFilter() 174 | edge_filter.kernelSize = cv.getTrackbarPos('KernelSize', self.TRACKBAR_WINDOW) 175 | edge_filter.erodeIter = cv.getTrackbarPos('ErodeIter', self.TRACKBAR_WINDOW) 176 | edge_filter.dilateIter = cv.getTrackbarPos('DilateIter', self.TRACKBAR_WINDOW) 177 | edge_filter.canny1 = cv.getTrackbarPos('Canny1', self.TRACKBAR_WINDOW) 178 | edge_filter.canny2 = cv.getTrackbarPos('Canny2', self.TRACKBAR_WINDOW) 179 | return edge_filter 180 | 181 | # given an image and an HSV filter, apply the filter and return the resulting image. 182 | # if a filter is not supplied, the control GUI trackbars will be used 183 | def apply_hsv_filter(self, original_image, hsv_filter=None): 184 | # convert image to HSV 185 | hsv = cv.cvtColor(original_image, cv.COLOR_BGR2HSV) 186 | 187 | # if we haven't been given a defined filter, use the filter values from the GUI 188 | if not hsv_filter: 189 | hsv_filter = self.get_hsv_filter_from_controls() 190 | 191 | # add/subtract saturation and value 192 | h, s, v = cv.split(hsv) 193 | s = self.shift_channel(s, hsv_filter.sAdd) 194 | s = self.shift_channel(s, -hsv_filter.sSub) 195 | v = self.shift_channel(v, hsv_filter.vAdd) 196 | v = self.shift_channel(v, -hsv_filter.vSub) 197 | hsv = cv.merge([h, s, v]) 198 | 199 | # Set minimum and maximum HSV values to display 200 | lower = np.array([hsv_filter.hMin, hsv_filter.sMin, hsv_filter.vMin]) 201 | upper = np.array([hsv_filter.hMax, hsv_filter.sMax, hsv_filter.vMax]) 202 | # Apply the thresholds 203 | mask = cv.inRange(hsv, lower, upper) 204 | result = cv.bitwise_and(hsv, hsv, mask=mask) 205 | 206 | # convert back to BGR for imshow() to display it properly 207 | img = cv.cvtColor(result, cv.COLOR_HSV2BGR) 208 | 209 | return img 210 | 211 | # given an image and a Canny edge filter, apply the filter and return the resulting image. 212 | # if a filter is not supplied, the control GUI trackbars will be used 213 | def apply_edge_filter(self, original_image, edge_filter=None): 214 | # if we haven't been given a defined filter, use the filter values from the GUI 215 | if not edge_filter: 216 | edge_filter = self.get_edge_filter_from_controls() 217 | 218 | kernel = np.ones((edge_filter.kernelSize, edge_filter.kernelSize), np.uint8) 219 | eroded_image = cv.erode(original_image, kernel, iterations=edge_filter.erodeIter) 220 | dilated_image = cv.dilate(eroded_image, kernel, iterations=edge_filter.dilateIter) 221 | 222 | # canny edge detection 223 | result = cv.Canny(dilated_image, edge_filter.canny1, edge_filter.canny2) 224 | 225 | # convert single channel image back to BGR 226 | img = cv.cvtColor(result, cv.COLOR_GRAY2BGR) 227 | 228 | return img 229 | 230 | # apply adjustments to an HSV channel 231 | # https://stackoverflow.com/questions/49697363/shifting-hsv-pixel-values-in-python-using-numpy 232 | def shift_channel(self, c, amount): 233 | if amount > 0: 234 | lim = 255 - amount 235 | c[c >= lim] = 255 236 | c[c < lim] += amount 237 | elif amount < 0: 238 | amount = -amount 239 | lim = amount 240 | c[c <= lim] = 0 241 | c[c > lim] -= amount 242 | return c 243 | 244 | def match_keypoints(self, original_image, patch_size=32): 245 | min_match_count = 5 246 | 247 | orb = cv.ORB_create(edgeThreshold=0, patchSize=patch_size) 248 | keypoints_needle, descriptors_needle = orb.detectAndCompute(self.needle_img, None) 249 | orb2 = cv.ORB_create(edgeThreshold=0, patchSize=patch_size, nfeatures=2000) 250 | keypoints_haystack, descriptors_haystack = orb2.detectAndCompute(original_image, None) 251 | 252 | FLANN_INDEX_LSH = 6 253 | index_params = dict(algorithm=FLANN_INDEX_LSH, 254 | table_number=6, 255 | key_size=12, 256 | multi_probe_level=1) 257 | 258 | search_params = dict(checks=50) 259 | 260 | try: 261 | flann = cv.FlannBasedMatcher(index_params, search_params) 262 | matches = flann.knnMatch(descriptors_needle, descriptors_haystack, k=2) 263 | except cv.error: 264 | return None, None, [], [], None 265 | 266 | # store all the good matches as per Lowe's ratio test. 267 | good = [] 268 | points = [] 269 | 270 | for pair in matches: 271 | if len(pair) == 2: 272 | if pair[0].distance < 0.7*pair[1].distance: 273 | good.append(pair[0]) 274 | 275 | if len(good) > min_match_count: 276 | print('match %03d, kp %03d' % (len(good), len(keypoints_needle))) 277 | for match in good: 278 | points.append(keypoints_haystack[match.trainIdx].pt) 279 | #print(points) 280 | 281 | return keypoints_needle, keypoints_haystack, good, points 282 | 283 | def centeroid(self, point_list): 284 | point_list = np.asarray(point_list, dtype=np.int32) 285 | length = point_list.shape[0] 286 | sum_x = np.sum(point_list[:, 0]) 287 | sum_y = np.sum(point_list[:, 1]) 288 | return [np.floor_divide(sum_x, length), np.floor_divide(sum_y, length)] 289 | -------------------------------------------------------------------------------- /007_canny_edge/windowcapture.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import win32gui, win32ui, win32con 3 | 4 | 5 | class WindowCapture: 6 | 7 | # properties 8 | w = 0 9 | h = 0 10 | hwnd = None 11 | cropped_x = 0 12 | cropped_y = 0 13 | offset_x = 0 14 | offset_y = 0 15 | 16 | # constructor 17 | def __init__(self, window_name=None): 18 | # find the handle for the window we want to capture. 19 | # if no window name is given, capture the entire screen 20 | if window_name is None: 21 | self.hwnd = win32gui.GetDesktopWindow() 22 | else: 23 | self.hwnd = win32gui.FindWindow(None, window_name) 24 | if not self.hwnd: 25 | raise Exception('Window not found: {}'.format(window_name)) 26 | 27 | # get the window size 28 | window_rect = win32gui.GetWindowRect(self.hwnd) 29 | self.w = window_rect[2] - window_rect[0] 30 | self.h = window_rect[3] - window_rect[1] 31 | 32 | # account for the window border and titlebar and cut them off 33 | border_pixels = 8 34 | titlebar_pixels = 30 35 | self.w = self.w - (border_pixels * 2) 36 | self.h = self.h - titlebar_pixels - border_pixels 37 | self.cropped_x = border_pixels 38 | self.cropped_y = titlebar_pixels 39 | 40 | # set the cropped coordinates offset so we can translate screenshot 41 | # images into actual screen positions 42 | self.offset_x = window_rect[0] + self.cropped_x 43 | self.offset_y = window_rect[1] + self.cropped_y 44 | 45 | def get_screenshot(self): 46 | 47 | # get the window image data 48 | wDC = win32gui.GetWindowDC(self.hwnd) 49 | dcObj = win32ui.CreateDCFromHandle(wDC) 50 | cDC = dcObj.CreateCompatibleDC() 51 | dataBitMap = win32ui.CreateBitmap() 52 | dataBitMap.CreateCompatibleBitmap(dcObj, self.w, self.h) 53 | cDC.SelectObject(dataBitMap) 54 | cDC.BitBlt((0, 0), (self.w, self.h), dcObj, (self.cropped_x, self.cropped_y), win32con.SRCCOPY) 55 | 56 | # convert the raw data into a format opencv can read 57 | #dataBitMap.SaveBitmapFile(cDC, 'debug.bmp') 58 | signedIntsArray = dataBitMap.GetBitmapBits(True) 59 | img = np.fromstring(signedIntsArray, dtype='uint8') 60 | img.shape = (self.h, self.w, 4) 61 | 62 | # free resources 63 | dcObj.DeleteDC() 64 | cDC.DeleteDC() 65 | win32gui.ReleaseDC(self.hwnd, wDC) 66 | win32gui.DeleteObject(dataBitMap.GetHandle()) 67 | 68 | # drop the alpha channel, or cv.matchTemplate() will throw an error like: 69 | # error: (-215:Assertion failed) (depth == CV_8U || depth == CV_32F) && type == _templ.type() 70 | # && _img.dims() <= 2 in function 'cv::matchTemplate' 71 | img = img[...,:3] 72 | 73 | # make image C_CONTIGUOUS to avoid errors that look like: 74 | # File ... in draw_rectangles 75 | # TypeError: an integer is required (got type tuple) 76 | # see the discussion here: 77 | # https://github.com/opencv/opencv/issues/14866#issuecomment-580207109 78 | img = np.ascontiguousarray(img) 79 | 80 | return img 81 | 82 | # find the name of the window you're interested in. 83 | # once you have it, update window_capture() 84 | # https://stackoverflow.com/questions/55547940/how-to-get-a-list-of-the-name-of-every-open-window 85 | @staticmethod 86 | def list_window_names(): 87 | def winEnumHandler(hwnd, ctx): 88 | if win32gui.IsWindowVisible(hwnd): 89 | print(hex(hwnd), win32gui.GetWindowText(hwnd)) 90 | win32gui.EnumWindows(winEnumHandler, None) 91 | 92 | # translate a pixel position on a screenshot image to a pixel position on the screen. 93 | # pos = (x, y) 94 | # WARNING: if you move the window being captured after execution is started, this will 95 | # return incorrect coordinates, because the window position is only calculated in 96 | # the __init__ constructor. 97 | def get_screen_position(self, pos): 98 | return (pos[0] + self.offset_x, pos[1] + self.offset_y) 99 | -------------------------------------------------------------------------------- /008_cascade_classifier/cascadeutils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | # reads all the files in the /negative folder and generates neg.txt from them. 5 | # we'll run it manually like this: 6 | # $ python 7 | # Python 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:21:23) [MSC v.1916 32 bit (Intel)] on win32 8 | # Type "help", "copyright", "credits" or "license" for more information. 9 | # >>> from cascadeutils import generate_negative_description_file 10 | # >>> generate_negative_description_file() 11 | # >>> exit() 12 | def generate_negative_description_file(): 13 | # open the output file for writing. will overwrite all existing data in there 14 | with open('neg.txt', 'w') as f: 15 | # loop over all the filenames 16 | for filename in os.listdir('negative'): 17 | f.write('negative/' + filename + '\n') 18 | 19 | # the opencv_annotation executable can be found in opencv/build/x64/vc15/bin 20 | # generate positive description file using: 21 | # $ C:/Users/Ben/learncodebygaming/opencv/build/x64/vc15/bin/opencv_annotation.exe --annotations=pos.txt --images=positive/ 22 | 23 | # You click once to set the upper left corner, then again to set the lower right corner. 24 | # Press 'c' to confirm. 25 | # Or 'd' to undo the previous confirmation. 26 | # When done, click 'n' to move to the next image. 27 | # Press 'esc' to exit. 28 | # Will exit automatically when you've annotated all of the images 29 | 30 | # generate positive samples from the annotations to get a vector file using: 31 | # $ C:/Users/Ben/learncodebygaming/opencv/build/x64/vc15/bin/opencv_createsamples.exe -info pos.txt -w 24 -h 24 -num 1000 -vec pos.vec 32 | 33 | # train the cascade classifier model using: 34 | # $ C:/Users/Ben/learncodebygaming/opencv/build/x64/vc15/bin/opencv_traincascade.exe -data cascade/ -vec pos.vec -bg neg.txt -numPos 200 -numNeg 100 -numStages 10 -w 24 -h 24 35 | 36 | # my final classifier training arguments: 37 | # $ C:/Users/Ben/learncodebygaming/opencv/build/x64/vc15/bin/opencv_traincascade.exe -data cascade/ -vec pos.vec -bg neg.txt -precalcValBufSize 6000 -precalcIdxBufSize 6000 -numPos 200 -numNeg 1000 -numStages 12 -w 24 -h 24 -maxFalseAlarmRate 0.4 -minHitRate 0.999 38 | -------------------------------------------------------------------------------- /008_cascade_classifier/edgefilter.py: -------------------------------------------------------------------------------- 1 | 2 | # custom data structure to hold the state of a Canny edge filter 3 | class EdgeFilter: 4 | 5 | def __init__(self, kernelSize=None, erodeIter=None, dilateIter=None, canny1=None, 6 | canny2=None): 7 | self.kernelSize = kernelSize 8 | self.erodeIter = erodeIter 9 | self.dilateIter = dilateIter 10 | self.canny1 = canny1 11 | self.canny2 = canny2 12 | -------------------------------------------------------------------------------- /008_cascade_classifier/hsvfilter.py: -------------------------------------------------------------------------------- 1 | 2 | # custom data structure to hold the state of an HSV filter 3 | class HsvFilter: 4 | 5 | def __init__(self, hMin=None, sMin=None, vMin=None, hMax=None, sMax=None, vMax=None, 6 | sAdd=None, sSub=None, vAdd=None, vSub=None): 7 | self.hMin = hMin 8 | self.sMin = sMin 9 | self.vMin = vMin 10 | self.hMax = hMax 11 | self.sMax = sMax 12 | self.vMax = vMax 13 | self.sAdd = sAdd 14 | self.sSub = sSub 15 | self.vAdd = vAdd 16 | self.vSub = vSub 17 | -------------------------------------------------------------------------------- /008_cascade_classifier/main.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | import os 4 | from time import time 5 | from windowcapture import WindowCapture 6 | from vision import Vision 7 | 8 | # Change the working directory to the folder this script is in. 9 | # Doing this because I'll be putting the files from each video in their own folder on GitHub 10 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 11 | 12 | 13 | # initialize the WindowCapture class 14 | wincap = WindowCapture('Albion Online Client') 15 | 16 | # load the trained model 17 | cascade_limestone = cv.CascadeClassifier('limestone_model_final.xml') 18 | # load an empty Vision class 19 | vision_limestone = Vision(None) 20 | 21 | loop_time = time() 22 | while(True): 23 | 24 | # get an updated image of the game 25 | screenshot = wincap.get_screenshot() 26 | 27 | # do object detection 28 | rectangles = cascade_limestone.detectMultiScale(screenshot) 29 | 30 | # draw the detection results onto the original image 31 | detection_image = vision_limestone.draw_rectangles(screenshot, rectangles) 32 | 33 | # display the images 34 | cv.imshow('Matches', detection_image) 35 | 36 | # debug the loop rate 37 | print('FPS {}'.format(1 / (time() - loop_time))) 38 | loop_time = time() 39 | 40 | # press 'q' with the output window focused to exit. 41 | # press 'f' to save screenshot as a positive image, press 'd' to 42 | # save as a negative image. 43 | # waits 1 ms every loop to process key presses 44 | key = cv.waitKey(1) 45 | if key == ord('q'): 46 | cv.destroyAllWindows() 47 | break 48 | elif key == ord('f'): 49 | cv.imwrite('positive/{}.jpg'.format(loop_time), screenshot) 50 | elif key == ord('d'): 51 | cv.imwrite('negative/{}.jpg'.format(loop_time), screenshot) 52 | 53 | print('Done.') 54 | -------------------------------------------------------------------------------- /008_cascade_classifier/neg.txt: -------------------------------------------------------------------------------- 1 | negative/1596724705.5517466.jpg 2 | negative/1596724746.4305139.jpg 3 | negative/1596724758.379512.jpg 4 | negative/1596724769.5195115.jpg 5 | negative/1596724785.5650678.jpg 6 | negative/1596724793.0220716.jpg 7 | negative/1596724805.2095428.jpg 8 | negative/1596724819.1929052.jpg 9 | negative/1596724834.3249273.jpg 10 | negative/1596724847.025956.jpg 11 | negative/1596724898.553667.jpg 12 | negative/1596724967.346105.jpg 13 | negative/1596725038.330387.jpg 14 | negative/1596725080.9118962.jpg 15 | negative/1596725170.84625.jpg 16 | negative/1596725187.592096.jpg 17 | negative/1596725220.033521.jpg 18 | negative/1596725264.334212.jpg 19 | negative/1596725284.118124.jpg 20 | negative/1596725349.903286.jpg 21 | negative/1596725431.8746161.jpg 22 | negative/1596725461.0213678.jpg 23 | negative/1596725515.069354.jpg 24 | negative/1596725582.226287.jpg 25 | negative/1596725745.2315128.jpg 26 | negative/1596725756.951403.jpg 27 | negative/1596725765.261232.jpg 28 | negative/1596725775.2572083.jpg 29 | negative/1596725821.9031878.jpg 30 | negative/1596725831.8041875.jpg 31 | negative/1596725920.4239123.jpg 32 | negative/1596725939.2339938.jpg 33 | negative/1596725954.4924223.jpg 34 | negative/1596726002.0213919.jpg 35 | negative/1596726049.6788344.jpg 36 | negative/1596726062.6242409.jpg 37 | negative/1596726097.9022322.jpg 38 | negative/1596726142.49426.jpg 39 | negative/1596726155.9890828.jpg 40 | negative/1596726182.5506525.jpg 41 | negative/1596726217.5782723.jpg 42 | negative/1596726234.8375776.jpg 43 | negative/1596726246.9830372.jpg 44 | negative/1596726292.729684.jpg 45 | negative/1596726301.8822803.jpg 46 | negative/1596726313.814556.jpg 47 | negative/1596726332.8304229.jpg 48 | negative/1596726345.7047927.jpg 49 | negative/1596726465.2339375.jpg 50 | negative/1596726487.8134801.jpg 51 | negative/1596726513.185823.jpg 52 | negative/1596726640.6113465.jpg 53 | negative/1596726674.816247.jpg 54 | negative/1596726689.225606.jpg 55 | negative/1596726697.8552132.jpg 56 | negative/1596726708.121546.jpg 57 | negative/1596726717.2575495.jpg 58 | negative/1596726724.903866.jpg 59 | negative/1596726738.219499.jpg 60 | negative/1596726750.0375574.jpg 61 | negative/1596726757.5366132.jpg 62 | negative/1596726822.4534585.jpg 63 | negative/1596726831.802462.jpg 64 | negative/1596726846.4050379.jpg 65 | negative/1596726875.157068.jpg 66 | negative/1596726888.3905616.jpg 67 | negative/1596726918.442663.jpg 68 | negative/1596726939.1568878.jpg 69 | negative/1596726959.2844813.jpg 70 | negative/1596726975.4137285.jpg 71 | negative/1596727112.1766517.jpg 72 | negative/1596727124.6547341.jpg 73 | negative/1596727149.089741.jpg 74 | negative/1596727157.397738.jpg 75 | negative/1596727168.9037406.jpg 76 | negative/1596727315.7115467.jpg 77 | negative/1596727333.9426105.jpg 78 | negative/1596727355.8319664.jpg 79 | negative/1596727372.243553.jpg 80 | negative/1596727384.199091.jpg 81 | negative/1596727392.9071019.jpg 82 | negative/1596727402.9000127.jpg 83 | negative/1596727416.8854368.jpg 84 | negative/1596727438.8985887.jpg 85 | negative/1596727452.5146.jpg 86 | negative/1596727470.7761557.jpg 87 | negative/1596727485.4515324.jpg 88 | negative/1596727494.702687.jpg 89 | negative/1596727506.0782263.jpg 90 | negative/1596727510.119744.jpg 91 | negative/1596727522.5037456.jpg 92 | negative/1596727529.4377453.jpg 93 | negative/1596727538.2637434.jpg 94 | negative/1596727547.951745.jpg 95 | negative/1596727558.6709976.jpg 96 | negative/1596727568.9909968.jpg 97 | negative/1596727581.266752.jpg 98 | negative/1596727595.2124581.jpg 99 | negative/1596727609.8064606.jpg 100 | negative/1596727646.9046378.jpg 101 | negative/1596727657.9977558.jpg 102 | negative/1596727863.9691815.jpg 103 | negative/1597344765.5637424.jpg 104 | -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596724705.5517466.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596724705.5517466.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596724746.4305139.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596724746.4305139.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596724758.379512.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596724758.379512.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596724769.5195115.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596724769.5195115.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596724785.5650678.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596724785.5650678.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596724793.0220716.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596724793.0220716.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596724805.2095428.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596724805.2095428.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596724819.1929052.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596724819.1929052.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596724834.3249273.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596724834.3249273.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596724847.025956.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596724847.025956.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596724898.553667.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596724898.553667.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596724967.346105.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596724967.346105.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725038.330387.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725038.330387.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725080.9118962.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725080.9118962.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725170.84625.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725170.84625.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725187.592096.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725187.592096.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725220.033521.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725220.033521.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725264.334212.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725264.334212.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725284.118124.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725284.118124.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725349.903286.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725349.903286.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725431.8746161.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725431.8746161.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725461.0213678.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725461.0213678.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725515.069354.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725515.069354.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725582.226287.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725582.226287.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725745.2315128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725745.2315128.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725756.951403.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725756.951403.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725765.261232.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725765.261232.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725775.2572083.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725775.2572083.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725821.9031878.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725821.9031878.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725831.8041875.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725831.8041875.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725920.4239123.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725920.4239123.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725939.2339938.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725939.2339938.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596725954.4924223.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596725954.4924223.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726002.0213919.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726002.0213919.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726049.6788344.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726049.6788344.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726062.6242409.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726062.6242409.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726097.9022322.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726097.9022322.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726142.49426.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726142.49426.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726155.9890828.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726155.9890828.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726182.5506525.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726182.5506525.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726217.5782723.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726217.5782723.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726234.8375776.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726234.8375776.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726246.9830372.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726246.9830372.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726292.729684.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726292.729684.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726301.8822803.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726301.8822803.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726313.814556.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726313.814556.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726332.8304229.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726332.8304229.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726345.7047927.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726345.7047927.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726465.2339375.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726465.2339375.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726487.8134801.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726487.8134801.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726513.185823.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726513.185823.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726640.6113465.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726640.6113465.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726674.816247.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726674.816247.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726689.225606.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726689.225606.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726697.8552132.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726697.8552132.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726708.121546.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726708.121546.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726717.2575495.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726717.2575495.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726724.903866.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726724.903866.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726738.219499.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726738.219499.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726750.0375574.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726750.0375574.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726757.5366132.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726757.5366132.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726822.4534585.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726822.4534585.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726831.802462.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726831.802462.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726846.4050379.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726846.4050379.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726875.157068.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726875.157068.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726888.3905616.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726888.3905616.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726918.442663.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726918.442663.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726939.1568878.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726939.1568878.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726959.2844813.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726959.2844813.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596726975.4137285.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596726975.4137285.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727112.1766517.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727112.1766517.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727124.6547341.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727124.6547341.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727149.089741.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727149.089741.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727157.397738.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727157.397738.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727168.9037406.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727168.9037406.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727315.7115467.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727315.7115467.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727333.9426105.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727333.9426105.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727355.8319664.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727355.8319664.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727372.243553.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727372.243553.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727384.199091.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727384.199091.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727392.9071019.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727392.9071019.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727402.9000127.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727402.9000127.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727416.8854368.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727416.8854368.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727438.8985887.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727438.8985887.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727452.5146.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727452.5146.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727470.7761557.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727470.7761557.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727485.4515324.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727485.4515324.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727494.702687.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727494.702687.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727506.0782263.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727506.0782263.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727510.119744.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727510.119744.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727522.5037456.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727522.5037456.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727529.4377453.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727529.4377453.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727538.2637434.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727538.2637434.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727547.951745.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727547.951745.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727558.6709976.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727558.6709976.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727568.9909968.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727568.9909968.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727581.266752.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727581.266752.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727595.2124581.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727595.2124581.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727609.8064606.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727609.8064606.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727646.9046378.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727646.9046378.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727657.9977558.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727657.9977558.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1596727863.9691815.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1596727863.9691815.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/negative/1597344765.5637424.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/negative/1597344765.5637424.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/pos.txt: -------------------------------------------------------------------------------- 1 | positive/1596724594.7233658.jpg 2 899 378 127 120 923 1 91 48 2 | positive/1596724628.6711628.jpg 2 890 497 144 133 929 44 94 68 3 | positive/1596724646.3438368.jpg 2 581 546 150 150 720 67 91 72 4 | positive/1596724654.9572363.jpg 3 920 129 100 86 965 477 126 130 1074 608 133 133 5 | positive/1596724663.8320026.jpg 5 868 12 88 69 890 288 112 101 984 390 105 97 1173 647 108 124 1173 647 108 124 6 | positive/1596724674.324035.jpg 3 808 153 98 89 889 242 91 80 1023 446 104 102 7 | positive/1596724688.3025408.jpg 3 997 42 94 72 1081 107 87 69 1250 268 67 80 8 | positive/1596724716.7686796.jpg 2 446 498 151 123 357 595 145 120 9 | positive/1596724725.0338614.jpg 2 451 428 143 116 370 506 124 132 10 | positive/1596724731.2288644.jpg 3 492 190 115 88 423 238 104 96 1043 506 121 89 11 | positive/1596724862.2278488.jpg 4 341 82 99 84 291 216 121 85 656 34 86 72 939 248 100 94 12 | positive/1596724877.1428533.jpg 4 550 303 106 98 514 484 136 116 897 225 113 95 1290 521 131 140 13 | positive/1596724932.9526978.jpg 3 256 240 117 64 439 621 136 118 1439 416 133 93 14 | positive/1596724981.3780077.jpg 2 553 167 97 84 650 168 101 83 15 | positive/1596725001.129405.jpg 1 234 283 142 79 16 | positive/1596725013.2133152.jpg 2 537 130 116 85 574 213 104 88 17 | positive/1596725024.112249.jpg 3 368 426 138 103 816 514 154 145 894 659 133 155 18 | positive/1596725056.3050916.jpg 2 585 40 98 63 970 95 46 56 19 | positive/1596725091.287897.jpg 1 822 99 66 79 20 | positive/1596725112.003226.jpg 2 616 69 93 77 1153 346 102 107 21 | positive/1596725119.7605214.jpg 2 359 364 136 98 957 179 111 94 22 | positive/1596725133.894812.jpg 1 599 87 107 82 23 | positive/1596725145.224427.jpg 1 105 346 139 90 24 | positive/1596725160.9995346.jpg 3 408 143 126 91 616 100 99 80 720 94 91 70 25 | positive/1596725207.3700407.jpg 3 368 157 107 80 970 136 97 85 511 151 87 87 26 | positive/1596725236.9265978.jpg 2 953 133 90 83 1141 75 90 65 27 | positive/1596725319.6801374.jpg 3 303 422 123 113 505 153 91 63 1238 46 107 61 28 | positive/1596725331.6399002.jpg 2 1153 181 104 91 889 652 158 150 29 | positive/1596725365.0860987.jpg 1 915 471 114 90 30 | positive/1596725448.8122423.jpg 1 422 314 103 98 31 | positive/1596725482.437604.jpg 2 608 517 147 124 649 652 146 141 32 | positive/1596725504.2595575.jpg 1 492 198 109 88 33 | positive/1596725531.0743005.jpg 1 1109 149 80 90 34 | positive/1596725547.959211.jpg 5 351 100 110 76 395 176 93 87 86 309 137 96 621 560 143 127 1091 1 96 55 35 | positive/1596725557.5062091.jpg 4 492 278 113 101 202 574 149 113 546 387 107 103 1325 137 88 76 36 | positive/1596725570.5192885.jpg 1 751 491 118 108 37 | positive/1596725594.7959447.jpg 2 1242 492 137 119 849 588 127 148 38 | positive/1596725612.3280354.jpg 2 957 481 125 130 1070 604 129 145 39 | positive/1596725623.4045992.jpg 4 861 277 110 105 952 366 112 116 1124 625 118 134 839 5 102 76 40 | positive/1596725636.845524.jpg 2 939 118 101 77 904 657 149 159 41 | positive/1596725656.2454305.jpg 3 303 423 128 115 1288 579 122 126 512 0 95 60 42 | positive/1596725673.0234246.jpg 2 350 371 108 107 1312 519 119 130 43 | positive/1596725683.22975.jpg 3 917 144 104 95 952 503 141 142 1073 625 137 164 44 | positive/1596725691.7093291.jpg 3 864 141 102 86 951 214 94 98 1104 417 98 106 45 | positive/1596725703.587616.jpg 2 1240 176 114 97 1347 258 112 98 46 | positive/1596725715.009877.jpg 2 366 504 149 112 273 581 136 139 47 | positive/1596725732.5384405.jpg 3 816 552 113 93 286 218 122 84 196 269 121 99 48 | positive/1596725787.8061755.jpg 2 1038 115 96 87 1128 189 96 86 49 | positive/1596725809.2862072.jpg 4 706 61 91 82 967 477 115 124 1118 427 121 82 1066 12 77 74 50 | positive/1596725850.87147.jpg 4 445 153 97 90 852 92 90 87 823 595 125 99 629 652 134 126 51 | positive/1596725863.4828084.jpg 3 168 463 130 118 399 176 102 64 1164 61 114 69 52 | positive/1596725874.1538086.jpg 3 27 256 121 67 142 655 142 111 1214 437 149 97 53 | positive/1596725886.3860302.jpg 4 375 45 90 74 720 22 86 68 1307 428 154 137 538 688 181 117 54 | positive/1596725967.164775.jpg 2 500 634 132 150 1470 499 129 152 55 | positive/1596725983.5232484.jpg 1 210 404 119 121 56 | positive/1596726015.8447444.jpg 1 997 512 147 113 57 | positive/1596726023.5497692.jpg 2 945 512 134 110 1112 293 129 89 58 | positive/1596726034.82377.jpg 3 642 500 116 95 64 239 124 88 156 182 127 86 59 | positive/1596726085.3942282.jpg 1 1095 312 81 91 60 | positive/1596726109.9420922.jpg 2 873 562 151 115 504 662 137 129 61 | positive/1596726116.478095.jpg 3 751 563 127 116 865 494 137 97 1077 468 146 114 62 | positive/1596726192.4596026.jpg 2 173 356 142 113 256 226 144 103 63 | positive/1596726416.5257359.jpg 1 1307 229 76 84 64 | positive/1596726430.6089704.jpg 2 361 528 133 121 454 436 140 132 65 | positive/1596726442.6422012.jpg 1 214 297 126 63 66 | positive/1596726453.5657032.jpg 2 1076 340 116 93 899 570 125 116 67 | positive/1596726474.629971.jpg 2 612 480 144 139 673 619 133 139 68 | positive/1596726526.9982874.jpg 2 791 60 95 81 235 35 103 74 69 | positive/1596726536.5520952.jpg 4 526 134 103 94 502 284 120 96 842 78 103 87 1181 312 119 111 70 | positive/1596726552.1565392.jpg 3 638 445 120 128 1023 355 118 119 618 677 142 139 71 | positive/1596726566.051003.jpg 2 529 576 127 150 673 73 98 84 72 | positive/1596726579.2008128.jpg 3 921 128 98 92 955 471 137 142 1068 598 140 158 73 | positive/1596726626.1602285.jpg 2 495 426 141 119 410 506 139 133 74 | positive/1596726654.1444535.jpg 3 522 287 126 107 445 347 118 116 1137 665 129 118 75 | positive/1596726662.3032484.jpg 3 627 116 107 79 564 162 103 84 1165 390 107 86 76 | positive/1596726774.8108573.jpg 4 343 332 116 97 1236 59 97 87 1246 157 99 94 1502 250 89 100 77 | positive/1596726788.6514382.jpg 3 889 60 99 74 879 153 78 91 1094 247 101 91 78 | positive/1596726803.447783.jpg 1 203 198 129 80 79 | positive/1596726860.5150664.jpg 1 1191 263 102 106 80 | positive/1596726901.542625.jpg 1 994 174 87 91 81 | positive/1596726926.4286637.jpg 1 993 10 100 90 82 | positive/1596726947.2468882.jpg 1 1000 106 94 94 83 | positive/1596726987.308508.jpg 1 1096 302 117 110 84 | positive/1596727004.7786236.jpg 5 108 145 128 83 133 231 109 89 925 25 90 74 1481 183 78 95 297 664 160 135 85 | positive/1596727013.9736161.jpg 5 512 148 110 96 560 244 106 95 264 388 142 118 884 665 132 155 1279 49 87 64 86 | positive/1596727037.3830843.jpg 1 816 596 137 150 87 | positive/1596727055.9510057.jpg 4 552 233 113 107 528 409 133 117 895 177 105 86 1274 444 118 131 88 | positive/1596727069.049649.jpg 2 945 107 89 84 884 621 169 176 89 | positive/1596727079.762559.jpg 3 904 137 119 96 943 489 146 153 1059 617 145 178 90 | positive/1596727099.340134.jpg 2 474 592 143 143 558 500 148 147 91 | positive/1596727137.8729897.jpg 3 881 29 92 73 835 68 89 78 1404 263 104 65 92 | positive/1596727178.5962224.jpg 2 1243 245 127 85 1111 447 122 106 93 | positive/1596727189.9316707.jpg 2 145 302 132 53 888 66 53 75 94 | positive/1596727203.982804.jpg 4 170 285 144 105 66 352 145 104 712 668 127 113 1441 308 133 76 95 | positive/1596727229.6601653.jpg 3 853 143 101 91 931 222 95 95 1086 427 98 108 96 | positive/1596727241.441173.jpg 2 966 54 94 76 941 525 149 146 97 | positive/1596727249.654685.jpg 2 778 8 92 76 680 441 136 131 98 | positive/1596727259.3076847.jpg 1 1161 365 136 120 99 | positive/1596727272.6673214.jpg 2 1141 153 101 72 1016 189 102 93 100 | positive/1596727293.8035266.jpg 4 666 93 105 90 1047 40 78 86 1088 483 132 105 920 538 130 123 101 | positive/1596727755.6122937.jpg 3 973 258 132 112 424 469 150 143 361 226 122 89 102 | positive/1596727802.904577.jpg 2 958 373 137 100 752 612 128 125 103 | positive/1596727822.1659546.jpg 4 666 66 93 93 329 123 115 90 290 261 122 106 963 287 101 118 104 | positive/1596727837.7473912.jpg 2 889 78 112 84 840 580 148 162 105 | positive/1596727848.3987978.jpg 3 946 27 93 80 982 302 119 122 1089 402 116 129 106 | -------------------------------------------------------------------------------- /008_cascade_classifier/pos.vec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/pos.vec -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596724594.7233658.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596724594.7233658.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596724628.6711628.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596724628.6711628.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596724646.3438368.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596724646.3438368.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596724654.9572363.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596724654.9572363.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596724663.8320026.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596724663.8320026.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596724674.324035.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596724674.324035.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596724688.3025408.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596724688.3025408.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596724716.7686796.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596724716.7686796.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596724725.0338614.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596724725.0338614.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596724731.2288644.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596724731.2288644.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596724862.2278488.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596724862.2278488.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596724877.1428533.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596724877.1428533.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596724932.9526978.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596724932.9526978.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596724981.3780077.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596724981.3780077.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725001.129405.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725001.129405.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725013.2133152.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725013.2133152.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725024.112249.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725024.112249.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725056.3050916.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725056.3050916.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725091.287897.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725091.287897.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725112.003226.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725112.003226.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725119.7605214.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725119.7605214.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725133.894812.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725133.894812.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725145.224427.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725145.224427.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725160.9995346.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725160.9995346.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725207.3700407.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725207.3700407.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725236.9265978.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725236.9265978.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725319.6801374.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725319.6801374.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725331.6399002.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725331.6399002.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725365.0860987.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725365.0860987.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725448.8122423.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725448.8122423.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725482.437604.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725482.437604.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725504.2595575.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725504.2595575.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725531.0743005.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725531.0743005.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725547.959211.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725547.959211.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725557.5062091.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725557.5062091.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725570.5192885.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725570.5192885.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725594.7959447.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725594.7959447.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725612.3280354.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725612.3280354.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725623.4045992.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725623.4045992.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725636.845524.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725636.845524.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725656.2454305.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725656.2454305.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725673.0234246.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725673.0234246.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725683.22975.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725683.22975.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725691.7093291.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725691.7093291.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725703.587616.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725703.587616.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725715.009877.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725715.009877.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725732.5384405.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725732.5384405.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725787.8061755.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725787.8061755.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725809.2862072.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725809.2862072.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725850.87147.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725850.87147.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725863.4828084.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725863.4828084.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725874.1538086.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725874.1538086.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725886.3860302.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725886.3860302.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725967.164775.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725967.164775.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596725983.5232484.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596725983.5232484.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726015.8447444.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726015.8447444.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726023.5497692.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726023.5497692.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726034.82377.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726034.82377.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726085.3942282.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726085.3942282.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726109.9420922.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726109.9420922.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726116.478095.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726116.478095.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726192.4596026.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726192.4596026.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726416.5257359.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726416.5257359.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726430.6089704.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726430.6089704.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726442.6422012.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726442.6422012.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726453.5657032.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726453.5657032.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726474.629971.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726474.629971.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726526.9982874.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726526.9982874.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726536.5520952.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726536.5520952.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726552.1565392.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726552.1565392.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726566.051003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726566.051003.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726579.2008128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726579.2008128.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726626.1602285.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726626.1602285.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726654.1444535.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726654.1444535.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726662.3032484.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726662.3032484.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726774.8108573.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726774.8108573.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726788.6514382.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726788.6514382.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726803.447783.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726803.447783.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726860.5150664.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726860.5150664.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726901.542625.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726901.542625.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726926.4286637.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726926.4286637.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726947.2468882.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726947.2468882.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596726987.308508.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596726987.308508.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727004.7786236.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727004.7786236.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727013.9736161.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727013.9736161.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727037.3830843.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727037.3830843.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727055.9510057.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727055.9510057.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727069.049649.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727069.049649.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727079.762559.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727079.762559.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727099.340134.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727099.340134.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727137.8729897.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727137.8729897.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727178.5962224.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727178.5962224.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727189.9316707.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727189.9316707.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727203.982804.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727203.982804.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727229.6601653.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727229.6601653.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727241.441173.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727241.441173.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727249.654685.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727249.654685.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727259.3076847.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727259.3076847.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727272.6673214.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727272.6673214.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727293.8035266.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727293.8035266.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727755.6122937.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727755.6122937.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727802.904577.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727802.904577.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727822.1659546.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727822.1659546.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727837.7473912.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727837.7473912.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1596727848.3987978.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1596727848.3987978.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/positive/1597344669.960422.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/008_cascade_classifier/positive/1597344669.960422.jpg -------------------------------------------------------------------------------- /008_cascade_classifier/vision.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | from hsvfilter import HsvFilter 4 | from edgefilter import EdgeFilter 5 | 6 | 7 | class Vision: 8 | # constants 9 | TRACKBAR_WINDOW = "Trackbars" 10 | 11 | # properties 12 | needle_img = None 13 | needle_w = 0 14 | needle_h = 0 15 | method = None 16 | 17 | # constructor 18 | def __init__(self, needle_img_path, method=cv.TM_CCOEFF_NORMED): 19 | if needle_img_path: 20 | # load the image we're trying to match 21 | # https://docs.opencv.org/4.2.0/d4/da8/group__imgcodecs.html 22 | self.needle_img = cv.imread(needle_img_path, cv.IMREAD_UNCHANGED) 23 | 24 | # Save the dimensions of the needle image 25 | self.needle_w = self.needle_img.shape[1] 26 | self.needle_h = self.needle_img.shape[0] 27 | 28 | # There are 6 methods to choose from: 29 | # TM_CCOEFF, TM_CCOEFF_NORMED, TM_CCORR, TM_CCORR_NORMED, TM_SQDIFF, TM_SQDIFF_NORMED 30 | self.method = method 31 | 32 | def find(self, haystack_img, threshold=0.5, max_results=10): 33 | # run the OpenCV algorithm 34 | result = cv.matchTemplate(haystack_img, self.needle_img, self.method) 35 | 36 | # Get the all the positions from the match result that exceed our threshold 37 | locations = np.where(result >= threshold) 38 | locations = list(zip(*locations[::-1])) 39 | #print(locations) 40 | 41 | # if we found no results, return now. this reshape of the empty array allows us to 42 | # concatenate together results without causing an error 43 | if not locations: 44 | return np.array([], dtype=np.int32).reshape(0, 4) 45 | 46 | # You'll notice a lot of overlapping rectangles get drawn. We can eliminate those redundant 47 | # locations by using groupRectangles(). 48 | # First we need to create the list of [x, y, w, h] rectangles 49 | rectangles = [] 50 | for loc in locations: 51 | rect = [int(loc[0]), int(loc[1]), self.needle_w, self.needle_h] 52 | # Add every box to the list twice in order to retain single (non-overlapping) boxes 53 | rectangles.append(rect) 54 | rectangles.append(rect) 55 | # Apply group rectangles. 56 | # The groupThreshold parameter should usually be 1. If you put it at 0 then no grouping is 57 | # done. If you put it at 2 then an object needs at least 3 overlapping rectangles to appear 58 | # in the result. I've set eps to 0.5, which is: 59 | # "Relative difference between sides of the rectangles to merge them into a group." 60 | rectangles, weights = cv.groupRectangles(rectangles, groupThreshold=1, eps=0.5) 61 | #print(rectangles) 62 | 63 | # for performance reasons, return a limited number of results. 64 | # these aren't necessarily the best results. 65 | if len(rectangles) > max_results: 66 | print('Warning: too many results, raise the threshold.') 67 | rectangles = rectangles[:max_results] 68 | 69 | return rectangles 70 | 71 | # given a list of [x, y, w, h] rectangles returned by find(), convert those into a list of 72 | # [x, y] positions in the center of those rectangles where we can click on those found items 73 | def get_click_points(self, rectangles): 74 | points = [] 75 | 76 | # Loop over all the rectangles 77 | for (x, y, w, h) in rectangles: 78 | # Determine the center position 79 | center_x = x + int(w/2) 80 | center_y = y + int(h/2) 81 | # Save the points 82 | points.append((center_x, center_y)) 83 | 84 | return points 85 | 86 | # given a list of [x, y, w, h] rectangles and a canvas image to draw on, return an image with 87 | # all of those rectangles drawn 88 | def draw_rectangles(self, haystack_img, rectangles): 89 | # these colors are actually BGR 90 | line_color = (0, 255, 0) 91 | line_type = cv.LINE_4 92 | 93 | for (x, y, w, h) in rectangles: 94 | # determine the box positions 95 | top_left = (x, y) 96 | bottom_right = (x + w, y + h) 97 | # draw the box 98 | cv.rectangle(haystack_img, top_left, bottom_right, line_color, lineType=line_type) 99 | 100 | return haystack_img 101 | 102 | # given a list of [x, y] positions and a canvas image to draw on, return an image with all 103 | # of those click points drawn on as crosshairs 104 | def draw_crosshairs(self, haystack_img, points): 105 | # these colors are actually BGR 106 | marker_color = (255, 0, 255) 107 | marker_type = cv.MARKER_CROSS 108 | 109 | for (center_x, center_y) in points: 110 | # draw the center point 111 | cv.drawMarker(haystack_img, (center_x, center_y), marker_color, marker_type) 112 | 113 | return haystack_img 114 | 115 | # create gui window with controls for adjusting arguments in real-time 116 | def init_control_gui(self): 117 | cv.namedWindow(self.TRACKBAR_WINDOW, cv.WINDOW_NORMAL) 118 | cv.resizeWindow(self.TRACKBAR_WINDOW, 350, 700) 119 | 120 | # required callback. we'll be using getTrackbarPos() to do lookups 121 | # instead of using the callback. 122 | def nothing(position): 123 | pass 124 | 125 | # create trackbars for bracketing. 126 | # OpenCV scale for HSV is H: 0-179, S: 0-255, V: 0-255 127 | cv.createTrackbar('HMin', self.TRACKBAR_WINDOW, 0, 179, nothing) 128 | cv.createTrackbar('SMin', self.TRACKBAR_WINDOW, 0, 255, nothing) 129 | cv.createTrackbar('VMin', self.TRACKBAR_WINDOW, 0, 255, nothing) 130 | cv.createTrackbar('HMax', self.TRACKBAR_WINDOW, 0, 179, nothing) 131 | cv.createTrackbar('SMax', self.TRACKBAR_WINDOW, 0, 255, nothing) 132 | cv.createTrackbar('VMax', self.TRACKBAR_WINDOW, 0, 255, nothing) 133 | # Set default value for Max HSV trackbars 134 | cv.setTrackbarPos('HMax', self.TRACKBAR_WINDOW, 179) 135 | cv.setTrackbarPos('SMax', self.TRACKBAR_WINDOW, 255) 136 | cv.setTrackbarPos('VMax', self.TRACKBAR_WINDOW, 255) 137 | 138 | # trackbars for increasing/decreasing saturation and value 139 | cv.createTrackbar('SAdd', self.TRACKBAR_WINDOW, 0, 255, nothing) 140 | cv.createTrackbar('SSub', self.TRACKBAR_WINDOW, 0, 255, nothing) 141 | cv.createTrackbar('VAdd', self.TRACKBAR_WINDOW, 0, 255, nothing) 142 | cv.createTrackbar('VSub', self.TRACKBAR_WINDOW, 0, 255, nothing) 143 | 144 | # trackbars for edge creation 145 | cv.createTrackbar('KernelSize', self.TRACKBAR_WINDOW, 1, 30, nothing) 146 | cv.createTrackbar('ErodeIter', self.TRACKBAR_WINDOW, 1, 5, nothing) 147 | cv.createTrackbar('DilateIter', self.TRACKBAR_WINDOW, 1, 5, nothing) 148 | cv.createTrackbar('Canny1', self.TRACKBAR_WINDOW, 0, 200, nothing) 149 | cv.createTrackbar('Canny2', self.TRACKBAR_WINDOW, 0, 500, nothing) 150 | # Set default value for Canny trackbars 151 | cv.setTrackbarPos('KernelSize', self.TRACKBAR_WINDOW, 5) 152 | cv.setTrackbarPos('Canny1', self.TRACKBAR_WINDOW, 100) 153 | cv.setTrackbarPos('Canny2', self.TRACKBAR_WINDOW, 200) 154 | 155 | # returns an HSV filter object based on the control GUI values 156 | def get_hsv_filter_from_controls(self): 157 | # Get current positions of all trackbars 158 | hsv_filter = HsvFilter() 159 | hsv_filter.hMin = cv.getTrackbarPos('HMin', self.TRACKBAR_WINDOW) 160 | hsv_filter.sMin = cv.getTrackbarPos('SMin', self.TRACKBAR_WINDOW) 161 | hsv_filter.vMin = cv.getTrackbarPos('VMin', self.TRACKBAR_WINDOW) 162 | hsv_filter.hMax = cv.getTrackbarPos('HMax', self.TRACKBAR_WINDOW) 163 | hsv_filter.sMax = cv.getTrackbarPos('SMax', self.TRACKBAR_WINDOW) 164 | hsv_filter.vMax = cv.getTrackbarPos('VMax', self.TRACKBAR_WINDOW) 165 | hsv_filter.sAdd = cv.getTrackbarPos('SAdd', self.TRACKBAR_WINDOW) 166 | hsv_filter.sSub = cv.getTrackbarPos('SSub', self.TRACKBAR_WINDOW) 167 | hsv_filter.vAdd = cv.getTrackbarPos('VAdd', self.TRACKBAR_WINDOW) 168 | hsv_filter.vSub = cv.getTrackbarPos('VSub', self.TRACKBAR_WINDOW) 169 | return hsv_filter 170 | 171 | # returns a Canny edge filter object based on the control GUI values 172 | def get_edge_filter_from_controls(self): 173 | # Get current positions of all trackbars 174 | edge_filter = EdgeFilter() 175 | edge_filter.kernelSize = cv.getTrackbarPos('KernelSize', self.TRACKBAR_WINDOW) 176 | edge_filter.erodeIter = cv.getTrackbarPos('ErodeIter', self.TRACKBAR_WINDOW) 177 | edge_filter.dilateIter = cv.getTrackbarPos('DilateIter', self.TRACKBAR_WINDOW) 178 | edge_filter.canny1 = cv.getTrackbarPos('Canny1', self.TRACKBAR_WINDOW) 179 | edge_filter.canny2 = cv.getTrackbarPos('Canny2', self.TRACKBAR_WINDOW) 180 | return edge_filter 181 | 182 | # given an image and an HSV filter, apply the filter and return the resulting image. 183 | # if a filter is not supplied, the control GUI trackbars will be used 184 | def apply_hsv_filter(self, original_image, hsv_filter=None): 185 | # convert image to HSV 186 | hsv = cv.cvtColor(original_image, cv.COLOR_BGR2HSV) 187 | 188 | # if we haven't been given a defined filter, use the filter values from the GUI 189 | if not hsv_filter: 190 | hsv_filter = self.get_hsv_filter_from_controls() 191 | 192 | # add/subtract saturation and value 193 | h, s, v = cv.split(hsv) 194 | s = self.shift_channel(s, hsv_filter.sAdd) 195 | s = self.shift_channel(s, -hsv_filter.sSub) 196 | v = self.shift_channel(v, hsv_filter.vAdd) 197 | v = self.shift_channel(v, -hsv_filter.vSub) 198 | hsv = cv.merge([h, s, v]) 199 | 200 | # Set minimum and maximum HSV values to display 201 | lower = np.array([hsv_filter.hMin, hsv_filter.sMin, hsv_filter.vMin]) 202 | upper = np.array([hsv_filter.hMax, hsv_filter.sMax, hsv_filter.vMax]) 203 | # Apply the thresholds 204 | mask = cv.inRange(hsv, lower, upper) 205 | result = cv.bitwise_and(hsv, hsv, mask=mask) 206 | 207 | # convert back to BGR for imshow() to display it properly 208 | img = cv.cvtColor(result, cv.COLOR_HSV2BGR) 209 | 210 | return img 211 | 212 | # given an image and a Canny edge filter, apply the filter and return the resulting image. 213 | # if a filter is not supplied, the control GUI trackbars will be used 214 | def apply_edge_filter(self, original_image, edge_filter=None): 215 | # if we haven't been given a defined filter, use the filter values from the GUI 216 | if not edge_filter: 217 | edge_filter = self.get_edge_filter_from_controls() 218 | 219 | kernel = np.ones((edge_filter.kernelSize, edge_filter.kernelSize), np.uint8) 220 | eroded_image = cv.erode(original_image, kernel, iterations=edge_filter.erodeIter) 221 | dilated_image = cv.dilate(eroded_image, kernel, iterations=edge_filter.dilateIter) 222 | 223 | # canny edge detection 224 | result = cv.Canny(dilated_image, edge_filter.canny1, edge_filter.canny2) 225 | 226 | # convert single channel image back to BGR 227 | img = cv.cvtColor(result, cv.COLOR_GRAY2BGR) 228 | 229 | return img 230 | 231 | # apply adjustments to an HSV channel 232 | # https://stackoverflow.com/questions/49697363/shifting-hsv-pixel-values-in-python-using-numpy 233 | def shift_channel(self, c, amount): 234 | if amount > 0: 235 | lim = 255 - amount 236 | c[c >= lim] = 255 237 | c[c < lim] += amount 238 | elif amount < 0: 239 | amount = -amount 240 | lim = amount 241 | c[c <= lim] = 0 242 | c[c > lim] -= amount 243 | return c 244 | 245 | def match_keypoints(self, original_image, patch_size=32): 246 | min_match_count = 5 247 | 248 | orb = cv.ORB_create(edgeThreshold=0, patchSize=patch_size) 249 | keypoints_needle, descriptors_needle = orb.detectAndCompute(self.needle_img, None) 250 | orb2 = cv.ORB_create(edgeThreshold=0, patchSize=patch_size, nfeatures=2000) 251 | keypoints_haystack, descriptors_haystack = orb2.detectAndCompute(original_image, None) 252 | 253 | FLANN_INDEX_LSH = 6 254 | index_params = dict(algorithm=FLANN_INDEX_LSH, 255 | table_number=6, 256 | key_size=12, 257 | multi_probe_level=1) 258 | 259 | search_params = dict(checks=50) 260 | 261 | try: 262 | flann = cv.FlannBasedMatcher(index_params, search_params) 263 | matches = flann.knnMatch(descriptors_needle, descriptors_haystack, k=2) 264 | except cv.error: 265 | return None, None, [], [], None 266 | 267 | # store all the good matches as per Lowe's ratio test. 268 | good = [] 269 | points = [] 270 | 271 | for pair in matches: 272 | if len(pair) == 2: 273 | if pair[0].distance < 0.7*pair[1].distance: 274 | good.append(pair[0]) 275 | 276 | if len(good) > min_match_count: 277 | print('match %03d, kp %03d' % (len(good), len(keypoints_needle))) 278 | for match in good: 279 | points.append(keypoints_haystack[match.trainIdx].pt) 280 | #print(points) 281 | 282 | return keypoints_needle, keypoints_haystack, good, points 283 | 284 | def centeroid(self, point_list): 285 | point_list = np.asarray(point_list, dtype=np.int32) 286 | length = point_list.shape[0] 287 | sum_x = np.sum(point_list[:, 0]) 288 | sum_y = np.sum(point_list[:, 1]) 289 | return [np.floor_divide(sum_x, length), np.floor_divide(sum_y, length)] 290 | -------------------------------------------------------------------------------- /008_cascade_classifier/windowcapture.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import win32gui, win32ui, win32con 3 | 4 | 5 | class WindowCapture: 6 | 7 | # properties 8 | w = 0 9 | h = 0 10 | hwnd = None 11 | cropped_x = 0 12 | cropped_y = 0 13 | offset_x = 0 14 | offset_y = 0 15 | 16 | # constructor 17 | def __init__(self, window_name=None): 18 | # find the handle for the window we want to capture. 19 | # if no window name is given, capture the entire screen 20 | if window_name is None: 21 | self.hwnd = win32gui.GetDesktopWindow() 22 | else: 23 | self.hwnd = win32gui.FindWindow(None, window_name) 24 | if not self.hwnd: 25 | raise Exception('Window not found: {}'.format(window_name)) 26 | 27 | # get the window size 28 | window_rect = win32gui.GetWindowRect(self.hwnd) 29 | self.w = window_rect[2] - window_rect[0] 30 | self.h = window_rect[3] - window_rect[1] 31 | 32 | # account for the window border and titlebar and cut them off 33 | border_pixels = 8 34 | titlebar_pixels = 30 35 | self.w = self.w - (border_pixels * 2) 36 | self.h = self.h - titlebar_pixels - border_pixels 37 | self.cropped_x = border_pixels 38 | self.cropped_y = titlebar_pixels 39 | 40 | # set the cropped coordinates offset so we can translate screenshot 41 | # images into actual screen positions 42 | self.offset_x = window_rect[0] + self.cropped_x 43 | self.offset_y = window_rect[1] + self.cropped_y 44 | 45 | def get_screenshot(self): 46 | 47 | # get the window image data 48 | wDC = win32gui.GetWindowDC(self.hwnd) 49 | dcObj = win32ui.CreateDCFromHandle(wDC) 50 | cDC = dcObj.CreateCompatibleDC() 51 | dataBitMap = win32ui.CreateBitmap() 52 | dataBitMap.CreateCompatibleBitmap(dcObj, self.w, self.h) 53 | cDC.SelectObject(dataBitMap) 54 | cDC.BitBlt((0, 0), (self.w, self.h), dcObj, (self.cropped_x, self.cropped_y), win32con.SRCCOPY) 55 | 56 | # convert the raw data into a format opencv can read 57 | #dataBitMap.SaveBitmapFile(cDC, 'debug.bmp') 58 | signedIntsArray = dataBitMap.GetBitmapBits(True) 59 | img = np.fromstring(signedIntsArray, dtype='uint8') 60 | img.shape = (self.h, self.w, 4) 61 | 62 | # free resources 63 | dcObj.DeleteDC() 64 | cDC.DeleteDC() 65 | win32gui.ReleaseDC(self.hwnd, wDC) 66 | win32gui.DeleteObject(dataBitMap.GetHandle()) 67 | 68 | # drop the alpha channel, or cv.matchTemplate() will throw an error like: 69 | # error: (-215:Assertion failed) (depth == CV_8U || depth == CV_32F) && type == _templ.type() 70 | # && _img.dims() <= 2 in function 'cv::matchTemplate' 71 | img = img[...,:3] 72 | 73 | # make image C_CONTIGUOUS to avoid errors that look like: 74 | # File ... in draw_rectangles 75 | # TypeError: an integer is required (got type tuple) 76 | # see the discussion here: 77 | # https://github.com/opencv/opencv/issues/14866#issuecomment-580207109 78 | img = np.ascontiguousarray(img) 79 | 80 | return img 81 | 82 | # find the name of the window you're interested in. 83 | # once you have it, update window_capture() 84 | # https://stackoverflow.com/questions/55547940/how-to-get-a-list-of-the-name-of-every-open-window 85 | @staticmethod 86 | def list_window_names(): 87 | def winEnumHandler(hwnd, ctx): 88 | if win32gui.IsWindowVisible(hwnd): 89 | print(hex(hwnd), win32gui.GetWindowText(hwnd)) 90 | win32gui.EnumWindows(winEnumHandler, None) 91 | 92 | # translate a pixel position on a screenshot image to a pixel position on the screen. 93 | # pos = (x, y) 94 | # WARNING: if you move the window being captured after execution is started, this will 95 | # return incorrect coordinates, because the window position is only calculated in 96 | # the __init__ constructor. 97 | def get_screen_position(self, pos): 98 | return (pos[0] + self.offset_x, pos[1] + self.offset_y) 99 | -------------------------------------------------------------------------------- /009_bot/bot.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import pyautogui 3 | from time import sleep, time 4 | from threading import Thread, Lock 5 | from math import sqrt 6 | 7 | 8 | class BotState: 9 | INITIALIZING = 0 10 | SEARCHING = 1 11 | MOVING = 2 12 | MINING = 3 13 | BACKTRACKING = 4 14 | 15 | 16 | class AlbionBot: 17 | 18 | # constants 19 | INITIALIZING_SECONDS = 6 20 | MINING_SECONDS = 14 21 | MOVEMENT_STOPPED_THRESHOLD = 0.975 22 | IGNORE_RADIUS = 130 23 | TOOLTIP_MATCH_THRESHOLD = 0.72 24 | 25 | # threading properties 26 | stopped = True 27 | lock = None 28 | 29 | # properties 30 | state = None 31 | targets = [] 32 | screenshot = None 33 | timestamp = None 34 | movement_screenshot = None 35 | window_offset = (0,0) 36 | window_w = 0 37 | window_h = 0 38 | limestone_tooltip = None 39 | click_history = [] 40 | 41 | def __init__(self, window_offset, window_size): 42 | # create a thread lock object 43 | self.lock = Lock() 44 | 45 | # for translating window positions into screen positions, it's easier to just 46 | # get the offsets and window size from WindowCapture rather than passing in 47 | # the whole object 48 | self.window_offset = window_offset 49 | self.window_w = window_size[0] 50 | self.window_h = window_size[1] 51 | 52 | # pre-load the needle image used to confirm our object detection 53 | self.limestone_tooltip = cv.imread('limestone_tooltip.jpg', cv.IMREAD_UNCHANGED) 54 | 55 | # start bot in the initializing mode to allow us time to get setup. 56 | # mark the time at which this started so we know when to complete it 57 | self.state = BotState.INITIALIZING 58 | self.timestamp = time() 59 | 60 | def click_next_target(self): 61 | # 1. order targets by distance from center 62 | # loop: 63 | # 2. hover over the nearest target 64 | # 3. confirm that it's limestone via the tooltip 65 | # 4. if it's not, check the next target 66 | # endloop 67 | # 5. if no target was found return false 68 | # 6. click on the found target and return true 69 | targets = self.targets_ordered_by_distance(self.targets) 70 | 71 | target_i = 0 72 | found_limestone = False 73 | while not found_limestone and target_i < len(targets): 74 | # if we stopped our script, exit this loop 75 | if self.stopped: 76 | break 77 | 78 | # load up the next target in the list and convert those coordinates 79 | # that are relative to the game screenshot to a position on our 80 | # screen 81 | target_pos = targets[target_i] 82 | screen_x, screen_y = self.get_screen_position(target_pos) 83 | print('Moving mouse to x:{} y:{}'.format(screen_x, screen_y)) 84 | 85 | # move the mouse 86 | pyautogui.moveTo(x=screen_x, y=screen_y) 87 | # short pause to let the mouse movement complete and allow 88 | # time for the tooltip to appear 89 | sleep(1.250) 90 | # confirm limestone tooltip 91 | if self.confirm_tooltip(target_pos): 92 | print('Click on confirmed target at x:{} y:{}'.format(screen_x, screen_y)) 93 | found_limestone = True 94 | pyautogui.click() 95 | # save this position to the click history 96 | self.click_history.append(target_pos) 97 | target_i += 1 98 | 99 | return found_limestone 100 | 101 | def have_stopped_moving(self): 102 | # if we haven't stored a screenshot to compare to, do that first 103 | if self.movement_screenshot is None: 104 | self.movement_screenshot = self.screenshot.copy() 105 | return False 106 | 107 | # compare the old screenshot to the new screenshot 108 | result = cv.matchTemplate(self.screenshot, self.movement_screenshot, cv.TM_CCOEFF_NORMED) 109 | # we only care about the value when the two screenshots are laid perfectly over one 110 | # another, so the needle position is (0, 0). since both images are the same size, this 111 | # should be the only result that exists anyway 112 | similarity = result[0][0] 113 | print('Movement detection similarity: {}'.format(similarity)) 114 | 115 | if similarity >= self.MOVEMENT_STOPPED_THRESHOLD: 116 | # pictures look similar, so we've probably stopped moving 117 | print('Movement detected stop') 118 | return True 119 | 120 | # looks like we're still moving. 121 | # use this new screenshot to compare to the next one 122 | self.movement_screenshot = self.screenshot.copy() 123 | return False 124 | 125 | def targets_ordered_by_distance(self, targets): 126 | # our character is always in the center of the screen 127 | my_pos = (self.window_w / 2, self.window_h / 2) 128 | # searched "python order points by distance from point" 129 | # simply uses the pythagorean theorem 130 | # https://stackoverflow.com/a/30636138/4655368 131 | def pythagorean_distance(pos): 132 | return sqrt((pos[0] - my_pos[0])**2 + (pos[1] - my_pos[1])**2) 133 | targets.sort(key=pythagorean_distance) 134 | 135 | # print(my_pos) 136 | # print(targets) 137 | # for t in targets: 138 | # print(pythagorean_distance(t)) 139 | 140 | # ignore targets at are too close to our character (within 130 pixels) to avoid 141 | # re-clicking a deposit we just mined 142 | targets = [t for t in targets if pythagorean_distance(t) > self.IGNORE_RADIUS] 143 | 144 | return targets 145 | 146 | def confirm_tooltip(self, target_position): 147 | # check the current screenshot for the limestone tooltip using match template 148 | result = cv.matchTemplate(self.screenshot, self.limestone_tooltip, cv.TM_CCOEFF_NORMED) 149 | # get the best match postition 150 | min_val, max_val, min_loc, max_loc = cv.minMaxLoc(result) 151 | # if we can closely match the tooltip image, consider the object found 152 | if max_val >= self.TOOLTIP_MATCH_THRESHOLD: 153 | # print('Tooltip found in image at {}'.format(max_loc)) 154 | # screen_loc = self.get_screen_position(max_loc) 155 | # print('Found on screen at {}'.format(screen_loc)) 156 | # mouse_position = pyautogui.position() 157 | # print('Mouse on screen at {}'.format(mouse_position)) 158 | # offset = (mouse_position[0] - screen_loc[0], mouse_position[1] - screen_loc[1]) 159 | # print('Offset calculated as x: {} y: {}'.format(offset[0], offset[1])) 160 | # the offset I always got was Offset calculated as x: -22 y: -29 161 | return True 162 | #print('Tooltip not found.') 163 | return False 164 | 165 | def click_backtrack(self): 166 | # pop the top item off the clicked points stack. this will be the click that 167 | # brought us to our current location. 168 | last_click = self.click_history.pop() 169 | # to undo this click, we must mirror it across the center point. so if our 170 | # character is at the middle of the screen at ex. (100, 100), and our last 171 | # click was at (120, 120), then to undo this we must now click at (80, 80). 172 | # our character is always in the center of the screen 173 | my_pos = (self.window_w / 2, self.window_h / 2) 174 | mirrored_click_x = my_pos[0] - (last_click[0] - my_pos[0]) 175 | mirrored_click_y = my_pos[1] - (last_click[1] - my_pos[1]) 176 | # convert this screenshot position to a screen position 177 | screen_x, screen_y = self.get_screen_position((mirrored_click_x, mirrored_click_y)) 178 | print('Backtracking to x:{} y:{}'.format(screen_x, screen_y)) 179 | pyautogui.moveTo(x=screen_x, y=screen_y) 180 | # short pause to let the mouse movement complete 181 | sleep(0.500) 182 | pyautogui.click() 183 | 184 | # translate a pixel position on a screenshot image to a pixel position on the screen. 185 | # pos = (x, y) 186 | # WARNING: if you move the window being captured after execution is started, this will 187 | # return incorrect coordinates, because the window position is only calculated in 188 | # the WindowCapture __init__ constructor. 189 | def get_screen_position(self, pos): 190 | return (pos[0] + self.window_offset[0], pos[1] + self.window_offset[1]) 191 | 192 | # threading methods 193 | 194 | def update_targets(self, targets): 195 | self.lock.acquire() 196 | self.targets = targets 197 | self.lock.release() 198 | 199 | def update_screenshot(self, screenshot): 200 | self.lock.acquire() 201 | self.screenshot = screenshot 202 | self.lock.release() 203 | 204 | def start(self): 205 | self.stopped = False 206 | t = Thread(target=self.run) 207 | t.start() 208 | 209 | def stop(self): 210 | self.stopped = True 211 | 212 | # main logic controller 213 | def run(self): 214 | while not self.stopped: 215 | if self.state == BotState.INITIALIZING: 216 | # do no bot actions until the startup waiting period is complete 217 | if time() > self.timestamp + self.INITIALIZING_SECONDS: 218 | # start searching when the waiting period is over 219 | self.lock.acquire() 220 | self.state = BotState.SEARCHING 221 | self.lock.release() 222 | 223 | elif self.state == BotState.SEARCHING: 224 | # check the given click point targets, confirm a limestone deposit, 225 | # then click it. 226 | success = self.click_next_target() 227 | # if not successful, try one more time 228 | if not success: 229 | success = self.click_next_target() 230 | 231 | # if successful, switch state to moving 232 | # if not, backtrack or hold the current position 233 | if success: 234 | self.lock.acquire() 235 | self.state = BotState.MOVING 236 | self.lock.release() 237 | elif len(self.click_history) > 0: 238 | self.click_backtrack() 239 | self.lock.acquire() 240 | self.state = BotState.BACKTRACKING 241 | self.lock.release() 242 | else: 243 | # stay in place and keep searching 244 | pass 245 | 246 | elif self.state == BotState.MOVING or self.state == BotState.BACKTRACKING: 247 | # see if we've stopped moving yet by comparing the current pixel mesh 248 | # to the previously observed mesh 249 | if not self.have_stopped_moving(): 250 | # wait a short time to allow for the character position to change 251 | sleep(0.500) 252 | else: 253 | # reset the timestamp marker to the current time. switch state 254 | # to mining if we clicked on a deposit, or search again if we 255 | # backtracked 256 | self.lock.acquire() 257 | if self.state == BotState.MOVING: 258 | self.timestamp = time() 259 | self.state = BotState.MINING 260 | elif self.state == BotState.BACKTRACKING: 261 | self.state = BotState.SEARCHING 262 | self.lock.release() 263 | 264 | elif self.state == BotState.MINING: 265 | # see if we're done mining. just wait some amount of time 266 | if time() > self.timestamp + self.MINING_SECONDS: 267 | # return to the searching state 268 | self.lock.acquire() 269 | self.state = BotState.SEARCHING 270 | self.lock.release() 271 | -------------------------------------------------------------------------------- /009_bot/detection.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | from threading import Thread, Lock 3 | 4 | 5 | class Detection: 6 | 7 | # threading properties 8 | stopped = True 9 | lock = None 10 | rectangles = [] 11 | # properties 12 | cascade = None 13 | screenshot = None 14 | 15 | def __init__(self, model_file_path): 16 | # create a thread lock object 17 | self.lock = Lock() 18 | # load the trained model 19 | self.cascade = cv.CascadeClassifier(model_file_path) 20 | 21 | def update(self, screenshot): 22 | self.lock.acquire() 23 | self.screenshot = screenshot 24 | self.lock.release() 25 | 26 | def start(self): 27 | self.stopped = False 28 | t = Thread(target=self.run) 29 | t.start() 30 | 31 | def stop(self): 32 | self.stopped = True 33 | 34 | def run(self): 35 | # TODO: you can write your own time/iterations calculation to determine how fast this is 36 | while not self.stopped: 37 | if not self.screenshot is None: 38 | # do object detection 39 | rectangles = self.cascade.detectMultiScale(self.screenshot) 40 | # lock the thread while updating the results 41 | self.lock.acquire() 42 | self.rectangles = rectangles 43 | self.lock.release() 44 | -------------------------------------------------------------------------------- /009_bot/limestone_tooltip.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/learncodebygaming/opencv_tutorials/2766b4a7e05f5bd4d9b49d95a9f2b6eb6c25f420/009_bot/limestone_tooltip.jpg -------------------------------------------------------------------------------- /009_bot/main.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | import os 4 | from time import time 5 | from windowcapture import WindowCapture 6 | from detection import Detection 7 | from vision import Vision 8 | from bot import AlbionBot, BotState 9 | 10 | # Change the working directory to the folder this script is in. 11 | # Doing this because I'll be putting the files from each video in their 12 | # own folder on GitHub 13 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 14 | 15 | 16 | DEBUG = True 17 | 18 | # initialize the WindowCapture class 19 | wincap = WindowCapture('Albion Online Client') 20 | # load the detector 21 | detector = Detection('limestone_model_final.xml') 22 | # load an empty Vision class 23 | vision = Vision() 24 | # initialize the bot 25 | bot = AlbionBot((wincap.offset_x, wincap.offset_y), (wincap.w, wincap.h)) 26 | 27 | wincap.start() 28 | detector.start() 29 | bot.start() 30 | 31 | while(True): 32 | 33 | # if we don't have a screenshot yet, don't run the code below this point yet 34 | if wincap.screenshot is None: 35 | continue 36 | 37 | # give detector the current screenshot to search for objects in 38 | detector.update(wincap.screenshot) 39 | 40 | # update the bot with the data it needs right now 41 | if bot.state == BotState.INITIALIZING: 42 | # while bot is waiting to start, go ahead and start giving it some targets to work 43 | # on right away when it does start 44 | targets = vision.get_click_points(detector.rectangles) 45 | bot.update_targets(targets) 46 | elif bot.state == BotState.SEARCHING: 47 | # when searching for something to click on next, the bot needs to know what the click 48 | # points are for the current detection results. it also needs an updated screenshot 49 | # to verify the hover tooltip once it has moved the mouse to that position 50 | targets = vision.get_click_points(detector.rectangles) 51 | bot.update_targets(targets) 52 | bot.update_screenshot(wincap.screenshot) 53 | elif bot.state == BotState.MOVING: 54 | # when moving, we need fresh screenshots to determine when we've stopped moving 55 | bot.update_screenshot(wincap.screenshot) 56 | elif bot.state == BotState.MINING: 57 | # nothing is needed while we wait for the mining to finish 58 | pass 59 | 60 | if DEBUG: 61 | # draw the detection results onto the original image 62 | detection_image = vision.draw_rectangles(wincap.screenshot, detector.rectangles) 63 | # display the images 64 | cv.imshow('Matches', detection_image) 65 | 66 | # press 'q' with the output window focused to exit. 67 | # waits 1 ms every loop to process key presses 68 | key = cv.waitKey(1) 69 | if key == ord('q'): 70 | wincap.stop() 71 | detector.stop() 72 | bot.stop() 73 | cv.destroyAllWindows() 74 | break 75 | 76 | print('Done.') 77 | -------------------------------------------------------------------------------- /009_bot/vision.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv 2 | import numpy as np 3 | 4 | 5 | class Vision: 6 | 7 | # given a list of [x, y, w, h] rectangles returned by find(), convert those into a list of 8 | # [x, y] positions in the center of those rectangles where we can click on those found items 9 | def get_click_points(self, rectangles): 10 | points = [] 11 | 12 | # Loop over all the rectangles 13 | for (x, y, w, h) in rectangles: 14 | # Determine the center position 15 | center_x = x + int(w/2) 16 | center_y = y + int(h/2) 17 | # Save the points 18 | points.append((center_x, center_y)) 19 | 20 | return points 21 | 22 | # given a list of [x, y, w, h] rectangles and a canvas image to draw on, return an image with 23 | # all of those rectangles drawn 24 | def draw_rectangles(self, haystack_img, rectangles): 25 | # these colors are actually BGR 26 | line_color = (0, 255, 0) 27 | line_type = cv.LINE_4 28 | 29 | for (x, y, w, h) in rectangles: 30 | # determine the box positions 31 | top_left = (x, y) 32 | bottom_right = (x + w, y + h) 33 | # draw the box 34 | cv.rectangle(haystack_img, top_left, bottom_right, line_color, lineType=line_type) 35 | 36 | return haystack_img 37 | 38 | # given a list of [x, y] positions and a canvas image to draw on, return an image with all 39 | # of those click points drawn on as crosshairs 40 | def draw_crosshairs(self, haystack_img, points): 41 | # these colors are actually BGR 42 | marker_color = (255, 0, 255) 43 | marker_type = cv.MARKER_CROSS 44 | 45 | for (center_x, center_y) in points: 46 | # draw the center point 47 | cv.drawMarker(haystack_img, (center_x, center_y), marker_color, marker_type) 48 | 49 | return haystack_img 50 | 51 | def centeroid(self, point_list): 52 | point_list = np.asarray(point_list, dtype=np.int32) 53 | length = point_list.shape[0] 54 | sum_x = np.sum(point_list[:, 0]) 55 | sum_y = np.sum(point_list[:, 1]) 56 | return [np.floor_divide(sum_x, length), np.floor_divide(sum_y, length)] 57 | -------------------------------------------------------------------------------- /009_bot/windowcapture.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import win32gui, win32ui, win32con 3 | from threading import Thread, Lock 4 | 5 | 6 | class WindowCapture: 7 | 8 | # threading properties 9 | stopped = True 10 | lock = None 11 | screenshot = None 12 | # properties 13 | w = 0 14 | h = 0 15 | hwnd = None 16 | cropped_x = 0 17 | cropped_y = 0 18 | offset_x = 0 19 | offset_y = 0 20 | 21 | # constructor 22 | def __init__(self, window_name=None): 23 | # create a thread lock object 24 | self.lock = Lock() 25 | 26 | # find the handle for the window we want to capture. 27 | # if no window name is given, capture the entire screen 28 | if window_name is None: 29 | self.hwnd = win32gui.GetDesktopWindow() 30 | else: 31 | self.hwnd = win32gui.FindWindow(None, window_name) 32 | if not self.hwnd: 33 | raise Exception('Window not found: {}'.format(window_name)) 34 | 35 | # get the window size 36 | window_rect = win32gui.GetWindowRect(self.hwnd) 37 | self.w = window_rect[2] - window_rect[0] 38 | self.h = window_rect[3] - window_rect[1] 39 | 40 | # account for the window border and titlebar and cut them off 41 | border_pixels = 8 42 | titlebar_pixels = 30 43 | self.w = self.w - (border_pixels * 2) 44 | self.h = self.h - titlebar_pixels - border_pixels 45 | self.cropped_x = border_pixels 46 | self.cropped_y = titlebar_pixels 47 | 48 | # set the cropped coordinates offset so we can translate screenshot 49 | # images into actual screen positions 50 | self.offset_x = window_rect[0] + self.cropped_x 51 | self.offset_y = window_rect[1] + self.cropped_y 52 | 53 | def get_screenshot(self): 54 | 55 | # get the window image data 56 | wDC = win32gui.GetWindowDC(self.hwnd) 57 | dcObj = win32ui.CreateDCFromHandle(wDC) 58 | cDC = dcObj.CreateCompatibleDC() 59 | dataBitMap = win32ui.CreateBitmap() 60 | dataBitMap.CreateCompatibleBitmap(dcObj, self.w, self.h) 61 | cDC.SelectObject(dataBitMap) 62 | cDC.BitBlt((0, 0), (self.w, self.h), dcObj, (self.cropped_x, self.cropped_y), win32con.SRCCOPY) 63 | 64 | # convert the raw data into a format opencv can read 65 | #dataBitMap.SaveBitmapFile(cDC, 'debug.bmp') 66 | signedIntsArray = dataBitMap.GetBitmapBits(True) 67 | img = np.fromstring(signedIntsArray, dtype='uint8') 68 | img.shape = (self.h, self.w, 4) 69 | 70 | # free resources 71 | dcObj.DeleteDC() 72 | cDC.DeleteDC() 73 | win32gui.ReleaseDC(self.hwnd, wDC) 74 | win32gui.DeleteObject(dataBitMap.GetHandle()) 75 | 76 | # drop the alpha channel, or cv.matchTemplate() will throw an error like: 77 | # error: (-215:Assertion failed) (depth == CV_8U || depth == CV_32F) && type == _templ.type() 78 | # && _img.dims() <= 2 in function 'cv::matchTemplate' 79 | img = img[...,:3] 80 | 81 | # make image C_CONTIGUOUS to avoid errors that look like: 82 | # File ... in draw_rectangles 83 | # TypeError: an integer is required (got type tuple) 84 | # see the discussion here: 85 | # https://github.com/opencv/opencv/issues/14866#issuecomment-580207109 86 | img = np.ascontiguousarray(img) 87 | 88 | return img 89 | 90 | # find the name of the window you're interested in. 91 | # once you have it, update window_capture() 92 | # https://stackoverflow.com/questions/55547940/how-to-get-a-list-of-the-name-of-every-open-window 93 | @staticmethod 94 | def list_window_names(): 95 | def winEnumHandler(hwnd, ctx): 96 | if win32gui.IsWindowVisible(hwnd): 97 | print(hex(hwnd), win32gui.GetWindowText(hwnd)) 98 | win32gui.EnumWindows(winEnumHandler, None) 99 | 100 | # translate a pixel position on a screenshot image to a pixel position on the screen. 101 | # pos = (x, y) 102 | # WARNING: if you move the window being captured after execution is started, this will 103 | # return incorrect coordinates, because the window position is only calculated in 104 | # the __init__ constructor. 105 | def get_screen_position(self, pos): 106 | return (pos[0] + self.offset_x, pos[1] + self.offset_y) 107 | 108 | # threading methods 109 | 110 | def start(self): 111 | self.stopped = False 112 | t = Thread(target=self.run) 113 | t.start() 114 | 115 | def stop(self): 116 | self.stopped = True 117 | 118 | def run(self): 119 | # TODO: you can write your own time/iterations calculation to determine how fast this is 120 | while not self.stopped: 121 | # get an updated image of the game 122 | screenshot = self.get_screenshot() 123 | # lock the thread while updating the results 124 | self.lock.acquire() 125 | self.screenshot = screenshot 126 | self.lock.release() 127 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ben Johnson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenCV Tutorials 2 | 3 | Source code for the OpenCV Object Detection in Games series on the **Learn Code By Gaming** YouTube channel. 4 | 5 | Watch the tutorials here: https://www.youtube.com/playlist?list=PL1m2M8LQlzfKtkKq2lK5xko4X-8EZzFPI 6 | --------------------------------------------------------------------------------