diff --git "a/app.py" "b/app.py"
--- "a/app.py"
+++ "b/app.py"
@@ -1,880 +1,880 @@
-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("""
-
- """, unsafe_allow_html=True)
-
-###################################################################################################
-## INITIALISATIONS
-###################################################################################################
-###
-@st.cache_data(show_spinner=True)
-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
-###################################################################################################
-###
-@st.cache_data(show_spinner=False)
-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
-
-@st.fragment
-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))
- 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))
- 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)
+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("""
+
+ """, unsafe_allow_html=True)
+
+###################################################################################################
+## INITIALISATIONS
+###################################################################################################
+###
+@st.cache_data(show_spinner=True)
+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
+###################################################################################################
+###
+@st.cache_data(show_spinner=False)
+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
+
+@st.fragment
+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 )
+ 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)
\ No newline at end of file