diff --git a/EyeTrackApp/algo_settings_widget.py b/EyeTrackApp/algo_settings_widget.py new file mode 100644 index 0000000..c2838a5 --- /dev/null +++ b/EyeTrackApp/algo_settings_widget.py @@ -0,0 +1,480 @@ +import PySimpleGUI as sg + +from config import EyeTrackSettingsConfig +from osc import EyeId +from queue import Queue +from threading import Event + +class AlgoSettingsWidget: + def __init__(self, widget_id: EyeId, main_config: EyeTrackSettingsConfig, osc_queue: Queue): + + self.gui_flip_x_axis_left = f"-FLIPXAXISLEFT{widget_id}-" + self.gui_flip_x_axis_right = f"-FLIPXAXISRIGHT{widget_id}-" + self.gui_flip_y_axis = f"-FLIPYAXIS{widget_id}-" + self.gui_general_settings_layout = f"-GENERALSETTINGSLAYOUT{widget_id}-" + self.gui_osc_address = f"-OSCADDRESS{widget_id}-" + self.gui_osc_port = f"-OSCPORT{widget_id}-" + self.gui_osc_receiver_port = f"OSCRECEIVERPORT{widget_id}-" + self.gui_osc_recenter_address = f"OSCRECENTERADDRESS{widget_id}-" + self.gui_osc_recalibrate_address = f"OSCRECALIBRATEADDRESS{widget_id}-" + self.gui_BLOB = f"-BLOBFALLBACK{widget_id}-" + self.gui_HSF = f"-HSF{widget_id}-" + self.gui_DADDY = f"-DADDY{widget_id}-" + self.gui_DADDYP = f"-DADDYP{widget_id}-" + self.gui_RANSAC3D = f"-RANSAC3D{widget_id}-" + self.gui_BLINK = f"-BLINK{widget_id}-" + self.gui_IBO = f"-IBO{widget_id}-" + self.gui_HSRAC = f"-HSRAC{widget_id}-" + self.gui_HSF_radius = f"-HSFRADIUS{widget_id}-" + self.gui_blob_maxsize = f"-BLOBMAXSIZE{widget_id}-" + self.gui_blob_minsize = f"-BLOBMINSIZE{widget_id}-" + self.gui_speed_coefficient = f"-SPEEDCOEFFICIENT{widget_id}-" + self.gui_min_cutoff = f"-MINCUTOFF{widget_id}-" + self.gui_eye_falloff = f"-EYEFALLOFF{widget_id}-" + self.gui_skip_autoradius = f"-SKIPAUTORADIUS{widget_id}-" + self.gui_HSRACP = f"-HSRACP{widget_id}-" + self.gui_RANSAC3DP = f"-RANSAC3DP{widget_id}-" + self.gui_HSFP = f"-HSFP{widget_id}-" + self.gui_BLOBP = f"-BLOBP{widget_id}-" + self.gui_thresh_add = f"-THRESHADD{widget_id}-" + self.gui_ROSC = f"-ROSC{widget_id}-" + self.gui_vrc_native = f"-VRCNATIVE{widget_id}-" + self.gui_circular_crop_left = f"-CIRCLECROPLEFT{widget_id}-" + self.gui_circular_crop_right = f"-CIRCLECROPRIGHT{widget_id}-" + self.gui_update_check = f"-UPDATECHECK{widget_id}-" + self.gui_threshold_slider = f"-BLOBTHRESHOLD{widget_id}-" + self.main_config = main_config + self.config = main_config.settings + self.osc_queue = osc_queue + + # Define the window's contents + self.general_settings_layout = [ + + [ + sg.Checkbox( + "Flip Left Eye X Axis", + default=self.config.gui_flip_x_axis_left, + key=self.gui_flip_x_axis_left, + background_color='#424042', + tooltip = "Flips the left eye's X axis.", + ), + sg.Checkbox( + "Flip Right Eye X Axis", + default=self.config.gui_flip_x_axis_right, + key=self.gui_flip_x_axis_right, + background_color='#424042', + tooltip = "Flips the right eye's X axis.", + ), + + # ], + #[ + sg.Checkbox( + "Flip Y Axis", + default=self.config.gui_flip_y_axis, + key=self.gui_flip_y_axis, + background_color='#424042', + tooltip = "Flips the eye's Y axis.", + ), + ], + [sg.Checkbox( + "VRC Native Eyetracking", + default=self.config.gui_vrc_native, + key=self.gui_vrc_native, + background_color='#424042', + tooltip = "Toggle VRCFT output or VRC native", + ), + sg.Checkbox( + "Dual Eye Falloff", + default=self.config.gui_eye_falloff, + key=self.gui_eye_falloff, + background_color='#424042', + tooltip = "If one eye stops tracking, we send tracking data from your other eye.", + ), + ], + [sg.Checkbox( + "Check For Updates", + default=self.config.gui_update_check, + key=self.gui_update_check, + background_color='#424042', + tooltip = "Toggle update check on launch.", + ), + ], + + + [ + sg.Text("Tracking Algorithim Settings:", background_color='#242224'), + ], + + [sg.Checkbox( + "", + default=self.config.gui_HSRAC, + key=self.gui_HSRAC, + background_color='#424042', + tooltip = "Our flagship algoritim, utilizing both HSF and RANSAC for best tracking quality and lighting resistance.", + ), + sg.Combo(['1','2','3','4'], + default_value=self.config.gui_HSRACP, + key=self.gui_HSRACP, + background_color='#424042', + text_color='white', + button_arrow_color= "black", + button_background_color = "#6f4ca1", + tooltip = "Select the priority of eyetracking algorithims.", + ), + sg.Text("HSRAC", background_color='#424042'), + # ], + # [ + sg.Checkbox( + "", + default=self.config.gui_HSF, + key=self.gui_HSF, + background_color='#424042', + tooltip = "HSF Is a new, lower resolution tracking algorithim that provides excelent resilancy to lighting conditions and great speed.", + ), + sg.Combo(['1','2','3','4','5'], + default_value=self.config.gui_HSFP, + key=self.gui_HSFP, + background_color='#424042', + text_color='white', + button_arrow_color= "black", + button_background_color = "#6f4ca1", + tooltip = "Select the priority of eyetracking algorithims.", + ), + sg.Text("Haar Surround Feature", background_color='#424042'), + ], + [sg.Checkbox( + "", + default=self.config.gui_DADDY, + key=self.gui_DADDY, + background_color='#424042', + tooltip = "DADDY Uses a Deep learning algorithm. This has a big CPU usage impact.", + ), + sg.Combo(['1','2','3','4','5'], + default_value=self.config.gui_DADDYP, + key=self.gui_DADDYP, + background_color='#424042', + text_color='white', + button_arrow_color= "black", + button_background_color = "#6f4ca1", + tooltip = "Select the priority of eyetracking algorithims.", + ), + sg.Text("DADDY", background_color='#424042'), + # ], + # [ + sg.Checkbox( + "", + default=self.config.gui_RANSAC3D, + key=self.gui_RANSAC3D, + background_color='#424042', + tooltip = "RANSAC3D provides good tracking quality, however does not do well in bad lighting conditions.", + ), + sg.Combo(['1','2','3','4','5'], + default_value=self.config.gui_RANSAC3DP, + key=self.gui_RANSAC3DP, + background_color='#424042', + text_color='white', + button_arrow_color= "black", + button_background_color = "#6f4ca1", + tooltip = "Select the priority of eyetracking algorithims.", + ), + sg.Text("RANSAC 3D", background_color='#424042'), + ], + [ + sg.Checkbox( + "", + default=self.config.gui_BLOB, + key=self.gui_BLOB, + background_color='#424042', + tooltip = "Blob tracking is the oldest and worst tracking algorithm, it provides fast, though sometimes innaccurate tracking.", + ), + sg.Combo(['1','2','3','4','5'], + default_value=self.config.gui_BLOBP, + key=self.gui_BLOBP, + background_color='#424042', + text_color='white', + button_arrow_color= "black", + button_background_color = "#6f4ca1", + tooltip = "Select the priority of eyetracking algorithims.", + ), + sg.Text("Blob", background_color='#424042'), + ], + [ + sg.Checkbox( + "Intensity Based Openness", + default=self.config.gui_IBO, + key=self.gui_IBO, + background_color='#424042', + ), + sg.Checkbox( + "Bianary Blink Algo", + default=self.config.gui_BLINK, + key=self.gui_BLINK, + background_color='#424042', + ), + ], + [ + sg.Checkbox( + "Left Eye Circle crop", + default=self.config.gui_circular_crop_left, + key=self.gui_circular_crop_left, + background_color='#424042', + ), + sg.Checkbox( + "Right Eye Circle crop", + default=self.config.gui_circular_crop_right, + key=self.gui_circular_crop_right, + background_color='#424042', + ), + ], + + [sg.Checkbox( + "HSF: Skip Auto Radius", + default=self.config.gui_skip_autoradius, + key=self.gui_skip_autoradius, + background_color='#424042', + tooltip = "To gain more control and possibly better tracking quality of HSF, please disable auto radius to enable manual adjustment.", + ), + + sg.Text("HSF Radius:", background_color='#424042'), + sg.Slider( + range=(1, 50), + default_value=self.config.gui_HSF_radius, + orientation="h", + key=self.gui_HSF_radius, + background_color='#424042', + tooltip = "Adjusts the radius paramater for HSF. Only adjust if you are having tracking issues.", + ), + ], + [sg.Text("RANSAC Thresh Add", background_color='#424042'), + sg.Slider( + range=(1, 50), + default_value=self.config.gui_thresh_add, + orientation="h", + key=self.gui_thresh_add, + background_color='#424042', + tooltip = "Adjusts the ammount of threshold to add to RANSAC. Usefull for fine tuning your setup.", + ), + # ], + # [ + sg.Text("Blob Threshold", background_color='#424042'), #TODO make this for right and left eyes? I dont know how vital that is.. + sg.Slider( + range=(0, 110), + default_value=self.config.gui_threshold, + orientation="h", + key=self.gui_threshold_slider, + background_color='#424042', + tooltip = "Adjusts the threshold for blob tracking.", + ), + ], + [sg.Text("Min Blob Size:", background_color='#424042'), + sg.Slider( + range=(1, 50), + default_value=self.config.gui_blob_minsize, + orientation="h", + key=self.gui_blob_minsize, + background_color='#424042', + tooltip = "Minimun size a blob has to be for blob tracking.", + ), + + sg.Text("Max Blob Size:", background_color='#424042'), + sg.Slider( + range=(1, 50), + default_value=self.config.gui_blob_maxsize, + orientation="h", + key=self.gui_blob_maxsize, + background_color='#424042', + tooltip = "Maximum size a blob can be for blob tracking.", + ), + + + ], + + ] + + + self.widget_layout = [ + [ + sg.Text("General Settings:", background_color='#242224'), + ], + [ + sg.Column(self.general_settings_layout, key=self.gui_general_settings_layout, background_color='#424042' ), + ], + ] + + self.cancellation_event = Event() # Set the event until start is called, otherwise we can block if shutdown is called. + self.cancellation_event.set() + self.image_queue = Queue() + + + def started(self): + return not self.cancellation_event.is_set() + + def start(self): + # If we're already running, bail + if not self.cancellation_event.is_set(): + return + self.cancellation_event.clear() + + def stop(self): + # If we're not running yet, bail + if self.cancellation_event.is_set(): + return + self.cancellation_event.set() + + def render(self, window, event, values): + # If anything has changed in our configuration settings, change/update those. + changed = False + + if self.config.gui_osc_port != int(values[self.gui_osc_port]): + print(self.config.gui_osc_port, values[self.gui_osc_port]) + try: + int(values[self.gui_osc_port]) + if len(values[self.gui_osc_port]) <= 5: + self.config.gui_osc_port = int(values[self.gui_osc_port]) + changed = True + else: + print("\033[91m[ERROR] OSC port value must be an integer 0-65535\033[0m") + except: + print("\033[91m[ERROR] OSC port value must be an integer 0-65535\033[0m") + + if self.config.gui_osc_receiver_port != int(values[self.gui_osc_receiver_port]): + try: + int(values[self.gui_osc_receiver_port]) + if len(values[self.gui_osc_receiver_port]) <= 5: + self.config.gui_osc_receiver_port = int(values[self.gui_osc_receiver_port]) + changed = True + else: + print("\033[91m[ERROR] OSC receive port value must be an integer 0-65535\033[0m") + except: + print("\033[91m[ERROR] OSC receive port value must be an integer 0-65535\033[0m") + + if self.config.gui_osc_address != values[self.gui_osc_address]: + self.config.gui_osc_address = values[self.gui_osc_address] + changed = True + + if self.config.gui_osc_recenter_address != values[self.gui_osc_recenter_address]: + self.config.gui_osc_recenter_address = values[self.gui_osc_recenter_address] + changed = True + + if self.config.gui_osc_recalibrate_address != values[self.gui_osc_recalibrate_address]: + self.config.gui_osc_recalibrate_address = values[self.gui_osc_recalibrate_address] + changed = True + + if self.config.gui_min_cutoff != values[self.gui_min_cutoff]: + self.config.gui_min_cutoff = values[self.gui_min_cutoff] + changed = True + + if self.config.gui_speed_coefficient != values[self.gui_speed_coefficient]: + self.config.gui_speed_coefficient = values[self.gui_speed_coefficient] + changed = True + + if self.config.gui_flip_x_axis_right != values[self.gui_flip_x_axis_right]: + self.config.gui_flip_x_axis_right = values[self.gui_flip_x_axis_right] + changed = True + + if self.config.gui_flip_x_axis_left != values[self.gui_flip_x_axis_left]: + self.config.gui_flip_x_axis_left = values[self.gui_flip_x_axis_left] + changed = True + + if self.config.gui_HSFP != int(values[self.gui_HSFP]): + self.config.gui_HSFP = int(values[self.gui_HSFP]) + changed = True + + if self.config.gui_HSF != values[self.gui_HSF]: + self.config.gui_HSF = values[self.gui_HSF] + changed = True + + if self.config.gui_vrc_native != values[self.gui_vrc_native]: + self.config.gui_vrc_native = values[self.gui_vrc_native] + changed = True + + if self.config.gui_DADDYP != int(values[self.gui_DADDYP]): + self.config.gui_DADDYP = int(values[self.gui_DADDYP]) + changed = True + + if self.config.gui_DADDY != values[self.gui_DADDY]: + self.config.gui_DADDY = values[self.gui_DADDY] + changed = True + + if self.config.gui_RANSAC3DP != int(values[self.gui_RANSAC3DP]): #TODO check that priority order is unique/auto fix it. + self.config.gui_RANSAC3DP = int(values[self.gui_RANSAC3DP]) + changed = True + + if self.config.gui_RANSAC3D != values[self.gui_RANSAC3D]: + self.config.gui_RANSAC3D = values[self.gui_RANSAC3D] + changed = True + + if self.config.gui_HSRACP != int(values[self.gui_HSRACP]): + self.config.gui_HSRACP = int(values[self.gui_HSRACP]) + changed = True + + if self.config.gui_HSRAC != values[self.gui_HSRAC]: + self.config.gui_HSRAC = values[self.gui_HSRAC] + changed = True + + if self.config.gui_skip_autoradius != values[self.gui_skip_autoradius]: + self.config.gui_skip_autoradius = values[self.gui_skip_autoradius] + changed = True + + if self.config.gui_update_check != values[self.gui_update_check]: + self.config.gui_update_check = values[self.gui_update_check] + changed = True + + if self.config.gui_BLINK != values[self.gui_BLINK]: + self.config.gui_BLINK = values[self.gui_BLINK] + changed = True + + if self.config.gui_IBO != values[self.gui_IBO]: + self.config.gui_IBO = values[self.gui_IBO] + changed = True + + if self.config.gui_circular_crop_left != values[self.gui_circular_crop_left]: + self.config.gui_circular_crop_left = values[self.gui_circular_crop_left] + changed = True + + if self.config.gui_circular_crop_right != values[self.gui_circular_crop_right]: + self.config.gui_circular_crop_right = values[self.gui_circular_crop_right] + changed = True + + if self.config.gui_HSF_radius != int(values[self.gui_HSF_radius]): + self.config.gui_HSF_radius = int(values[self.gui_HSF_radius]) + changed = True + + if self.config.gui_flip_y_axis != values[self.gui_flip_y_axis]: + self.config.gui_flip_y_axis = values[self.gui_flip_y_axis] + changed = True + + if self.config.gui_BLOB != values[self.gui_BLOB]: + self.config.gui_BLOB = values[self.gui_BLOB] + changed = True + + if self.config.gui_BLOBP != int(values[self.gui_BLOBP]): + self.config.gui_BLOBP = int(values[self.gui_BLOBP]) + changed = True + + if self.config.gui_threshold != values[self.gui_threshold_slider]: + self.config.gui_threshold = int(values[self.gui_threshold_slider]) + changed = True + + if self.config.gui_thresh_add != values[self.gui_thresh_add]: + self.config.gui_thresh_add = int(values[self.gui_thresh_add]) + changed = True + + if self.config.gui_eye_falloff != values[self.gui_eye_falloff]: + self.config.gui_eye_falloff = values[self.gui_eye_falloff] + changed = True + + if self.config.gui_blob_maxsize != values[self.gui_blob_maxsize]: + self.config.gui_blob_maxsize = values[self.gui_blob_maxsize] + changed = True + + if self.config.gui_ROSC != values[self.gui_ROSC]: + self.config.gui_ROSC = values[self.gui_ROSC] + changed = True + + if changed: + self.main_config.save() + # print(changed) + # changed = False + # print(changed) + self.osc_queue.put((EyeId.SETTINGS)) diff --git a/EyeTrackApp/blob.py b/EyeTrackApp/blob.py index f6b71db..01bf125 100644 --- a/EyeTrackApp/blob.py +++ b/EyeTrackApp/blob.py @@ -45,8 +45,8 @@ def circle_crop(self): radius = int(float(self.lkg_projected_sphere["axes"][0])) self.xc = int(float(self.lkg_projected_sphere["center"][0])) self.yc = int(float(self.lkg_projected_sphere["center"][1])) - if radius < 8: #minimum size - radius = 8 + if radius < 10: #minimum size + radius = 10 # draw filled circle in white on black background as mask mask = np.zeros((ht, wd), dtype=np.uint8) mask = cv2.circle(mask, (self.xc, self.yc), radius, 255, -1) diff --git a/EyeTrackApp/eye.py b/EyeTrackApp/eye.py index 04eb6ef..9f85f64 100644 --- a/EyeTrackApp/eye.py +++ b/EyeTrackApp/eye.py @@ -6,6 +6,7 @@ class EyeId(IntEnum): LEFT = 1 BOTH = 2 SETTINGS = 3 + ALGOSETTINGS = 4 class EyeInfoOrigin(Enum): diff --git a/EyeTrackApp/eyetrackapp.py b/EyeTrackApp/eyetrackapp.py index 9071b9b..ac3bf72 100644 --- a/EyeTrackApp/eyetrackapp.py +++ b/EyeTrackApp/eyetrackapp.py @@ -10,6 +10,8 @@ from config import EyeTrackConfig from eye import EyeId from osc import VRChatOSCReceiver, VRChatOSC from settings_widget import SettingsWidget +from algo_settings_widget import AlgoSettingsWidget + from utils.misc_utils import is_nt @@ -25,15 +27,16 @@ WINDOW_NAME = "EyeTrackApp" RIGHT_EYE_NAME = "-RIGHTEYEWIDGET-" LEFT_EYE_NAME = "-LEFTEYEWIDGET-" SETTINGS_NAME = "-SETTINGSWIDGET-" +ALGO_SETTINGS_NAME = "-ALGOSETTINGSWIDGET-" LEFT_EYE_RADIO_NAME = "-LEFTEYERADIO-" RIGHT_EYE_RADIO_NAME = "-RIGHTEYERADIO-" BOTH_EYE_RADIO_NAME = "-BOTHEYERADIO-" SETTINGS_RADIO_NAME = "-SETTINGSRADIO-" - +ALGO_SETTINGS_RADIO_NAME = "-ALGOSETTINGSRADIO-" page_url = "https://github.com/RedHawk989/EyeTrackVR/releases/latest" -appversion = "EyeTrackApp 0.2.0 BETA 4" +appversion = "EyeTrackApp 0.2.0 BETA 5" def main(): @@ -93,6 +96,7 @@ def main(): settings = [ SettingsWidget(EyeId.SETTINGS, config, osc_queue), + AlgoSettingsWidget(EyeId.SETTINGS, config, osc_queue), ] layout = [ @@ -125,6 +129,13 @@ def main(): default=(config.eye_display_id == EyeId.SETTINGS), key=SETTINGS_RADIO_NAME, ), + sg.Radio( + "Algo Settings", + "EYESELECTRADIO", + background_color="#292929", + default=(config.eye_display_id == EyeId.ALGOSETTINGS), + key=ALGO_SETTINGS_RADIO_NAME, + ), ], [ sg.Column( @@ -148,6 +159,13 @@ def main(): visible=(config.eye_display_id in [EyeId.SETTINGS]), background_color="#424042", ), + sg.Column( + settings[1].widget_layout, + vertical_alignment="top", + key=ALGO_SETTINGS_NAME, + visible=(config.eye_display_id in [EyeId.ALGOSETTINGS]), + background_color="#424042", + ), ], ] @@ -155,9 +173,10 @@ def main(): eyes[1].start() if config.eye_display_id in [EyeId.RIGHT, EyeId.BOTH]: eyes[0].start() - - if config.eye_display_id in [EyeId.SETTINGS, EyeId.BOTH]: + if config.eye_display_id in [EyeId.SETTINGS]: settings[0].start() + if config.eye_display_id in [EyeId.ALGOSETTINGS]: + settings[1].start() #self.main_config.eye_display_id # the eye's needs to be running before it is passed to the OSC @@ -198,32 +217,38 @@ def main(): eyes[0].start() eyes[1].stop() settings[0].stop() + settings[1].stop() window[RIGHT_EYE_NAME].update(visible=True) window[LEFT_EYE_NAME].update(visible=False) window[SETTINGS_NAME].update(visible=False) + window[ALGO_SETTINGS_NAME].update(visible=False) config.eye_display_id = EyeId.RIGHT config.settings.tracker_single_eye = 2 config.save() elif values[LEFT_EYE_RADIO_NAME] and config.eye_display_id != EyeId.LEFT: settings[0].stop() + settings[1].stop() eyes[0].stop() eyes[1].start() window[RIGHT_EYE_NAME].update(visible=False) window[LEFT_EYE_NAME].update(visible=True) window[SETTINGS_NAME].update(visible=False) + window[ALGO_SETTINGS_NAME].update(visible=False) config.eye_display_id = EyeId.LEFT config.settings.tracker_single_eye = 1 config.save() elif values[BOTH_EYE_RADIO_NAME] and config.eye_display_id != EyeId.BOTH: settings[0].stop() + settings[1].stop() eyes[0].stop() eyes[1].start() eyes[0].start() window[LEFT_EYE_NAME].update(visible=True) window[RIGHT_EYE_NAME].update(visible=True) window[SETTINGS_NAME].update(visible=False) + window[ALGO_SETTINGS_NAME].update(visible=False) config.eye_display_id = EyeId.BOTH config.settings.tracker_single_eye = 0 config.save() @@ -231,13 +256,27 @@ def main(): elif values[SETTINGS_RADIO_NAME] and config.eye_display_id != EyeId.SETTINGS: eyes[0].stop() eyes[1].stop() + settings[1].stop() settings[0].start() window[RIGHT_EYE_NAME].update(visible=False) window[LEFT_EYE_NAME].update(visible=False) window[SETTINGS_NAME].update(visible=True) + window[ALGO_SETTINGS_NAME].update(visible=False) config.eye_display_id = EyeId.SETTINGS config.save() + elif values[ALGO_SETTINGS_RADIO_NAME] and config.eye_display_id != EyeId.ALGOSETTINGS: + eyes[0].stop() + eyes[1].stop() + settings[0].stop() + settings[1].start() + window[RIGHT_EYE_NAME].update(visible=False) + window[LEFT_EYE_NAME].update(visible=False) + window[SETTINGS_NAME].update(visible=False) + window[ALGO_SETTINGS_NAME].update(visible=True) + config.eye_display_id = EyeId.ALGOSETTINGS + config.save() + # Otherwise, render all of our cameras for eye in eyes: if eye.started(): diff --git a/EyeTrackApp/ransac.py b/EyeTrackApp/ransac.py index 3d017aa..13399fd 100644 --- a/EyeTrackApp/ransac.py +++ b/EyeTrackApp/ransac.py @@ -151,8 +151,8 @@ def circle_crop(self): radius = int(float(self.lkg_projected_sphere["axes"][0])) self.xc = int(float(self.lkg_projected_sphere["center"][0])) self.yc = int(float(self.lkg_projected_sphere["center"][1])) - if radius < 8: #minimum size - radius = 8 + if radius < 10: #minimum size + radius = 10 # draw filled circle in white on black background as mask mask = np.zeros((ht, wd), dtype=np.uint8) mask = cv2.circle(mask, (self.xc, self.yc), radius, 255, -1)