From 683698747eb17c779ea539f85b26088abc906393 Mon Sep 17 00:00:00 2001 From: PySimpleGUI Date: Wed, 1 Apr 2020 16:52:46 -0400 Subject: [PATCH 1/6] New option to convert photo to grayscale first --- PySimpleGUI_Colorizer.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/PySimpleGUI_Colorizer.py b/PySimpleGUI_Colorizer.py index 161de48..54f507f 100644 --- a/PySimpleGUI_Colorizer.py +++ b/PySimpleGUI_Colorizer.py @@ -85,6 +85,7 @@ def colorize_image(image_filename=None, cv2_frame=None): left_col = [[sg.Text('Folder'), sg.In(size=(25,1), enable_events=True ,key='-FOLDER-'), sg.FolderBrowse()], [sg.Listbox(values=[], enable_events=True, size=(40,20),key='-FILE LIST-')], + [sg.CBox('Convert to gray first',key='-MAKEGRAY-')], [sg.Text('Version ' + version, font='Courier 8')]] images_col = [[sg.Text('Input file:'), sg.In(enable_events=True, key='-IN FILE-'), sg.FileBrowse()], @@ -122,7 +123,18 @@ def colorize_image(image_filename=None, cv2_frame=None): window['-OUT-'].update(data='') window['-IN FILE-'].update('') - image, colorized = colorize_image(filename) + if values['-MAKEGRAY-']: + gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # Convert webcam frame to grayscale + gray_3_channels = np.zeros_like(image) # Convert grayscale frame (single channel) to 3 channels + gray_3_channels[:, :, 0] = gray + gray_3_channels[:, :, 1] = gray + gray_3_channels[:, :, 2] = gray + image, colorized = colorize_image(cv2_frame=gray_3_channels) + gray_in = cv2.imencode('.png', gray_3_channels)[1].tobytes() + window['-IN-'].update(data=gray_in) + else: + image, colorized = colorize_image(filename) + imgbytes_out = cv2.imencode('.png', colorized)[1].tobytes() window['-OUT-'].update(data=imgbytes_out) except: From ef145370eb4aeca5a629be6d9b0a5cf7c013dad9 Mon Sep 17 00:00:00 2001 From: PySimpleGUI Date: Wed, 1 Apr 2020 16:59:26 -0400 Subject: [PATCH 2/6] Bumped version number --- PySimpleGUI_Colorizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PySimpleGUI_Colorizer.py b/PySimpleGUI_Colorizer.py index 54f507f..4a4caf1 100644 --- a/PySimpleGUI_Colorizer.py +++ b/PySimpleGUI_Colorizer.py @@ -19,7 +19,7 @@ import PySimpleGUI as sg import os.path -version = '9 Feb 2020' +version = '1 April 2020' prototxt = r'model/colorization_deploy_v2.prototxt' model = r'model/colorization_release_v2.caffemodel' From e17761554520ee0f43162343a9ba5af11730662c Mon Sep 17 00:00:00 2001 From: PySimpleGUI Date: Thu, 2 Apr 2020 16:24:59 -0400 Subject: [PATCH 3/6] Make grayscale refactor. Don't clear output image if only changing input image --- PySimpleGUI_Colorizer.py | 68 ++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/PySimpleGUI_Colorizer.py b/PySimpleGUI_Colorizer.py index 4a4caf1..1d528df 100644 --- a/PySimpleGUI_Colorizer.py +++ b/PySimpleGUI_Colorizer.py @@ -19,7 +19,7 @@ import PySimpleGUI as sg import os.path -version = '1 April 2020' +version = '2 April 2020' prototxt = r'model/colorization_deploy_v2.prototxt' model = r'model/colorization_release_v2.caffemodel' @@ -79,6 +79,16 @@ def colorize_image(image_filename=None, cv2_frame=None): colorized = (255 * colorized).astype("uint8") return image, colorized + +def convert_to_grayscale(frame): + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # Convert webcam frame to grayscale + gray_3_channels = np.zeros_like(frame) # Convert grayscale frame (single channel) to 3 channels + gray_3_channels[:, :, 0] = gray + gray_3_channels[:, :, 1] = gray + gray_3_channels[:, :, 2] = gray + return gray_3_channels + + # --------------------------------- The GUI --------------------------------- # First the window layout...2 columns @@ -98,7 +108,7 @@ def colorize_image(image_filename=None, cv2_frame=None): window = sg.Window('Photo Colorizer', layout, grab_anywhere=True) # ----- Run the Event Loop ----- -colorized = cap = None +prev_filename = colorized = cap = None while True: event, values = window.read() if event in (None, 'Exit'): @@ -118,25 +128,18 @@ def colorize_image(image_filename=None, cv2_frame=None): try: filename = os.path.join(values['-FOLDER-'], values['-FILE LIST-'][0]) image = cv2.imread(filename) - imgbytes_in = cv2.imencode('.png', image)[1].tobytes() - window['-IN-'].update(data=imgbytes_in) + window['-IN-'].update(data=cv2.imencode('.png', image)[1].tobytes()) window['-OUT-'].update(data='') window['-IN FILE-'].update('') if values['-MAKEGRAY-']: - gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # Convert webcam frame to grayscale - gray_3_channels = np.zeros_like(image) # Convert grayscale frame (single channel) to 3 channels - gray_3_channels[:, :, 0] = gray - gray_3_channels[:, :, 1] = gray - gray_3_channels[:, :, 2] = gray + gray_3_channels = convert_to_grayscale(image) + window['-IN-'].update(data=cv2.imencode('.png', gray_3_channels)[1].tobytes()) image, colorized = colorize_image(cv2_frame=gray_3_channels) - gray_in = cv2.imencode('.png', gray_3_channels)[1].tobytes() - window['-IN-'].update(data=gray_in) else: image, colorized = colorize_image(filename) - imgbytes_out = cv2.imencode('.png', colorized)[1].tobytes() - window['-OUT-'].update(data=imgbytes_out) + window['-OUT-'].update(data=cv2.imencode('.png', colorized)[1].tobytes()) except: continue elif event == '-PHOTO-': # Colorize photo button clicked @@ -147,38 +150,35 @@ def colorize_image(image_filename=None, cv2_frame=None): filename = os.path.join(values['-FOLDER-'], values['-FILE LIST-'][0]) else: continue - image, colorized = colorize_image(filename) - imgbytes_in = cv2.imencode('.png', image)[1].tobytes() - imgbytes_out = cv2.imencode('.png', colorized)[1].tobytes() - window['-IN-'].update(data=imgbytes_in) - window['-OUT-'].update(data=imgbytes_out) + if values['-MAKEGRAY-']: + gray_3_channels = convert_to_grayscale(cv2.imread(filename)) + window['-IN-'].update(data=cv2.imencode('.png', gray_3_channels)[1].tobytes()) + image, colorized = colorize_image(cv2_frame=gray_3_channels) + else: + image, colorized = colorize_image(filename) + window['-IN-'].update(data=cv2.imencode('.png', image)[1].tobytes()) + window['-OUT-'].update(data=cv2.imencode('.png', colorized)[1].tobytes()) except: continue elif event == '-IN FILE-': # A single filename was chosen filename = values['-IN FILE-'] - try: - image = cv2.imread(filename) - imgbytes_in = cv2.imencode('.png', image)[1].tobytes() - window['-IN-'].update(data=imgbytes_in) - window['-OUT-'].update(data='') - except: - continue + if filename != prev_filename: + prev_filename = filename + try: + image = cv2.imread(filename) + window['-IN-'].update(data=cv2.imencode('.png', image)[1].tobytes()) + except: + continue elif event == '-WEBCAM-': # Webcam button clicked sg.popup_quick_message('Starting up your Webcam... this takes a moment....', auto_close_duration=1, background_color='red', text_color='white', font='Any 16') window['-WEBCAM-'].update('Stop Webcam', button_color=('white','red')) cap = cv2.VideoCapture(0) if not cap else cap while True: # Loop that reads and shows webcam until stop button ret, frame = cap.read() # Read a webcam frame - gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # Convert webcam frame to grayscale - gray_3_channels = np.zeros_like(frame) # Convert grayscale frame (single channel) to 3 channels - gray_3_channels[:, :, 0] = gray - gray_3_channels[:, :, 1] = gray - gray_3_channels[:, :, 2] = gray + gray_3_channels = convert_to_grayscale(frame) image, colorized = colorize_image(cv2_frame=gray_3_channels) # Colorize the 3-channel grayscale frame - imgbytes_in = cv2.imencode('.png', gray_3_channels)[1].tobytes() - imgbytes_out = cv2.imencode('.png', colorized)[1].tobytes() - window['-IN-'].update(data=imgbytes_in) - window['-OUT-'].update(data=imgbytes_out) + window['-IN-'].update(data=cv2.imencode('.png', gray_3_channels)[1].tobytes()) + window['-OUT-'].update(data=cv2.imencode('.png', colorized)[1].tobytes()) event, values = window.read(timeout=0) # Update the window outputs and check for new events if event in (None, '-WEBCAM-', 'Exit'): # Clicked the Stop Webcam button or closed window entirely window['-WEBCAM-'].update('Start Webcam', button_color=sg.theme_button_color()) From 65a48fae7c4b71a47f5b2fcade7f7968b41afb0a Mon Sep 17 00:00:00 2001 From: PySimpleGUI Date: Sun, 7 Jun 2020 14:11:38 -0400 Subject: [PATCH 4/6] Changed getting filename to save as. Was trying to get the file as an "oipen" instead of save --- PySimpleGUI_Colorizer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PySimpleGUI_Colorizer.py b/PySimpleGUI_Colorizer.py index 1d528df..e4174e7 100644 --- a/PySimpleGUI_Colorizer.py +++ b/PySimpleGUI_Colorizer.py @@ -19,7 +19,7 @@ import PySimpleGUI as sg import os.path -version = '2 April 2020' +version = '7 June 2020' prototxt = r'model/colorization_deploy_v2.prototxt' model = r'model/colorization_release_v2.caffemodel' @@ -186,7 +186,7 @@ def convert_to_grayscale(frame): window['-OUT-'].update('') break elif event == '-SAVE-' and colorized is not None: # Clicked the Save File button - filename = sg.popup_get_file('Save colorized image.\nColorized image be saved in format matching the extension you enter.') + filename = sg.popup_get_file('Save colorized image.\nColorized image be saved in format matching the extension you enter.', save_as=True) try: if filename: cv2.imwrite(filename, colorized) From 12aba321ca8b8c0988b9611d3e8361b98a430582 Mon Sep 17 00:00:00 2001 From: PySimpleGUI Date: Sun, 26 Jul 2020 14:57:20 -0400 Subject: [PATCH 5/6] Addition of new multi-window version of the webcam colorizer. Shows 3 video windows. --- PySimpleGUI_Colorizer_Webcam_Multi_Window.py | 165 +++++++++++++++++++ readme.md | 18 ++ 2 files changed, 183 insertions(+) create mode 100644 PySimpleGUI_Colorizer_Webcam_Multi_Window.py diff --git a/PySimpleGUI_Colorizer_Webcam_Multi_Window.py b/PySimpleGUI_Colorizer_Webcam_Multi_Window.py new file mode 100644 index 0000000..e6d3f3a --- /dev/null +++ b/PySimpleGUI_Colorizer_Webcam_Multi_Window.py @@ -0,0 +1,165 @@ +""" + July 2020 - experimental multi-window version of the webcam portion of window colorizer program + + Colorization based on the Zhang Image Colorization Deep Learning Algorithm + This header to remain with this code. + + The implementation of the colorization algorithm is from PyImageSearch + You can learn how the algorithm works and the details of this implementation here: + https://www.pyimagesearch.com/2019/02/25/black-and-white-image-colorization-with-opencv-and-deep-learning/ + + You will need to download the pre-trained data from this location and place in the model folder: + https://www.dropbox.com/s/dx0qvhhp5hbcx7z/colorization_release_v2.caffemodel?dl=1 + + GUI implemented in PySimpleGUI by the PySimpleGUI group + Of course, enjoy, learn , play, have fun! + Copyright 2020 PySimpleGUI +""" + +import numpy as np +import cv2 +import PySimpleGUI as sg +import os.path + +prototxt = r'model/colorization_deploy_v2.prototxt' +model = r'model/colorization_release_v2.caffemodel' +points = r'model/pts_in_hull.npy' +points = os.path.join(os.path.dirname(__file__), points) +prototxt = os.path.join(os.path.dirname(__file__), prototxt) +model = os.path.join(os.path.dirname(__file__), model) +if not os.path.isfile(model): + sg.popup_scrolled('Missing model file', 'You are missing the file "colorization_release_v2.caffemodel"', + 'Download it and place into your "model" folder', 'You can download this file from this location:\n', r'https://www.dropbox.com/s/dx0qvhhp5hbcx7z/colorization_release_v2.caffemodel?dl=1') + exit() +net = cv2.dnn.readNetFromCaffe(prototxt, model) # load model from disk +pts = np.load(points) + +# add the cluster centers as 1x1 convolutions to the model +class8 = net.getLayerId("class8_ab") +conv8 = net.getLayerId("conv8_313_rh") +pts = pts.transpose().reshape(2, 313, 1, 1) +net.getLayer(class8).blobs = [pts.astype("float32")] +net.getLayer(conv8).blobs = [np.full([1, 313], 2.606, dtype="float32")] + +def colorize_image(image_filename=None, cv2_frame=None): + """ + Where all the magic happens. Colorizes the image provided. Can colorize either + a filename OR a cv2 frame (read from a web cam most likely) + :param image_filename: (str) full filename to colorize + :param cv2_frame: (cv2 frame) + :return: cv2 frame colorized image in cv2 format + """ + # load the input image from disk, scale the pixel intensities to the range [0, 1], and then convert the image from the BGR to Lab color space + image = cv2.imread(image_filename) if image_filename else cv2_frame + scaled = image.astype("float32") / 255.0 + lab = cv2.cvtColor(scaled, cv2.COLOR_BGR2LAB) + + # resize the Lab image to 224x224 (the dimensions the colorization network accepts), split channels, extract the 'L' channel, and then perform mean centering + resized = cv2.resize(lab, (224, 224)) + L = cv2.split(resized)[0] + L -= 50 + + # pass the L channel through the network which will *predict* the 'a' and 'b' channel values + 'print("[INFO] colorizing image...")' + net.setInput(cv2.dnn.blobFromImage(L)) + ab = net.forward()[0, :, :, :].transpose((1, 2, 0)) + + # resize the predicted 'ab' volume to the same dimensions as our input image + ab = cv2.resize(ab, (image.shape[1], image.shape[0])) + + # grab the 'L' channel from the *original* input image (not the resized one) and concatenate the original 'L' channel with the predicted 'ab' channels + L = cv2.split(lab)[0] + colorized = np.concatenate((L[:, :, np.newaxis], ab), axis=2) + + # convert the output image from the Lab color space to RGB, then clip any values that fall outside the range [0, 1] + colorized = cv2.cvtColor(colorized, cv2.COLOR_LAB2BGR) + colorized = np.clip(colorized, 0, 1) + + # the current colorized image is represented as a floating point data type in the range [0, 1] -- let's convert to an unsigned 8-bit integer representation in the range [0, 255] + colorized = (255 * colorized).astype("uint8") + return colorized + + +def convert_to_grayscale(frame): + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # Convert webcam frame to grayscale + gray_3_channels = np.zeros_like(frame) # Convert grayscale frame (single channel) to 3 channels + gray_3_channels[:, :, 0] = gray + gray_3_channels[:, :, 1] = gray + gray_3_channels[:, :, 2] = gray + return gray_3_channels + + +def make_video_window(title, location): + return sg.Window(title, [[sg.Image(key='-IMAGE-')]], finalize=True, margins=(0,0), element_padding=(0,0), location=location) + +def convert_cvt_to_data(cv2_frame): + return cv2.imencode('.png', cv2_frame)[1].tobytes() + + +def main(): + # --------------------------------- The GUI --------------------------------- + + layout = [ [sg.Text('Colorized Webcam Demo', font='Any 18')], + [sg.Button('Start Webcam', key='-WEBCAM-'), sg.Button('Exit')]] + + # ----- Make the starting window ----- + window_start = sg.Window('Webcam Colorizer', layout, grab_anywhere=True, finalize=True) + + # ----- Run the Event Loop ----- + cap, playback_active = None, False + while True: + window, event, values = sg.read_all_windows(timeout=10) + if event == 'Exit' or (window == window_start and event is None): + break + elif event == '-WEBCAM-': # Webcam button clicked + if not playback_active: + sg.popup_quick_message('Starting up your Webcam... this takes a moment....', auto_close_duration=1, background_color='red', text_color='white', font='Any 16') + window_start['-WEBCAM-'].update('Stop Webcam', button_color=('white','red')) + cap = cv2.VideoCapture(0) if not cap else cap + window_raw_camera = make_video_window('Your Webcam Raw Video', (300,200)) + window_gray_camera = make_video_window('Video as Grayscale', (1000,200)) + window_colorized_camera = make_video_window('Your Colorized Video', (1700,200)) + playback_active = True + else: + playback_active = False + window['-WEBCAM-'].update('Start Webcam', button_color=sg.theme_button_color()) + window_raw_camera.close() + window_gray_camera.close() + window_colorized_camera.close() + elif event == sg.TIMEOUT_EVENT and playback_active: + ret, frame = cap.read() # Read a webcam frame + + # display raw image + if window_raw_camera: + window_raw_camera['-IMAGE-'].update(data=convert_cvt_to_data(frame)) + # display gray image + gray_3_channels = convert_to_grayscale(frame) + if window_gray_camera: + window_gray_camera['-IMAGE-'].update(data=convert_cvt_to_data(gray_3_channels)) + # display colorized image + if window_colorized_camera: + window_colorized_camera['-IMAGE-'].update(data=convert_cvt_to_data(colorize_image(cv2_frame=gray_3_channels))) + + # if a window closed + if event is None: + if window == window_raw_camera: + window_raw_camera.close() + window_raw_camera = None + elif window == window_gray_camera: + window_gray_camera.close() + window_gray_camera = None + elif window == window_colorized_camera: + window_colorized_camera.close() + window_colorized_camera = None + + # If playback is active, but all camera windows closed, indicate not longer playing and change button color + if playback_active and window_colorized_camera is None and window_gray_camera is None and window_raw_camera is None: + playback_active = False + window_start['-WEBCAM-'].update('Start Webcam', button_color=sg.theme_button_color()) + + # ----- Exit program ----- + window.close() + +if __name__ == '__main__': + main() + diff --git a/readme.md b/readme.md index 3c08a24..08839cc 100644 --- a/readme.md +++ b/readme.md @@ -83,3 +83,21 @@ Here is more eye-candy courtesy of Deep Learning ![SNAG-0626](https://user-images.githubusercontent.com/46163555/71523945-43c03a00-2899-11ea-8bf2-ee6ac2216286.jpg) ![SNAG-0620](https://user-images.githubusercontent.com/46163555/71523946-43c03a00-2899-11ea-9f25-2f2b2c882ad3.jpg) + + +----------------------------------- + +# Webcam Multi-Window Demo + +In July 2020 a new demo was added that uses the new (released to GitHub only at this point) multi-window support. This demo shows 3 video windows: + +1. Your webcam's raw video stream +2. Grayscale version of the video +3. Fully colorized colored of the grayscale video + +Here's a screenshot to give you a rough idea of what to expect from the demo. The colors likely didn't do so well in this specific shot as there was a lot of background lighting. + +![SNAG-0881](https://user-images.githubusercontent.com/46163555/88486988-9e189a80-cf4f-11ea-8dc7-727b7539bab9.jpg) + + +You will need to use the PySimpleGUI.py file from the project's GitHub http://www.PySimpleGUI.com. Minimum version is 4.26.0.13. From fcd37370e7ec274ab2d7f7f13d7072912c78e89b Mon Sep 17 00:00:00 2001 From: PySimpleGUI Date: Sun, 24 Apr 2022 11:22:32 -0400 Subject: [PATCH 6/6] Updated Contributing --- CONTRIBUTING.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8486cf6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,39 @@ +## Contributing to PySimpleGUI + +Hi there! Mike here....thank you for taking time to read this document. + +### Open Source License, but Private Development + +PySimpleGUI is different than most projects on GitHub. It is licensed using the "Open Source License" LGPL3. However, the coding and development of the project is not structured in the same way most open source projects are structured. + +This project/account does not accept user submitted code nor documentation. + +### You Can Still Contribute + +#### Write Applications, Use PySimpleGUI, Make Repos, Post Screenshots, Write Tutorials, Teach Others + +These are a few of the ways you can directly contribute to PySimpleGUI. Using the package to make cool stuff and helping others learn how to use it to make cool stuff is a big help to PySimpleGUI. **Everyone** learns from seeing other people's implementations. It's through user's creating applications that new problems and needs are discovered. These have had a profound and positive impact on the project in the past. + +#### Make Suggestions + +There are 100's of open issues in the main PySimpleGUI GitHub account that are actively worked, daily. There are 1,000s that have been completed. The evolution of PySimpleGUI over the years has been a combination of my vision for the product and ideas from users. So many people have helped make PySimpleGUI better. + +### Pull Requests + +Pull requests are *not being accepted* for the project. This includes sending code changes via other means than "pull requests". Plainly put, code you send will not be used. + +I don't mean to be ugly. This isn't personal. Heck, I don't know "you",the reader personally. It's not about ego. It's complicated. The result is that it allows me to dedicate my life to this project. It's what's required, for whatever reason, for me to do this. That's the best explanation I have. I love and respect the users of this work. + + +### Bug Fixes + +If you file an Issue for a bug, have located the bug, and found a fix in 10 lines of code or less.... and you wish to share your fix with the community, then feel free to include it with the filed Issue. If it's longer than 10 lines and wish to discuss it, then send an email to help@PySimpleGUI.org. + +## Thank You + +This project comes from a well-meaning, love of computing, and helping others place. It's not about "me", it's about ***you***. + +The support from the user community has been ***amazing***. Your passion for creating PySimpleGUI applications is infectious. Every "thank you" is noticed and appreciated! Your passion for wanting to see PySimpleGUI improve is neither ignored nor unappreciated. At a time when the Internet can feel toxic, there's been expressions of appreciation, gratitude, and encouragement that's unbelievable. I'm touched on a very frequent basis and am filled with gratitude myself as a result. + +It's understood that this way of development of a Python package is unorthodox. You may find it frustrating and slow, but hope you can respect the decision for it to operate in this manner and be supportive. +