Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import cv2 | |
| import imutils | |
| from paddleocr import PaddleOCR, draw_ocr | |
| from PIL import Image | |
| import io | |
| import os | |
| import numpy as np | |
| import ast | |
| import operator | |
| import matplotlib.pyplot as plt | |
| st.set_page_config( | |
| page_title='OpenCV Image processing', layout ="wide", | |
| initial_sidebar_state="expanded", | |
| ) | |
| os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" | |
| st.markdown(""" | |
| <style> | |
| .block-container { | |
| padding-top: 1rem; | |
| padding-bottom: 1rem; | |
| padding-left: 1rem; | |
| padding-right: 2rem; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| ################################################################################################### | |
| ## INITIALISATIONS | |
| ################################################################################################### | |
| ### | |
| def initializations(): | |
| print("Initializations ...") | |
| out_dict_lang_ppocr = {'Abaza': 'abq', 'Adyghe': 'ady', 'Afrikaans': 'af', 'Albanian': 'sq', \ | |
| 'Angika': 'ang', 'Arabic': 'ar', 'Avar': 'ava', 'Azerbaijani': 'az', 'Belarusian': 'be', \ | |
| 'Bhojpuri': 'bho','Bihari': 'bh','Bosnian': 'bs','Bulgarian': 'bg','Chinese & English': 'ch', \ | |
| 'Chinese Traditional': 'chinese_cht', 'Croatian': 'hr', 'Czech': 'cs', 'Danish': 'da', \ | |
| 'Dargwa': 'dar', 'Dutch': 'nl', 'English': 'en', 'Estonian': 'et', 'French': 'fr', \ | |
| 'German': 'german','Goan Konkani': 'gom','Hindi': 'hi','Hungarian': 'hu','Icelandic': 'is', \ | |
| 'Indonesian': 'id', 'Ingush': 'inh', 'Irish': 'ga', 'Italian': 'it', 'Japan': 'japan', \ | |
| 'Kabardian': 'kbd', 'Korean': 'korean', 'Kurdish': 'ku', 'Lak': 'lbe', 'Latvian': 'lv', \ | |
| 'Lezghian': 'lez', 'Lithuanian': 'lt', 'Magahi': 'mah', 'Maithili': 'mai', 'Malay': 'ms', \ | |
| 'Maltese': 'mt', 'Maori': 'mi', 'Marathi': 'mr', 'Mongolian': 'mn', 'Nagpur': 'sck', \ | |
| 'Nepali': 'ne', 'Newari': 'new', 'Norwegian': 'no', 'Occitan': 'oc', 'Persian': 'fa', \ | |
| 'Polish': 'pl', 'Portuguese': 'pt', 'Romanian': 'ro', 'Russia': 'ru', 'Saudi Arabia': 'sa', \ | |
| 'Serbian(cyrillic)': 'rs_cyrillic', 'Serbian(latin)': 'rs_latin', 'Slovak': 'sk', \ | |
| 'Slovenian': 'sl', 'Spanish': 'es', 'Swahili': 'sw', 'Swedish': 'sv', 'Tabassaran': 'tab', \ | |
| 'Tagalog': 'tl', 'Tamil': 'ta', 'Telugu': 'te', 'Turkish': 'tr', 'Ukranian': 'uk', \ | |
| 'Urdu': 'ur', 'Uyghur': 'ug', 'Uzbek': 'uz', 'Vietnamese': 'vi', 'Welsh': 'cy'} | |
| out_dict_interpolation = {"INTER_LINEAR": cv2.INTER_LINEAR, | |
| "INTER_NEAREST": cv2.INTER_NEAREST, | |
| # "INTER_LINEAR_EXACT": cv2.INTER_LINEAR_EXACT, | |
| "INTER_AREA": cv2.INTER_AREA, | |
| "INTER_CUBIC": cv2.INTER_CUBIC, | |
| "INTER_LANCZOS4": cv2.INTER_LANCZOS4, | |
| # "INTER_NEAREST_EXACT": cv2.INTER_NEAREST_EXACT, | |
| # "INTER_MAX": cv2.INTER_MAX, | |
| # "WARP_FILL_OUTLIERS": cv2.WARP_FILL_OUTLIERS, | |
| # "WARP_INVERSE_MAP": cv2.WARP_INVERSE_MAP, | |
| } | |
| out_dict_thresholding_type = {"THRESH_BINARY": cv2.THRESH_BINARY, | |
| "THRESH_BINARY_INV": cv2.THRESH_BINARY_INV, | |
| "THRESH_TRUNC": cv2.THRESH_TRUNC, | |
| "THRESH_TOZERO": cv2.THRESH_TOZERO, | |
| } | |
| out_dict_adaptative_method = {"ADAPTIVE_THRESH_MEAN_C": cv2.ADAPTIVE_THRESH_MEAN_C, | |
| "ADAPTIVE_THRESH_GAUSSIAN_C": cv2.ADAPTIVE_THRESH_GAUSSIAN_C} | |
| return out_dict_lang_ppocr, out_dict_interpolation, out_dict_thresholding_type, out_dict_adaptative_method | |
| ################################################################################################### | |
| ## FONTIONS | |
| ################################################################################################### | |
| ### | |
| def load_image(in_image_file): | |
| """Load input file and open it | |
| Args: | |
| in_image_file (string or Streamlit UploadedFile): image to consider | |
| Returns: | |
| matrix : input file opened with Opencv | |
| """ | |
| #if isinstance(in_image_file, str): | |
| # out_image_path = "img."+in_image_file.split('.')[-1] | |
| #else: | |
| # out_image_path = "img."+in_image_file.name.split('.')[-1] | |
| if isinstance(in_image_file, str): | |
| out_image_path = "tmp_"+in_image_file | |
| else: | |
| out_image_path = "tmp_"+in_image_file.name | |
| img = Image.open(in_image_file) | |
| img_saved = img.save(out_image_path) | |
| # Read image | |
| # out_image_orig = Image.open(out_image_path) | |
| out_image_cv2 = cv2.cvtColor(cv2.imread(out_image_path), cv2.COLOR_BGR2RGB) | |
| st.session_state.resize = False | |
| st.session_state.scaling_factor = None | |
| st.session_state.interpolation = None | |
| st.session_state.rotate = None | |
| st.session_state.angle = None | |
| st.session_state.convolution = None | |
| st.session_state.text_convol = None | |
| st.session_state.convol_kernel = None | |
| st.session_state.averaging = None | |
| st.session_state.averaging_kernel_size = None | |
| st.session_state.gaussian_bluring = None | |
| st.session_state.gb_kernel_size = None | |
| st.session_state.sigmaX = None | |
| st.session_state.sigmaY = None | |
| st.session_state.median_bluring = None | |
| st.session_state.mb_kernel_size = None | |
| st.session_state.bilateral_filtering = None | |
| st.session_state.d = None | |
| st.session_state.sigma_color = None | |
| st.session_state.sigma_space = None | |
| st.session_state.erosion = None | |
| st.session_state.erosion_kernel_size = None | |
| st.session_state.nb_iter_erosion = None | |
| st.session_state.dilation = None | |
| st.session_state.dilation_kernel_size = None | |
| st.session_state.nb_iter_dilation = None | |
| st.session_state.binarization = None | |
| st.session_state.bin_thresh = None | |
| st.session_state.bin_thresh = None | |
| st.session_state.bin_thresholding_type = None | |
| st.session_state.bin_otsu = None | |
| st.session_state.thresh_typ = None | |
| st.session_state.adaptative_thresh = None | |
| st.session_state.at_thresholding_type = None | |
| st.session_state.at_max_value = None | |
| st.session_state.at_adaptative_method = None | |
| st.session_state.at_block_size = None | |
| st.session_state.at_const = None | |
| st.session_state.processed_image = None | |
| return out_image_cv2, out_image_path | |
| ### | |
| def eval_expr(expr): | |
| """Eval numeric expression | |
| Args: | |
| expr (string): numeric expression | |
| Returns: | |
| float: eval result | |
| """ | |
| result = 1. | |
| # Dictionnary of authorized operators | |
| operators = { | |
| ast.Add: operator.add, | |
| ast.Sub: operator.sub, | |
| ast.Mult: operator.mul, | |
| ast.Div: operator.truediv, | |
| ast.Pow: operator.pow, | |
| ast.USub: operator.neg, | |
| } | |
| def _eval(node): | |
| if isinstance(node, ast.Expression): | |
| return _eval(node.body) | |
| elif isinstance(node, ast.Constant): # nombre | |
| return node.value | |
| elif isinstance(node, ast.BinOp): # opΓ©rations binaires | |
| return operators[type(node.op)](_eval(node.left), _eval(node.right)) | |
| elif isinstance(node, ast.UnaryOp): # opΓ©rations unaires (-n) | |
| return operators[type(node.op)](_eval(node.operand)) | |
| else: | |
| raise TypeError(node) | |
| try: | |
| parsed = ast.parse(expr, mode='eval') | |
| result = _eval(parsed.body) | |
| except: | |
| pass | |
| return result | |
| ### | |
| def text_kernel_to_latex(text_eval): | |
| """Try to parse a kernel text description like: 1/6 * [[1,1],[1,1]] | |
| Args: | |
| text_eval (string): the string with the kernel expression | |
| Returns: | |
| string: left part of input string before * | |
| list: right part of input string after * | |
| string: latex expression corresponding to the text kernel input | |
| """ | |
| list_eval = text_eval.split('*') | |
| text_kernel = list_eval[-1].strip() | |
| list_kernel = ast.literal_eval(text_kernel) | |
| latex = "\\begin{bmatrix}\n" | |
| for row in list_kernel: | |
| latex += " & ".join(map(str, row)) + " \\\\\n" | |
| latex += "\\end{bmatrix}" | |
| text_coeff = 1. | |
| latex_text = latex | |
| if len(list_eval) > 1: | |
| text_coeff = list_eval[0].strip() | |
| latex_text = text_coeff + ' ' + latex | |
| return text_coeff, list_kernel, latex_text | |
| ### | |
| def get_img_fig(img): | |
| """Plot image with matplotlib, in order to have image size | |
| Args: | |
| img (Image): Image to show | |
| Returns: | |
| Matplotlib figure | |
| """ | |
| fig = plt.figure() | |
| if len(img.shape) == 3: | |
| plt.imshow(img, cmap=None) | |
| else: | |
| plt.imshow(img, cmap='gray') | |
| return fig | |
| def show_latex(latex_code): | |
| st.latex(latex_code) | |
| ################################################################################################### | |
| ## STREAMLIT APP | |
| ################################################################################################### | |
| st.title(''':orange[Image processing with OpenCV]''') | |
| st.write("") | |
| st.write("") | |
| st.write("") | |
| st.set_option("client.showErrorDetails", False) | |
| dict_lang_ppocr, dict_interpolation, dict_thresholding_type, dict_adaptative_method = initializations() | |
| cols = st.columns([0.25, 0.25, 0.5]) | |
| cols[0].markdown("#### :orange[Choose picture:]") | |
| img_typ = cols[0].radio("#### :orange[Choose picture type:]", ['Upload file', 'Take a picture', 'Use a demo file'], \ | |
| index=0) | |
| if img_typ == 'Upload file': | |
| image_file = cols[1].file_uploader("Upload a file:", type=["png","jpg","jpeg"]) | |
| if img_typ == 'Take a picture': | |
| image_file = cols[1].camera_input("Take a picture:") | |
| if img_typ == 'Use a demo file': | |
| image_file = 'img_demo_enhance.png' | |
| ##----------- Process input image ----------------------------------------------------------------- | |
| if image_file is not None: | |
| img_cv2, image_path = load_image(image_file) | |
| cols[2].markdown('#### :orange[Original image]') | |
| cnt_img_ori = cols[2].container(height=300, border=False) | |
| #cnt_img_ori.image(img_cv2) #, use_container_width=True) | |
| cnt_img_ori.pyplot(get_img_fig(img_cv2), use_container_width=True) | |
| col1, col2 = st.columns([0.5, 0.5]) #gap="medium") | |
| col1.markdown('#### :orange[Processed image]') | |
| list_op = [] | |
| if col1.checkbox("GrayScale"): | |
| try: | |
| img_first = cv2.cvtColor(img_cv2.copy(), cv2.COLOR_BGR2GRAY) | |
| list_op.append("Grayscale") | |
| except Exception as e: | |
| st.exception(e) | |
| else: | |
| img_first = img_cv2.copy() | |
| if col1.checkbox("Bit-wise inversion"): | |
| try: | |
| img_first = cv2.bitwise_not(img_first) | |
| list_op.append("Bit-wise inversion") | |
| except Exception as e: | |
| st.exception(e) | |
| # Processed image construction | |
| cnt_img_wrk = col1.container(height=500, border=False) | |
| img_processed = cnt_img_wrk.empty() | |
| img_wrk = img_first.copy() | |
| if st.session_state.resize: | |
| try: | |
| img_wrk = cv2.resize(img_wrk, None, fx=st.session_state.scaling_factor, | |
| fy=st.session_state.scaling_factor, | |
| interpolation=dict_interpolation[st.session_state.interpolation]) | |
| list_op.append("Resize - fx="+str(st.session_state.scaling_factor)+", fy="+ | |
| str(st.session_state.scaling_factor)+", interpolation="+ | |
| st.session_state.interpolation) | |
| except Exception as e: | |
| st.exception(e) | |
| if st.session_state.rotate: | |
| try: | |
| img_wrk = imutils.rotate(img_wrk, angle=st.session_state.angle) | |
| list_op.append("Rotate - angle="+str(st.session_state.angle)) | |
| except Exception as e: | |
| st.exception(e) | |
| if st.session_state.convolution: | |
| try: | |
| img_wrk = cv2.filter2D(src=img_wrk, ddepth=-1, kernel=st.session_state.convol_kernel) | |
| list_op.append("Filtering - Custom 2D Convolution - kernel="+ st.session_state.text_convol) | |
| except Exception as e: | |
| st.exception(e) | |
| if st.session_state.averaging: | |
| try: | |
| img_wrk = cv2.blur(src=img_wrk, ksize=st.session_state.averaging_kernel_size) | |
| list_op.append("Filtering - Averaging - kernel_size="+ | |
| str(st.session_state.averaging_kernel_size)) | |
| except Exception as e: | |
| st.exception(e) | |
| if st.session_state.gaussian_bluring: | |
| try: | |
| img_wrk = cv2.GaussianBlur(src=img_wrk, ksize=st.session_state.gb_kernel_size, \ | |
| sigmaX=st.session_state.sigmaX, sigmaY=st.session_state.sigmaY) | |
| list_op.append("Filtering - Gaussian Blurring - ksize="+ \ | |
| str(st.session_state.gb_kernel_size)+", sigmaX="+ | |
| str(st.session_state.sigmaX)+", sigmaY="+str(st.session_state.sigmaY)) | |
| except Exception as e: | |
| st.exception(e) | |
| if st.session_state.median_bluring: | |
| try: | |
| img_wrk = cv2.medianBlur(img_wrk, st.session_state.mb_kernel_size) | |
| list_op.append("Filtering - Median Blurring - kernel_size="+ \ | |
| str(st.session_state.mb_kernel_size)) | |
| except Exception as e: | |
| st.exception(e) | |
| if st.session_state.bilateral_filtering: | |
| try: | |
| img_wrk = cv2.bilateralFilter(img_wrk, st.session_state.d, st.session_state.sigma_color, | |
| st.session_state.sigma_space) | |
| list_op.append("Filtering - Bilateral Filtering - d="+ str(st.session_state.d)+ | |
| ", sigma_color="+str(st.session_state.sigma_color)+ \ | |
| ", sigma_space="+str(st.session_state.sigma_space)) | |
| except Exception as e: | |
| st.exception(e) | |
| if st.session_state.erosion: | |
| try: | |
| kernel = np.ones((st.session_state.erosion_kernel_size, | |
| st.session_state.erosion_kernel_size), | |
| np.uint8) | |
| img_wrk = cv2.erode(img_wrk, kernel, iterations=st.session_state.nb_iter_erosion) | |
| list_op.append("Erosion - kernel_size="+str(st.session_state.erosion_kernel_size)+ \ | |
| ", iterations="+str(st.session_state.nb_iter_erosion)) | |
| except Exception as e: | |
| st.exception(e) | |
| if st.session_state.dilation: | |
| try: | |
| kernel = np.ones((st.session_state.dilation_kernel_size, | |
| st.session_state.dilation_kernel_size), | |
| np.uint8) | |
| img_wrk = cv2.dilate(img_wrk, kernel, iterations=st.session_state.nb_iter_dilation) | |
| list_op.append("Dilation - kernel_size="+str(st.session_state.dilation_kernel_size )+ \ | |
| ", iterations="+str(st.session_state.nb_iter_dilation)) | |
| except Exception as e: | |
| st.exception(e) | |
| if st.session_state.binarization: | |
| try: | |
| ret, img_wrk = cv2.threshold(img_wrk, st.session_state.bin_thresh, | |
| st.session_state.bin_value, | |
| st.session_state.thresh_typ) | |
| list_op.append("Thresholding - thresh="+str(st.session_state.bin_thresh)+ \ | |
| ", maxval="+str(st.session_state.bin_value)+", type="+ \ | |
| st.session_state.bin_thresholding_type+", otsu="+ \ | |
| str(st.session_state.bin_otsu)) | |
| except Exception as e: | |
| st.exception(e) | |
| if st.session_state.adaptative_thresh: | |
| try: | |
| img_wrk = cv2.adaptiveThreshold(img_wrk, st.session_state.at_max_value, | |
| dict_adaptative_method[st.session_state.at_adaptative_method], | |
| dict_thresholding_type[st.session_state.at_thresholding_type], | |
| st.session_state.at_block_size, st.session_state.at_const) | |
| list_op.append("Adaptative thresholding - maxValue="+ | |
| str(st.session_state.at_max_value)+", adaptiveMethod="+ | |
| st.session_state.at_adaptative_method+", thresholdType"+ | |
| ", thresholding_type="+st.session_state.at_thresholding_type+ | |
| ", blockSize="+str(st.session_state.at_block_size)+", C="+ | |
| str(st.session_state.at_const)) | |
| except Exception as e: | |
| st.exception(e) | |
| # Show image | |
| img_processed.pyplot(get_img_fig(img_wrk), use_container_width=True) | |
| st.session_state.processed_image = img_wrk | |
| # Process | |
| col2.markdown('#### :orange[Check & enhance]') | |
| with col2.expander(":blue[Image processing]", expanded=False): | |
| tab1, tab2, tab3, tab4, tab5 = \ | |
| st.tabs(["Resize", "Rotate", "Filtering", | |
| "Morphologie", "Thresholding"]) | |
| with tab1: # Resize | |
| with tab1.form("Resize parameters"): | |
| st.session_state.scaling_factor = st.slider("Scaling factor :", 0.1, 20., 1., 0.1) | |
| cols_tab1 = st.columns([0.1, 0.9], gap="medium", vertical_alignment="center") | |
| cols_tab1[0].markdown("π¬", help="""An interpolation functionβs goal is | |
| to examine neighborhoods of pixels and use these neighborhoods to optically increase or decrease | |
| the size of the image without introducing distortions (or at least as few distortions | |
| as possible).\n | |
| ```cv2.INTER_LINEAR``` This option uses the bilinear interpolation algorithm. Unlike INTER_NEAREST, | |
| this does the interpolation in two dimensions and predicts the function used to calculate the color | |
| of a pixel. This algorithm is effective in handling visual distortions while zooming or | |
| enlarging an image.\n | |
| ```cv2.INTER_NEAREST``` This option uses the nearest neighbor interpolation algorithm. It retains | |
| the sharpness of the edges though the overall image may be blurred.\n | |
| ```cv2.INTER_LINEAR_EXACT```is a modification of ```INTER_LINEAR``` and both uses bilinear | |
| interpolation algorithm. The only difference is that the calculations in ```INTER_LINEAR_EXACT``` | |
| are accurate to a bit.\n | |
| ```cv2.INTER_AREA``` option uses resampling using pixel area relation technique. While enlarging | |
| images, INTER_AREA work same as INTER_NEAREST. In other cases, ```INTER_AREA works``` better in | |
| image decimation and avoiding false inference patterns in images (moire pattern).\n | |
| ```cv2.INTER_CUBIC``` option uses bicubic interpolation technique. This is an extension of cubic | |
| interpolation technique and is used for 2 dimension regular grid patterns.\n | |
| ```cv2.INTER_LANCZOS4``` option uses Lanczos interpolation over 8 x 8 pixel neighborhood technique. | |
| It uses Fourier series and Chebyshev polynomials and is suited for images with large number of | |
| small size details.\n | |
| ```cv2.INTER_NEAREST_EXACT ``` is a modification of INTER_NEAREST with bit level accuracy.\n | |
| ```cv2.INTER_MAX ``` option uses mask for interpolation codes.\n | |
| ```cv2.WARP_FILL_OUTLIERS ``` interpolation technique skips the outliers during interpolation calculations.\n | |
| ```cv2.WARP_INVERSE_MAP ``` option uses inverse transformation technique for interpolation.\n""") | |
| cols_tab1[0].link_button("π", "https://opencv.org/blog/resizing-and-rescaling-images-with-opencv/#h-resizing-with-different-interpolation-methods") | |
| st.session_state.interpolation = cols_tab1[1].selectbox("Interpolation method:", | |
| list(dict_interpolation.keys())) | |
| c1, c2 = st.columns(2) | |
| apply_tab1 = c1.toggle("Apply", help="Click here to indicate whether the operation should be carried out or not, then validate with Confirm.", key=1) | |
| with c2: | |
| submit_tab1 = st.form_submit_button(":green[Confirm]") | |
| if submit_tab1: | |
| st.session_state.resize = apply_tab1 | |
| st.rerun() | |
| with tab2: # Rotate | |
| with tab2.form("Rotate parameters"): | |
| st.session_state.angle = st.slider("Angle :", 0, 360, 0, step=10) | |
| c1, c2 = st.columns(2) | |
| apply_tab2 = c1.toggle("Apply", help="Click here to indicate whether the operation should be carried out or not, then validate with Confirm.", key=2) | |
| with c2: | |
| submit_tab2 = st.form_submit_button(":green[Confirm]") | |
| if submit_tab2: | |
| st.session_state.rotate = apply_tab2 | |
| st.rerun() | |
| with tab3: # Filtering | |
| st.write("π :blue[*More about image filtering*] π \ | |
| [here](https://learnopencv.com/image-filtering-using-convolution-in-opencv/)") | |
| selection = st.segmented_control("Filtering type", | |
| ["Custom 2D Convolution", "Blurring"], | |
| selection_mode="single") | |
| match selection: | |
| case "Custom 2D Convolution": | |
| with st.form("tab3_1"): | |
| st.write("π :blue[*More about convolution matrix*] π \ | |
| [here](https://en.wikipedia.org/wiki/Kernel_(image_processing))") | |
| text_convol = st.text_input("Write your custom kernel here (example : 1/9 * [[1,1,1], [1,1,1], [1,1,1]]):", | |
| value=None) | |
| kernel = None | |
| if text_convol is not None: | |
| try: | |
| text_coeff, list_kernel, latex_code = text_kernel_to_latex(text_convol) | |
| coeff = eval_expr(text_coeff) | |
| kernel = coeff * np.array(list_kernel) | |
| show_latex(latex_code) | |
| except Exception as e: | |
| st.exception(e) | |
| text_convol = None | |
| else: | |
| text_coeff, list_kernel, latex_code = \ | |
| text_kernel_to_latex("1/9 * [[1,1,1], [1,1,1], [1,1,1]]") | |
| show_latex(latex_code) | |
| c1, c2 = st.columns(2) | |
| apply_tab31 = c1.toggle("Apply", help="Click here to indicate whether the operation should be carried out or not, then validate with Confirm.", key=3) | |
| with c2: | |
| submit_tab31 = st.form_submit_button(":green[Confirm]") | |
| if submit_tab31: | |
| st.session_state.convolution = apply_tab31 | |
| st.session_state.text_convol = text_convol | |
| st.session_state.convol_kernel = kernel | |
| st.rerun() | |
| case "Blurring": | |
| st.write("π :blue[*More about blurring techniques*] π \ | |
| [here](https://docs.opencv.org/4.x/d4/d13/tutorial_py_filtering.html)") | |
| b1, b2, b3, b4 = st.tabs(["Averaging", "Gaussian Blurring", "Median Blurring", | |
| "Bilateral Filtering"]) | |
| # typ_blurring = st.segmented_control("Bluring type", | |
| # ["Averaging", "Gaussian Blurring", "Median Blurring", | |
| # "Bilateral Filtering"], | |
| # selection_mode="multi") | |
| with b1: | |
| with st.form("tab_32a"): | |
| st.markdown("π¬ :green[Averaging?]", | |
| help="This is done by convolving an image with a normalized box filter.\ | |
| It simply takes the average of all the pixels under the kernel \ | |
| area and replaces the central element." | |
| ) | |
| kernel_width = st.slider("Kernel size width:", 2, 20, None, 1) | |
| kernel_height = st.slider("Kernel size height:", 2, 20, None, 1) | |
| c1, c2 = st.columns(2) | |
| apply_tab32a = c1.toggle("Apply", help="Click here to indicate whether the operation should be carried out or not, then validate with Confirm.", key=4) | |
| with c2: | |
| submit_tab32a = st.form_submit_button(":green[Confirm]") | |
| if submit_tab32a: | |
| st.session_state.averaging = apply_tab32a | |
| st.session_state.averaging_kernel_size = (kernel_width, kernel_height) | |
| st.rerun() | |
| with b2: | |
| with st.form("tab_32b"): | |
| st.markdown("π¬ :green[Gaussian Blurringing?]", | |
| help="In this method, instead of a box filter, a Gaussian kernel is used. \ | |
| We should specify the width and height of the kernel which should be positive and odd. \ | |
| We also should specify the standard deviation in the X and Y directions, `sigmaX` and `sigmaY` respectively. \ | |
| If only `sigmaX` is specified, `sigmaY` is taken as the same as sigmaX. If both are given as zeros, they are \ | |
| calculated from the kernel size.\n \ | |
| Gaussian blurring is highly effective in removing Gaussian noise from an image.") | |
| kernel_width = st.slider("Kernel size width:", 2, 20, None, 1,) | |
| kernel_height = st.slider("Kernel size height:", 2, 20, None, 1) | |
| st.markdown("Standard deviations of the Gaussian kernel:", | |
| help="""The parameters `sigmaX` and `sigmaY` represent the standard deviations | |
| of the Gaussian kernel in the horizontal (X) and vertical (Y) directions, | |
| respectively. These values control the extent of blurring applied to the image.β\n | |
| **Typical Values for sigmaX and sigmaY:** | |
| - Low values (e.g., 1β3): Apply a mild blur, useful for slight noise reduction while preserving image details.β | |
| - Moderate values (e.g., 5β10): Produce a more noticeable blur, helpful for reducing more significant noise or smoothing out textures. | |
| - High values (e.g., >10): Result in a strong blur, which can be used for artistic effects or to obscure details.β | |
| It's common practice to set sigmaX and sigmaY to 0. In this case, OpenCV calculates the standard deviations based on the kernel size (ksize). | |
| If only sigmaX is specified and sigmaY is set to 0, OpenCV uses the same value for both directions. β\n | |
| **Recommendations:** | |
| - Specify sigmaX and sigmaY explicitly: For precise control over the blurring effect, define both parameters based on the desired outcome.β | |
| - Use sigmaX = 0 and sigmaY = 0: To allow OpenCV to compute the standard deviations automatically from the kernel size.β | |
| - Choose an appropriate kernel size: The ksize parameter should be a tuple of positive odd integers (e.g., (3, 3), (5, 5)). | |
| """) | |
| sigmaX = st.slider("sigmaX:", 0, 20, 0, 1) | |
| sigmaY = st.slider("sigmaY:", 0, 20, 0, 1) | |
| c1, c2 = st.columns(2) | |
| apply_tab32b = c1.toggle("Apply", help="Click here to indicate whether the operation should be carried out or not, then validate with Confirm.", key=5) | |
| with c2: | |
| submit_tab32b = st.form_submit_button(":green[Confirm]") | |
| if submit_tab32b: | |
| st.session_state.gaussian_bluring = apply_tab32b | |
| st.session_state.gb_kernel_size = (kernel_width, kernel_height) | |
| st.session_state.sigmaX = sigmaX | |
| st.session_state.sigmaY = sigmaY | |
| st.rerun() | |
| with b3: | |
| with st.form("tab_32c"): | |
| st.markdown("π¬ :green[Median Blurring?]", | |
| help="It takes the median of all the pixels under the \ | |
| kernel area and the central element is replaced with this median value. Interestingly, in the above \ | |
| filters, the central element is a newly calculated value which may be a pixel value in the image or a new value. \ | |
| But in median blurring, the central element is always replaced by some pixel value in the image. \ | |
| It reduces the noise effectively. Its kernel size should be a positive odd integer.\n \ | |
| Median blurring is highly effective against salt-and-pepper noise in an image.") | |
| kernel_size = st.slider("Kernel size:", 3, 15, None, 2, key=101) | |
| c1, c2 = st.columns(2) | |
| apply_tab32c = c1.toggle("Apply", help="Click here to indicate whether the operation should be carried out or not, then validate with Confirm.", key=6) | |
| with c2: | |
| submit_tab32c = st.form_submit_button(":green[Confirm]") | |
| if submit_tab32c: | |
| st.session_state.median_bluring = apply_tab32c | |
| st.session_state.mb_kernel_size = kernel_size | |
| st.rerun() | |
| with b4: | |
| with st.form("tab_32d"): | |
| st.markdown("π¬ :green[Bilateral Filtering?]", | |
| help="It is highly effective in noise removal while \ | |
| keeping edges sharp. But the operation is slower compared to other filters. We already saw that a \ | |
| Gaussian filter takes the neighbourhood around the pixel and finds its Gaussian weighted average. \ | |
| This Gaussian filter is a function of space alone, that is, nearby pixels are considered while \ | |
| filtering. It doesn't consider whether pixels have almost the same intensity. It doesn't consider \ | |
| whether a pixel is an edge pixel or not. So it blurs the edges also, which we don't want to do.\n \ | |
| Bilateral filtering also takes a Gaussian filter in space, but one more Gaussian filter which is \ | |
| a function of pixel difference. \ | |
| The Gaussian function of space makes sure that only nearby pixels are considered for blurring, \ | |
| while the Gaussian function of intensity difference makes sure that only those pixels with similar \ | |
| intensities to the central pixel are considered for blurring. \ | |
| So it preserves the edges since pixels at edges will have large intensity variation.") | |
| st.markdown("Diameter of each pixel neighborhood that is used during filtering:", | |
| help=""" **Effect:**\n | |
| A larger `d` value means that more neighboring pixels are considered in the filtering process, leading to a more pronounced | |
| blurring effect. Conversely, a smaller `d` focuses the filter on a tighter area, preserving more details.β | |
| **Automatic Calculation:**\n | |
| If `d` is set to a non-positive value (e.g., 0 or negative), OpenCV automatically calculates it based on the sigmaSpace parameter. | |
| Specifically, the radius is computed as `radius = cvRound(sigmaSpace * 1.5)`, and then `d = radius * 2 + 1` to ensure it's an odd | |
| number. This ensures that the kernel has a central pixel. β | |
| **Typical Values for `d`:**\n | |
| The choice of d depends on the desired balance between noise reduction and edge preservation:β | |
| - Small d (e.g., 5 to 9): Suitable for subtle smoothing while maintaining edge sharpness.β | |
| - Medium d (e.g., 9 to 15): Offers a balance between noise reduction and detail preservation.β | |
| - Large d (e.g., 15 and above): Provides stronger blurring, which may be useful for artistic effects but can lead to loss of | |
| fine details.β | |
| **Recommendations:**\n | |
| - Large filters (d > 5) are very slow, so it is recommended to use `d=5` for real-time applications, and perhaps | |
| `d=9` for offline applications that need heavy noise filtering. | |
| - Start with Moderate Values: Begin with `d=9`, `sigmaColor=75`, and `sigmaSpace=75` as a baseline. Adjust these values based on | |
| the specific requirements of your application.β | |
| - Consider Image Size: For larger images, you might need to increase `d` to achieve a noticeable effect. Conversely, | |
| for smaller images, a smaller `d` might suffice.β | |
| - Balance with `sigmaColor` and `sigmaSpace`: Ensure that `d` is appropriately balanced with `sigmaColor` and | |
| `sigmaSpace`. An excessively large `sigmaSpace` with a small `d` might not utilize the full potential of the spatial filtering. | |
| """) | |
| d_value = st.slider("d:", 3, 15, None, 2) | |
| st.markdown("`sigmaColor` and `sigmaSpace`:", help=""" | |
| `sigmaColor`: This parameter defines the filter sigma in the color space. A larger value means that pixels with more significant | |
| color differences will be mixed together, resulting in areas of semi-equal color.β | |
| `sigmaSpace`: This parameter defines the filter sigma in the coordinate space. A larger value means that pixels farther apart | |
| will influence each other as long as their colors are close enough.β\n | |
| These parameters work together to ensure that the filter smooths the image while preserving edges.β | |
| **Typical Values for `sigmaColor` and `sigmaSpace`:**\n | |
| The choice of `sigmaColor` and `sigmaSpace` depends on the specific application and the desired effect. | |
| However, some commonly used values are:β | |
| - `sigmaColor`: Values around 75 are often used for general smoothing while preserving edges.β | |
| - `sigmaSpace`: Similarly, values around 75 are typical for maintaining edge sharpness while reducing noise.β | |
| For example, applying the bilateral filter with `d=9`, `sigmaColor=75`, and `sigmaSpace=75` is a common practice. | |
| **Recommendations:**`\n | |
| - Start with Equal Values: Setting `sigmaColor` and `sigmaSpace` to the same value (e.g., 75) is a good starting point.β | |
| - Adjust Based on Results: If the image appears too blurred, reduce the values. If noise is still present, increase them.β | |
| - Consider Image Characteristics: For images with high noise, higher values may be necessary. For images where edge preservation | |
| is critical, lower values are preferable.""") | |
| sigma_color = st.slider("sigmaColor", 1, 255, None, 1) | |
| sigma_space = st.slider("sigmaSpace", 1, 255, None, 1) | |
| c1, c2 = st.columns(2) | |
| apply_tab32d = c1.toggle("Apply", help="Click here to indicate whether the operation should be carried out or not, then validate with Confirm.", key=7) | |
| with c2: | |
| submit_tab32d = st.form_submit_button(":green[Confirm]") | |
| if submit_tab32d: | |
| st.session_state.bilateral_filtering = apply_tab32d | |
| st.session_state.d = d_value | |
| st.session_state.sigma_color = sigma_color | |
| st.session_state.sigma_space = sigma_space | |
| st.rerun() | |
| with tab4: # Morphologie | |
| list_select = st.segmented_control("Morphological operation:", | |
| ["Erosion", 'Dilation'], | |
| selection_mode="multi") | |
| if "Erosion" in list_select: | |
| with st.form("tab_4a"): | |
| st.markdown("π¬ :green[Erosion?]", | |
| help="The basic idea of erosion is just like soil erosion only, it erodes \ | |
| away the boundaries of foreground object (Always try to keep foreground in white). \ | |
| So what it does? The kernel slides through the image (as in 2D convolution). A pixel in the \ | |
| original image (either 1 or 0) will be considered 1 only if all the pixels under the kernel is 1, \ | |
| otherwise it is eroded (made to zero). \n \ | |
| So what happends is that, all the pixels near boundary will be discarded depending upon the \ | |
| size of kernel. So the thickness or size of the foreground object decreases or simply white region \ | |
| decreases in the image. \n\ | |
| It is useful for removing small white noises, detach two connected objects etc. \n \ | |
| :orange[**Best practice :** convert to grayscale before apply erosion.]β") | |
| kernel_size_ero = st.slider("Kernel size:", 3, 21, 3, 2, key=102) | |
| nb_iter = st.slider('Iterations number:', 1, 7, 1, 1, key=201) | |
| c1, c2 = st.columns(2) | |
| apply_tab4a = c1.toggle("Apply", help="Click here to indicate whether the operation should be carried out or not, then validate with Confirm.", key=8) | |
| with c2: | |
| submit_tab4a = st.form_submit_button(":green[Confirm]") | |
| if submit_tab4a: | |
| st.session_state.erosion = apply_tab4a | |
| st.session_state.erosion_kernel_size = kernel_size_ero | |
| st.session_state.nb_iter_erosion = nb_iter | |
| st.rerun() | |
| if "Dilation" in list_select: | |
| with st.form("tab_4b"): | |
| st.markdown("π¬ :green[Dilation?]", | |
| help="The opposite of an erosion is a dilation. Just like an \ | |
| erosion will eat away at the foreground pixels, a dilation will grow the foreground pixels. \ | |
| Dilations increase the size of foreground objects and are especially useful for joining broken \ | |
| parts of an image together. Dilations, just as an erosion, also utilize structuring elements \ | |
| β a center pixel p of the structuring element is set to white if ANY pixel in the structuring \ | |
| element is > 0. \n \ | |
| :orange[**Best practice :** convert to grayscale before apply dilation.]β") | |
| kernel_size_dil = st.slider("Kernel size:", 3, 21, 3, 2, key=103) | |
| nb_iter = st.slider('Iterations number:', 1, 7, 1, 1, key=202) | |
| kernel = np.ones((kernel_size_dil,kernel_size_dil),np.uint8) | |
| c1, c2 = st.columns(2) | |
| apply_tab4b = c1.toggle("Apply", help="Click here to indicate whether the operation should be carried out or not, then validate with Confirm.", key=9) | |
| with c2: | |
| submit_tab4b = st.form_submit_button(":green[Confirm]") | |
| if submit_tab4b: | |
| st.session_state.dilation = apply_tab4b | |
| st.session_state.dilation_kernel_size = kernel_size_dil | |
| st.session_state.nb_iter_dilation = nb_iter | |
| st.rerun() | |
| with tab5: # Thresholding | |
| selection = st.segmented_control("Type:", ["Binarization", "Adaptative thresholding"]) | |
| match selection: | |
| case "Binarization": | |
| with st.form("tab5_a"): | |
| st.markdown("π¬ :green[What is thresholding?]", | |
| help='''Thresholding is the binarization of an image. In general, we seek to | |
| convert a grayscale image to a binary image, where the pixels are either | |
| 0 or 255. | |
| A simple thresholding example would be selecting a threshold value T, | |
| and then setting all pixel intensities less than T to 0, and all pixel | |
| values greater than T to 255. In this way, we are able to create a binary | |
| representation of the image.''') | |
| st.markdown("*:orange[β Image must be in gray scale]*") | |
| cols_tab1 = st.columns([0.1, 0.9], gap="medium", vertical_alignment="center") | |
| with cols_tab1[1]: | |
| thresholding_type = cols_tab1[1].selectbox("Thresholding type:", | |
| list(dict_thresholding_type.keys())) | |
| with cols_tab1[0].popover(":material/info:", help="Help on thresholding type", | |
| use_container_width=False): | |
| st.link_button("π:blue[cf. OpenCV documentation :]", | |
| "https://docs.opencv.org/3.0-beta/modules/imgproc/doc/miscellaneous_transformations.html#threshold") | |
| thresh = st.slider("Thresh :", 0, 255, 255, 1) | |
| if thresholding_type in ["cv.THRESH_BINARY", "cv.THRESH_BINARY_INV"]: | |
| value = st.slider("Value :", 0, 255, 255, 1) | |
| else: | |
| value = 255 | |
| cols_tab3 = st.columns(2, gap="medium", vertical_alignment="center") | |
| otsu = cols_tab3[0].checkbox("Optimum Global Thresholding using Otsuβs Method?", | |
| help='''Otsuβs method tries to find a threshold value | |
| which minimizes the weighted within-class variance. | |
| Since Variance is the spread of the distribution | |
| about the mean. Thus, minimizing the within-class | |
| variance will tend to make the classes compact.''') | |
| cols_tab3[1].link_button("π:blue[Documentation]", | |
| "https://theailearner.com/2019/07/19/optimum-global-thresholding-using-otsus-method/") | |
| thresh_typ = dict_thresholding_type[thresholding_type] | |
| c1, c2 = st.columns(2) | |
| apply_tab5a = c1.toggle("Apply", help="Click here to indicate whether the operation should be carried out or not, then validate with Confirm.", key=10) | |
| with c2: | |
| submit_tab5a = st.form_submit_button(":green[Confirm]") | |
| if submit_tab5a: | |
| if otsu: | |
| thresh_typ = thresh_typ+cv2.THRESH_OTSU | |
| st.session_state.binarization = apply_tab5a | |
| st.session_state.bin_thresh = thresh | |
| st.session_state.bin_value = value | |
| st.session_state.bin_thresholding_type = thresholding_type | |
| st.session_state.bin_otsu = otsu | |
| st.session_state.thresh_typ = thresh_typ | |
| st.rerun() | |
| case "Adaptative thresholding": | |
| with st.form("tab5_b"): | |
| st.markdown("π¬ :green[What is adaptative thresholding?]", | |
| help='''This is a usefull technique when dealing with images having non-uniform illumination. | |
| In this, the threshold value is calculated separately for each pixel using | |
| some statistics obtained from its neighborhood. This way we will get different thresholds | |
| for different image regions and thus tackles the problem of varying illumination.''') | |
| st.markdown("*:orange[β Image must be in gray scale]*") | |
| thresholding_type = st.selectbox("Thresholding type:", | |
| list(dict_thresholding_type.keys())[:2]) | |
| max_value = st.slider("Max value :", 0, 255, 255, 1, | |
| help="""This is the value assigned to the pixels after thresholding. | |
| This depends on the thresholding type. If the type is cv2.THRESH_BINARY, | |
| all the pixels greater than the threshold are assigned this maxValue.""") | |
| adaptative_method = st.selectbox("Adaptative method:", | |
| list(dict_adaptative_method.keys()), | |
| help="""This tells us how the threshold is calculated from the pixel neighborhood. | |
| This currently supports two methods: | |
| - cv2.ADAPTIVE_THRESH_MEAN_C: In this, the threshold value is the mean of the neighborhood area.\n | |
| - cv2.ADAPTIVE_THRESH_GAUSSIAN_C: In this, the threshold value is the weighted sum of the | |
| neighborhood area. This uses Gaussian weights computed using getGaussiankernel() method.""") | |
| block_size = st.slider("Block size:", 3, 21, 3, 2, | |
| help='''**π What is blockSize?**\n | |
| In adaptive thresholding, the threshold for each pixel is determined based on a local neighborhood around it. | |
| The blockSize parameter specifies the size of this neighborhood. | |
| Specifically, it defines the dimensions of the square region (of size blockSize Γ blockSize) centered on the pixel being processed. | |
| The threshold is then calculated based on the pixel values within this region.β\n | |
| **β Acceptable Values for blockSize**\n | |
| Must be an odd integer greater than 1: This ensures that the neighborhood has a central pixel.β | |
| Common choices: 3, 5, 7, 9, 11, 13, 15, etc.β | |
| Even numbers are invalid: Using an even blockSize (e.g., 2, 4, 6) would result in an error because | |
| there would be no central pixel in the neighborhood.β\n | |
| **π― Impact of blockSize on Thresholding**\n | |
| Smaller blockSize (e.g., 3 or 5):β\n | |
| - Captures fine details and small variations in illumination.β | |
| - May be more sensitive to noise.β\n | |
| Larger blockSize (e.g., 15 or 21):β\n | |
| - Provides smoother thresholding, reducing the effect of noise.β | |
| - Might overlook small features or details. | |
| Choosing the appropriate blockSize depends on the specific characteristics of your image and the details you wish to preserve or suppress.''') | |
| const = st.slider("C:", -10, 20, 0, 1, | |
| help='''The parameter C serves as a constant subtracted from the computed mean or weighted mean of the | |
| neighborhood pixels. This subtraction fine-tunes the thresholding process, allowing for better control | |
| over the binarization outcome. | |
| **π― Typical Values for C** | |
| The optimal value for C varies depending on the image's characteristics, such as lighting conditions and noise levels. Commonly used values include:β | |
| - 2 to 10: These values are often effective for standard images with moderate lighting variations.β | |
| - Higher values (e.g., 15 or 20): Useful for images with significant noise or when a more aggressive thresholding is needed.β | |
| - Negative values: Occasionally used to make the thresholding more lenient, capturing lighter details that might otherwise be missed.β | |
| It's advisable to experiment with different C values to determine the most suitable one for your specific application. ''') | |
| c1, c2 = st.columns(2) | |
| apply_tab5b = c1.toggle("Apply", help="Click here to indicate whether the operation should be carried out or not, then validate with Confirm.", key=11) | |
| with c2: | |
| submit_tab5b = st.form_submit_button(":green[Confirm]") | |
| if submit_tab5b: | |
| st.session_state.adaptative_thresh = apply_tab5b | |
| st.session_state.at_max_value = max_value | |
| st.session_state.at_adaptative_method = adaptative_method | |
| st.session_state.at_thresholding_type = thresholding_type | |
| st.session_state.at_block_size = block_size | |
| st.session_state.at_const = const | |
| st.rerun() | |
| col1_a, col1_b = col1.columns(2) | |
| if col1_a.button("π :blue[List of operations]"): | |
| col1_a.write(list_op) | |
| if col1_b.button("Prepare download"): | |
| if len(img_wrk.shape) == 2: | |
| pil_img = Image.fromarray(img_wrk).convert("L") | |
| else: | |
| img_rgb = cv2.cvtColor(img_wrk, cv2.COLOR_BGR2RGB) | |
| pil_img = Image.fromarray(img_rgb) | |
| img_bytes = io.BytesIO() | |
| pil_img.save(img_bytes, format='PNG') | |
| img_bytes.seek(0) | |
| col1_b.download_button( | |
| label="Download processed image", | |
| data=img_bytes, | |
| file_name="processed_image.png", | |
| on_click="ignore", | |
| icon=":material/download:", | |
| mime="image/png" | |
| ) | |
| with col2.expander(":blue[Quick overview of OCR recognition (with PPOCR)]", expanded=True): | |
| with st.form("form1"): | |
| key_ppocr_lang = st.selectbox("Choose language: :", dict_lang_ppocr.keys(), 20) | |
| res_cnt = st.empty() | |
| submit_detect = st.form_submit_button("Launch overview") | |
| ##----------- Process OCR -------------------------------------------------------------- | |
| if submit_detect: | |
| with res_cnt, st.spinner("PPOCR initialization ..."): | |
| ocr = PaddleOCR(lang=dict_lang_ppocr[key_ppocr_lang]) #, show_log=False) | |
| with res_cnt, st.spinner("OCR process ..."): | |
| result = ocr.ocr(img_wrk) | |
| # draw result | |
| result = result[0] | |
| if len(img_wrk.shape) == 3: | |
| image = img_wrk.copy() | |
| else: | |
| image = cv2.cvtColor(img_wrk, cv2.COLOR_GRAY2RGB) | |
| boxes = [line[0] for line in result] | |
| txts = [line[1][0] for line in result] | |
| scores = [line[1][1] for line in result] | |
| im_show = draw_ocr(image, boxes, txts, scores, font_path='./fonts/french.ttf') | |
| im_show = Image.fromarray(im_show) | |
| res_cnt.image(im_show, use_container_width=True) |