-
-
Notifications
You must be signed in to change notification settings - Fork 126
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Runmaxx 9.1 Threadmill on Win 10 (Issue #1581)
- Loading branch information
Showing
3 changed files
with
179 additions
and
178 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,110 +1,107 @@ | ||
# iFit-Wolf3 - Autoincline control of treadmill via ADB and OCR | ||
# Author: Al Udell | ||
# Revised: April 22, 2023 | ||
|
||
# process-image.py - take Zwift screenshot, crop incline, OCR incline | ||
|
||
# imports | ||
import cv2 | ||
import numpy as np | ||
import re | ||
from datetime import datetime | ||
from paddleocr import PaddleOCR | ||
from PIL import Image, ImageGrab | ||
|
||
# Take Zwift screenshot | ||
screenshot = ImageGrab.grab() | ||
|
||
# Scale image to 3000 x 2000 | ||
screenshot = screenshot.resize((3000, 2000)) | ||
|
||
# Convert screenshot to a numpy array | ||
screenshot_np = np.array(screenshot) | ||
|
||
# Crop image to incline area | ||
screenwidth, screenheight = screenshot.size | ||
|
||
# Values for Zwift climb portal incline | ||
col1 = int(screenwidth/3000 * 2822) | ||
row1 = int(screenheight/2000 * 218) | ||
col2 = int(screenwidth/3000 * 2980) | ||
row2 = int(screenheight/2000 * 302) | ||
|
||
cropped_np = screenshot_np[row1:row2, col1:col2] | ||
|
||
# Convert numpy array to PIL image | ||
cropped_pil = Image.fromarray(cropped_np) | ||
|
||
# Convert PIL Image to a cv2 image | ||
cropped_cv2 = cv2.cvtColor(np.array(cropped_pil), cv2.COLOR_RGB2BGR) | ||
|
||
# Convert cv2 image to HSV | ||
result = cropped_cv2.copy() | ||
image = cv2.cvtColor(cropped_cv2, cv2.COLOR_BGR2HSV) | ||
|
||
# Isolate white mask | ||
lower = np.array([0,0,159]) | ||
upper = np.array([0,0,255]) | ||
mask0 = cv2.inRange(image, lower, upper) | ||
result0 = cv2.bitwise_and(result, result, mask=mask0) | ||
|
||
# Isolate yellow mask | ||
lower = np.array([24,239,241]) | ||
upper = np.array([24,253,255]) | ||
mask1 = cv2.inRange(image, lower, upper) | ||
result1 = cv2.bitwise_and(result, result, mask=mask1) | ||
|
||
# Isolate orange mask | ||
lower = np.array([8,191,243]) | ||
upper = np.array([8,192,243]) | ||
mask2 = cv2.inRange(image, lower, upper) | ||
result2 = cv2.bitwise_and(result, result, mask=mask2) | ||
|
||
# Isolate red mask | ||
lower = np.array([0,255,255]) | ||
upper = np.array([10,255,255]) | ||
mask3 = cv2.inRange(image, lower, upper) | ||
result3 = cv2.bitwise_and(result, result, mask=mask3) | ||
|
||
# Join colour masks | ||
mask = mask0+mask1+mask2+mask3 | ||
|
||
# Set output image to zero everywhere except mask | ||
merge = image.copy() | ||
merge[np.where(mask==0)] = 0 | ||
|
||
# Convert to grayscale | ||
gray = cv2.cvtColor(merge, cv2.COLOR_BGR2GRAY) | ||
|
||
# Convert to black/white by threshold | ||
ret,bin = cv2.threshold(gray,30,255,cv2.THRESH_BINARY) | ||
|
||
# Closing | ||
kernel = np.ones((3,3),np.uint8) | ||
closing = cv2.morphologyEx(bin, cv2.MORPH_CLOSE, kernel) | ||
|
||
# Invert black/white | ||
inv = cv2.bitwise_not(closing) | ||
|
||
# Apply average blur | ||
averageBlur = cv2.blur(inv, (3, 3)) | ||
|
||
# OCR image | ||
ocr = PaddleOCR(lang='en', use_gpu=False, enable_mkldnn=True, use_angle_cls=False, table=False, layout=False, show_log=False) | ||
result = ocr.ocr(averageBlur, cls=False, det=True, rec=True) | ||
|
||
# Extract OCR text | ||
ocr_text = '' | ||
for line in result: | ||
for word in line: | ||
ocr_text += f"{word[1][0]}" | ||
|
||
# Remove all characters that are not "-" and integers from OCR text | ||
pattern = r"[^-\d]+" | ||
ocr_text = re.sub(pattern, "", ocr_text) | ||
if ocr_text: | ||
incline = ocr_text | ||
else: | ||
incline = 'None' | ||
|
||
print(incline) | ||
# iFit-Wolf3 - Autoincline control of treadmill via ADB and OCR | ||
# Author: Al Udell | ||
# Revised: August 16, 2023 | ||
|
||
# zwift-incline-climb-portal.py - take Zwift screenshot, crop incline, OCR incline | ||
|
||
# imports | ||
import cv2 | ||
import numpy as np | ||
import re | ||
from datetime import datetime | ||
from paddleocr import PaddleOCR | ||
from PIL import Image, ImageGrab | ||
|
||
# Take Zwift screenshot | ||
screenshot = ImageGrab.grab() | ||
|
||
# Scale image to 3000 x 2000 | ||
screenshot = screenshot.resize((3000, 2000)) | ||
|
||
# Crop image to incline area | ||
screenwidth, screenheight = screenshot.size | ||
|
||
# Values for Zwift climb portal incline | ||
col1 = int(screenwidth/3000 * 2822) | ||
row1 = int(screenheight/2000 * 218) | ||
col2 = int(screenwidth/3000 * 2980) | ||
row2 = int(screenheight/2000 * 302) | ||
|
||
cropped = screenshot.crop((col1, row1, col2, row2)) | ||
|
||
# Scale image to correct size for borderless window mode | ||
width, height = cropped.size | ||
cropped = cropped.resize((int(width * 1.3), int(height * 1.3))) | ||
|
||
# Convert image to np array | ||
cropped_np = np.array(cropped) | ||
|
||
# Convert np array to PIL | ||
cropped_pil = Image.fromarray(cropped_np) | ||
|
||
# Convert PIL image to cv2 RGB | ||
cropped_cv2 = cv2.cvtColor(np.array(cropped_pil), cv2.COLOR_RGB2BGR) | ||
|
||
# Convert cv2 RGB to HSV | ||
result = cropped_cv2.copy() | ||
image = cv2.cvtColor(cropped_cv2, cv2.COLOR_BGR2HSV) | ||
|
||
# Isolate white mask | ||
lower = np.array([0,0,159]) | ||
upper = np.array([0,0,255]) | ||
mask0 = cv2.inRange(image, lower, upper) | ||
result0 = cv2.bitwise_and(result, result, mask=mask0) | ||
|
||
# Isolate yellow mask | ||
lower = np.array([24,239,241]) | ||
upper = np.array([24,253,255]) | ||
mask1 = cv2.inRange(image, lower, upper) | ||
result1 = cv2.bitwise_and(result, result, mask=mask1) | ||
|
||
# Isolate orange mask | ||
lower = np.array([8,191,243]) | ||
upper = np.array([8,192,243]) | ||
mask2 = cv2.inRange(image, lower, upper) | ||
result2 = cv2.bitwise_and(result, result, mask=mask2) | ||
|
||
# Isolate red mask | ||
lower = np.array([0,255,255]) | ||
upper = np.array([10,255,255]) | ||
mask3 = cv2.inRange(image, lower, upper) | ||
result3 = cv2.bitwise_and(result, result, mask=mask3) | ||
|
||
# Join colour masks | ||
mask = mask0+mask1+mask2+mask3 | ||
|
||
# Set output image to zero everywhere except mask | ||
merge = image.copy() | ||
merge[np.where(mask==0)] = 0 | ||
|
||
# Convert to grayscale | ||
gray = cv2.cvtColor(merge, cv2.COLOR_BGR2GRAY) | ||
|
||
# Convert to black/white by threshold | ||
ret,bin = cv2.threshold(gray, 70, 255, cv2.THRESH_BINARY_INV) | ||
|
||
# Apply gaussian blur | ||
gaussianBlur = cv2.GaussianBlur(bin,(3,3),0) | ||
|
||
# OCR image | ||
ocr = PaddleOCR(lang='en', use_gpu=False, enable_mkldnn=True, use_angle_cls=False, table=False, layout=False, show_log=False) | ||
result = ocr.ocr(gaussianBlur, cls=False, det=True, rec=True) | ||
|
||
# Extract OCR text | ||
ocr_text = '' | ||
for line in result: | ||
for word in line: | ||
ocr_text += f"{word[1][0]}" | ||
|
||
# Remove all characters that are not "-" and integers from OCR text | ||
pattern = r"[^-\d]+" | ||
ocr_text = re.sub(pattern, "", ocr_text) | ||
if ocr_text: | ||
incline = ocr_text | ||
else: | ||
incline = 'None' | ||
|
||
print(incline) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,67 +1,71 @@ | ||
# iFit-Workout - Auto-incline and auto-speed control of treadmill via ADB and OCR for Zwift workouts | ||
# Author: Al Udell | ||
# Revised: April 27, 2023 | ||
|
||
# process-image.py - take Zwift screenshot, crop speed/incline instruction, OCR speed/incline | ||
|
||
# imports | ||
import cv2 | ||
import numpy as np | ||
import re | ||
from datetime import datetime | ||
from paddleocr import PaddleOCR | ||
from PIL import Image, ImageGrab | ||
|
||
# Take Zwift screenshot | ||
screenshot = ImageGrab.grab() | ||
|
||
# Scale image to 3000 x 2000 | ||
screenshot = screenshot.resize((3000, 2000)) | ||
|
||
# Convert screenshot to a numpy array | ||
screenshot_np = np.array(screenshot) | ||
|
||
# Convert numpy array to a cv2 RGB image | ||
screenshot_cv2 = cv2.cvtColor(screenshot_np, cv2.COLOR_BGR2RGB) | ||
|
||
# Crop image to workout instruction area | ||
screenwidth, screenheight = screenshot.size | ||
col1 = int(screenwidth/3000 * 1010) | ||
row1 = int(screenheight/2000 * 260) | ||
col2 = int(screenwidth/3000 * 1285) | ||
row2 = int(screenheight/2000 * 480) | ||
cropped_cv2 = screenshot_cv2[row1:row2, col1:col2] | ||
|
||
# OCR image | ||
ocr = PaddleOCR(lang='en', use_gpu=False, enable_mkldnn=True, use_angle_cls=False, table=False, layout=False, show_log=False) | ||
result = ocr.ocr(cropped_cv2, cls=False, det=True, rec=True) | ||
|
||
# Extract OCR text | ||
ocr_text = '' | ||
for line in result: | ||
for word in line: | ||
ocr_text += f"{word[1][0]} " | ||
|
||
# Find the speed number | ||
num_pattern = r'\d+(\.\d+)?' # Regular expression pattern to match numbers with optional decimal places | ||
unit_pattern = r'\s+(kph|mph)' # Regular expression pattern to match "kph" or "mph" units | ||
speed_match = re.search(num_pattern + unit_pattern, ocr_text) | ||
if speed_match: | ||
speed = speed_match.group(0) | ||
pattern = r'\d+\.\d+' | ||
speed = re.findall(pattern, speed)[0] | ||
else: | ||
speed = 'None' | ||
|
||
# Find the incline number | ||
incline_pattern = r'\d+\s*%' # Regular expression pattern to match numbers with "%" | ||
incline_match = re.search(incline_pattern, ocr_text) | ||
if incline_match: | ||
incline = incline_match.group(0) | ||
pattern = r'\d+' | ||
incline = re.findall(pattern, incline)[0] | ||
else: | ||
incline = 'None' | ||
|
||
print(f"{speed};{incline}") | ||
|
||
# iFit-Workout - Auto-incline and auto-speed control of treadmill via ADB and OCR for Zwift workouts | ||
# Author: Al Udell | ||
# Revised: August 16, 2023 | ||
|
||
# zwift-workout.py - take Zwift screenshot, crop speed/incline instruction, OCR speed/incline | ||
|
||
# imports | ||
import cv2 | ||
import numpy as np | ||
import re | ||
from datetime import datetime | ||
from paddleocr import PaddleOCR | ||
from PIL import Image, ImageGrab | ||
|
||
# Take Zwift screenshot | ||
screenshot = ImageGrab.grab() | ||
|
||
# Scale image to 3000 x 2000 | ||
screenshot = screenshot.resize((3000, 2000)) | ||
|
||
# Crop image to workout instruction area | ||
screenwidth, screenheight = screenshot.size | ||
|
||
# Values for Zwift workout instructions | ||
col1 = int(screenwidth/3000 * 1010) | ||
row1 = int(screenheight/2000 * 260) | ||
col2 = int(screenwidth/3000 * 1285) | ||
row2 = int(screenheight/2000 * 480) | ||
|
||
cropped = screenshot.crop((col1, row1, col2, row2)) | ||
|
||
# Scale image to correct size for borderless window mode | ||
width, height = cropped.size | ||
cropped = cropped.resize((int(width * 0.99), int(height * 0.99))) | ||
|
||
# Convert image to np array | ||
cropped_np = np.array(cropped) | ||
|
||
# OCR image | ||
ocr = PaddleOCR(lang='en', use_gpu=False, enable_mkldnn=True, use_angle_cls=False, table=False, layout=False, show_log=False) | ||
result = ocr.ocr(cropped_np, cls=False, det=True, rec=True) | ||
|
||
# Extract OCR text | ||
ocr_text = '' | ||
for line in result: | ||
for word in line: | ||
ocr_text += f"{word[1][0]} " | ||
|
||
# Find the speed number | ||
num_pattern = r'\d+(\.\d+)?' # Regular expression pattern to match numbers with optional decimal places | ||
unit_pattern = r'\s+(kph|mph)' # Regular expression pattern to match "kph" or "mph" units | ||
speed_match = re.search(num_pattern + unit_pattern, ocr_text) | ||
if speed_match: | ||
speed = speed_match.group(0) | ||
pattern = r'\d+\.\d+' | ||
speed = re.findall(pattern, speed)[0] | ||
else: | ||
speed = 'None' | ||
|
||
# Find the incline number | ||
incline_pattern = r'-?\d+\s*%' # Regular expression pattern to match numbers with "%" | ||
incline_match = re.search(incline_pattern, ocr_text) | ||
if incline_match: | ||
incline = incline_match.group(0) | ||
pattern = r'-?\d+' | ||
incline = re.findall(pattern, incline)[0] | ||
else: | ||
incline = 'None' | ||
|
||
print(f"{speed};{incline}") | ||
|