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. + diff --git a/PySimpleGUI_Colorizer.py b/PySimpleGUI_Colorizer.py index 161de48..e4174e7 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 = '7 June 2020' prototxt = r'model/colorization_deploy_v2.prototxt' model = r'model/colorization_release_v2.caffemodel' @@ -79,12 +79,23 @@ 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 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()], @@ -97,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'): @@ -117,14 +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('') - image, colorized = colorize_image(filename) - imgbytes_out = cv2.imencode('.png', colorized)[1].tobytes() - window['-OUT-'].update(data=imgbytes_out) + if values['-MAKEGRAY-']: + 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) + else: + image, colorized = colorize_image(filename) + + window['-OUT-'].update(data=cv2.imencode('.png', colorized)[1].tobytes()) except: continue elif event == '-PHOTO-': # Colorize photo button clicked @@ -135,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()) @@ -174,7 +186,7 @@ def colorize_image(image_filename=None, cv2_frame=None): 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) 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.