import streamlit as st import cv2 import numpy as np from PIL import Image import io import matplotlib.pyplot as plt def apply_threshold(image, threshold_type, thresh_value=127, max_value=255): if threshold_type == "Binary": _, thresh = cv2.threshold(image, thresh_value, max_value, cv2.THRESH_BINARY) elif threshold_type == "Binary Inverse": _, thresh = cv2.threshold(image, thresh_value, max_value, cv2.THRESH_BINARY_INV) elif threshold_type == "Truncate": _, thresh = cv2.threshold(image, thresh_value, max_value, cv2.THRESH_TRUNC) elif threshold_type == "To Zero": _, thresh = cv2.threshold(image, thresh_value, max_value, cv2.THRESH_TOZERO) elif threshold_type == "To Zero Inverse": _, thresh = cv2.threshold(image, thresh_value, max_value, cv2.THRESH_TOZERO_INV) elif threshold_type == "Adaptive Mean": thresh = cv2.adaptiveThreshold(image, max_value, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2) elif threshold_type == "Adaptive Gaussian": thresh = cv2.adaptiveThreshold(image, max_value, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) elif threshold_type == "Otsu": _, thresh = cv2.threshold(image, 0, max_value, cv2.THRESH_BINARY + cv2.THRESH_OTSU) return thresh def apply_histogram_equalization(image, method): if method == "Simple": if len(image.shape) == 3: img_yuv = cv2.cvtColor(image, cv2.COLOR_BGR2YUV) img_yuv[:,:,0] = cv2.equalizeHist(img_yuv[:,:,0]) return cv2.cvtColor(img_yuv, cv2.COLOR_YUV2BGR) else: return cv2.equalizeHist(image) elif method == "CLAHE": clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) if len(image.shape) == 3: img_yuv = cv2.cvtColor(image, cv2.COLOR_BGR2YUV) img_yuv[:,:,0] = clahe.apply(img_yuv[:,:,0]) return cv2.cvtColor(img_yuv, cv2.COLOR_YUV2BGR) else: return clahe.apply(image) return image def apply_color_quantization(image, k): data = np.float32(image).reshape((-1,3)) criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0) _, label, center = cv2.kmeans(data, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS) center = np.uint8(center) result = center[label.flatten()] return result.reshape(image.shape) def main(): st.set_page_config(layout="wide") st.title("Advanced Image Processing Laboratory") st.markdown(""" """, unsafe_allow_html=True) # Sidebar st.sidebar.title("Controls Panel") uploaded_file = st.sidebar.file_uploader("Upload Image", type=["jpg", "jpeg", "png"]) if uploaded_file is not None: file_bytes = np.asarray(bytearray(uploaded_file.read()), dtype=np.uint8) original_img = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR) # Create columns for better layout col1, col2 = st.columns(2) with col1: st.subheader("Original Image") st.image(cv2.cvtColor(original_img, cv2.COLOR_BGR2RGB)) # Main processing options processing_option = st.sidebar.selectbox( "Select Processing Category", ["Basic Operations", "Filtering", "Color Spaces", "Thresholding", "Morphological Operations", "Edge Detection", "Feature Detection", "Histogram Operations", "Advanced Effects"] ) # Process and display result with col2: st.subheader("Processed Image") if processing_option == "Basic Operations": st.sidebar.markdown(""" ### Basic Operations Transform your image with fundamental operations: - **Resize**: Scale the image up or down - **Rotate**: Rotate the image by any angle - **Flip**: Mirror the image horizontally or vertically - **Brightness/Contrast**: Adjust image lighting - **Color Quantization**: Reduce the number of colors """) operation = st.sidebar.selectbox( "Select Operation", ["Resize", "Rotate", "Flip", "Brightness/Contrast", "Color Quantization"] ) if operation == "Resize": st.sidebar.markdown("Adjust the scale factor to resize the image. Values > 1 enlarge, values < 1 shrink.") scale = st.sidebar.slider("Scale Factor", 0.1, 2.0, 1.0) processed_img = cv2.resize(original_img, None, fx=scale, fy=scale) elif operation == "Rotate": st.sidebar.markdown("Rotate the image by specifying an angle in degrees. Positive values rotate counter-clockwise.") angle = st.sidebar.slider("Angle", -180, 180, 0) center = (original_img.shape[1] // 2, original_img.shape[0] // 2) matrix = cv2.getRotationMatrix2D(center, angle, 1.0) processed_img = cv2.warpAffine(original_img, matrix, (original_img.shape[1], original_img.shape[0])) elif operation == "Flip": st.sidebar.markdown("Mirror the image in different directions.") flip_option = st.sidebar.selectbox("Flip Direction", ["Horizontal", "Vertical", "Both"]) if flip_option == "Horizontal": processed_img = cv2.flip(original_img, 1) elif flip_option == "Vertical": processed_img = cv2.flip(original_img, 0) else: processed_img = cv2.flip(original_img, -1) elif operation == "Brightness/Contrast": st.sidebar.markdown(""" Adjust image brightness and contrast: - **Brightness**: Negative values darken, positive values brighten - **Contrast**: Negative values decrease contrast, positive values increase it """) brightness = st.sidebar.slider("Brightness", -100, 100, 0) contrast = st.sidebar.slider("Contrast", -100, 100, 0) processed_img = original_img.copy() if brightness != 0: if brightness > 0: shadow = brightness highlight = 255 else: shadow = 0 highlight = 255 + brightness alpha_b = (highlight - shadow)/255 gamma_b = shadow processed_img = cv2.addWeighted(processed_img, alpha_b, processed_img, 0, gamma_b) if contrast != 0: f = 131*(contrast + 127)/(127*(131-contrast)) alpha_c = f gamma_c = 127*(1-f) processed_img = cv2.addWeighted(processed_img, alpha_c, processed_img, 0, gamma_c) elif operation == "Color Quantization": st.sidebar.markdown("Reduce the number of colors in the image. Lower values create more poster-like effects.") k = st.sidebar.slider("Number of Colors", 2, 16, 8) processed_img = apply_color_quantization(original_img, k) elif processing_option == "Filtering": st.sidebar.markdown(""" ### Image Filtering Apply different filters to smooth or enhance the image: - **Blur**: Simple averaging filter - **Gaussian**: Weighted gaussian smoothing - **Median**: Good for removing salt-and-pepper noise - **Bilateral**: Edge-preserving smoothing - **Custom Kernel**: Apply specific filter effects """) filter_type = st.sidebar.selectbox( "Select Filter", ["Blur", "Gaussian", "Median", "Bilateral", "Custom Kernel"] ) if filter_type == "Custom Kernel": st.sidebar.markdown("Apply predefined kernel effects. Larger kernel sizes create stronger effects.") kernel_size = st.sidebar.slider("Kernel Size", 3, 7, 3, step=2) kernel_type = st.sidebar.selectbox("Kernel Type", ["Sharpen", "Edge Detection", "Emboss"]) if kernel_type == "Sharpen": kernel = np.array([[-1,-1,-1], [-1,9,-1], [-1,-1,-1]]) elif kernel_type == "Edge Detection": kernel = np.array([[-1,-1,-1], [-1,8,-1], [-1,-1,-1]]) elif kernel_type == "Emboss": kernel = np.array([[-2,-1,0], [-1,1,1], [0,1,2]]) processed_img = cv2.filter2D(original_img, -1, kernel) else: st.sidebar.markdown("Adjust kernel size to control the strength of the filter effect.") kernel_size = st.sidebar.slider("Kernel Size", 3, 15, 3, step=2) if filter_type == "Bilateral": st.sidebar.markdown(""" Bilateral Filter Parameters: - **d**: Diameter of pixel neighborhood - **Sigma Color**: Filter sigma in color space - **Sigma Space**: Filter sigma in coordinate space """) d = st.sidebar.slider("d", 1, 15, 9) sigma_color = st.sidebar.slider("Sigma Color", 1, 255, 75) sigma_space = st.sidebar.slider("Sigma Space", 1, 255, 75) processed_img = cv2.bilateralFilter(original_img, d, sigma_color, sigma_space) else: if filter_type == "Blur": processed_img = cv2.blur(original_img, (kernel_size, kernel_size)) elif filter_type == "Gaussian": processed_img = cv2.GaussianBlur(original_img, (kernel_size, kernel_size), 0) elif filter_type == "Median": processed_img = cv2.medianBlur(original_img, kernel_size) elif processing_option == "Color Spaces": st.sidebar.markdown(""" ### Color Spaces Convert the image between different color representations: - **RGB**: Standard Red-Green-Blue color space - **HSV**: Hue-Saturation-Value, useful for color segmentation - **LAB**: Perceptually uniform color space - **YCrCb**: Used in video encoding - **Individual Channels**: View color components separately """) color_space = st.sidebar.selectbox( "Select Color Space", ["RGB", "HSV", "LAB", "YCrCb", "Individual Channels"] ) if color_space == "RGB": processed_img = cv2.cvtColor(original_img, cv2.COLOR_BGR2RGB) elif color_space == "HSV": processed_img = cv2.cvtColor(original_img, cv2.COLOR_BGR2HSV) elif color_space == "LAB": processed_img = cv2.cvtColor(original_img, cv2.COLOR_BGR2LAB) elif color_space == "YCrCb": processed_img = cv2.cvtColor(original_img, cv2.COLOR_BGR2YCrCb) elif color_space == "Individual Channels": channel = st.sidebar.selectbox("Select Channel", ["Blue", "Green", "Red"]) if channel == "Blue": processed_img = original_img[:,:,0] elif channel == "Green": processed_img = original_img[:,:,1] else: processed_img = original_img[:,:,2] elif processing_option == "Thresholding": # Convert to grayscale for thresholding gray_img = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY) threshold_type = st.sidebar.selectbox( "Select Threshold Type", ["Binary", "Binary Inverse", "Truncate", "To Zero", "To Zero Inverse", "Adaptive Mean", "Adaptive Gaussian", "Otsu"] ) if threshold_type in ["Binary", "Binary Inverse", "Truncate", "To Zero", "To Zero Inverse"]: thresh_value = st.sidebar.slider("Threshold Value", 0, 255, 127) max_value = st.sidebar.slider("Maximum Value", 0, 255, 255) processed_img = apply_threshold(gray_img, threshold_type, thresh_value, max_value) else: processed_img = apply_threshold(gray_img, threshold_type) elif processing_option == "Morphological Operations": operation = st.sidebar.selectbox( "Select Operation", ["Erosion", "Dilation", "Opening", "Closing", "Gradient", "Top Hat", "Black Hat"] ) kernel_size = st.sidebar.slider("Kernel Size", 3, 15, 5, step=2) kernel = np.ones((kernel_size, kernel_size), np.uint8) if operation == "Erosion": processed_img = cv2.erode(original_img, kernel, iterations=1) elif operation == "Dilation": processed_img = cv2.dilate(original_img, kernel, iterations=1) elif operation == "Opening": processed_img = cv2.morphologyEx(original_img, cv2.MORPH_OPEN, kernel) elif operation == "Closing": processed_img = cv2.morphologyEx(original_img, cv2.MORPH_CLOSE, kernel) elif operation == "Gradient": processed_img = cv2.morphologyEx(original_img, cv2.MORPH_GRADIENT, kernel) elif operation == "Top Hat": processed_img = cv2.morphologyEx(original_img, cv2.MORPH_TOPHAT, kernel) elif operation == "Black Hat": processed_img = cv2.morphologyEx(original_img, cv2.MORPH_BLACKHAT, kernel) elif processing_option == "Edge Detection": st.sidebar.markdown(""" ### Edge Detection Different methods to detect edges in the image: - **Canny**: Advanced edge detector with thresholds - **Sobel**: Directional gradient detection - **Laplacian**: Detect edges using 2nd derivatives - **Scharr**: More accurate gradient calculation """) detector = st.sidebar.selectbox( "Select Detector", ["Canny", "Sobel", "Laplacian", "Scharr"] ) if detector == "Canny": threshold1 = st.sidebar.slider("Threshold 1", 0, 255, 100) threshold2 = st.sidebar.slider("Threshold 2", 0, 255, 200) processed_img = cv2.Canny(original_img, threshold1, threshold2) elif detector == "Sobel": dx = st.sidebar.slider("dx", 0, 2, 1) dy = st.sidebar.slider("dy", 0, 2, 1) ksize = st.sidebar.slider("Kernel Size", 1, 7, 3, step=2) gray = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY) processed_img = cv2.Sobel(gray, cv2.CV_64F, dx, dy, ksize=ksize) processed_img = np.uint8(np.absolute(processed_img)) elif detector == "Laplacian": ksize = st.sidebar.slider("Kernel Size", 1, 7, 3, step=2) gray = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY) processed_img = cv2.Laplacian(gray, cv2.CV_64F, ksize=ksize) processed_img = np.uint8(np.absolute(processed_img)) elif detector == "Scharr": direction = st.sidebar.selectbox("Direction", ["X", "Y"]) gray = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY) if direction == "X": processed_img = cv2.Scharr(gray, cv2.CV_64F, 1, 0) else: processed_img = cv2.Scharr(gray, cv2.CV_64F, 0, 1) processed_img = np.uint8(np.absolute(processed_img)) elif processing_option == "Feature Detection": st.sidebar.markdown(""" ### Feature Detection Detect interesting points or features in the image: - **Harris Corner**: Detects corner points using intensity changes - **Shi-Tomasi**: More robust corner detection - **FAST**: High-speed corner detection Parameters for Harris Corner: - **Block Size**: Size of neighborhood considered - **Kernel Size**: Aperture parameter for Sobel operator - **k**: Harris detector free parameter """) detector = st.sidebar.selectbox( "Select Detector", ["Harris Corner", "Shi-Tomasi", "FAST"] ) if detector == "Harris Corner": gray = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY) block_size = st.sidebar.slider("Block Size", 2, 10, 2) ksize = st.sidebar.slider("Kernel Size", 3, 31, 3, step=2) k = st.sidebar.slider("k", 0.01, 0.1, 0.04, step=0.01) processed_img = original_img.copy() gray = np.float32(gray) dst = cv2.cornerHarris(gray, block_size, ksize, k) dst = cv2.dilate(dst, None) processed_img[dst > 0.01 * dst.max()] = [0, 0, 255] elif detector == "Shi-Tomasi": gray = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY) corners = cv2.goodFeaturesToTrack(gray, 25, 0.01, 10) corners = np.int0(corners) processed_img = original_img.copy() for i in corners: x, y = i.ravel() cv2.circle(processed_img, (x, y), 3, [0, 0, 255], -1) elif detector == "FAST": gray = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY) fast = cv2.FastFeatureDetector_create() kp = fast.detect(gray, None) processed_img = original_img.copy() cv2.drawKeypoints(original_img, kp, processed_img, color=(0, 0, 255)) elif processing_option == "Histogram Operations": st.sidebar.markdown(""" ### Histogram Operations Analyze and modify image intensity distribution: - **Show Histogram**: Display color/intensity distribution - **Equalization**: Enhance contrast using histogram equalization - **CLAHE**: Contrast Limited Adaptive Histogram Equalization """) operation = st.sidebar.selectbox( "Select Operation", ["Show Histogram", "Equalization", "CLAHE"] ) if operation == "Show Histogram": fig, ax = plt.subplots() if len(original_img.shape) == 3: colors = ('b', 'g', 'r') for i, color in enumerate(colors): hist = cv2.calcHist([original_img], [i], None, [256], [0, 256]) ax.plot(hist, color=color) else: hist = cv2.calcHist([original_img], [0], None, [256], [0, 256]) ax.plot(hist) st.pyplot(fig) processed_img = original_img elif operation == "Equalization": processed_img = apply_histogram_equalization(original_img, "Simple") elif operation == "CLAHE": processed_img = apply_histogram_equalization(original_img, "CLAHE") elif processing_option == "Advanced Effects": st.sidebar.markdown(""" ### Advanced Effects Apply complex image transformations: - **Pencil Sketch**: Convert image to pencil drawing style - **Cartoon**: Create cartoon-like effect - **HDR Effect**: Enhance local details """) effect = st.sidebar.selectbox( "Select Effect", ["Pencil Sketch", "Cartoon", "HDR Effect"] ) if effect == "Pencil Sketch": gray = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY) inv = 255 - gray blur = cv2.GaussianBlur(inv, (21, 21), 0) processed_img = cv2.divide(gray, 255-blur, scale=256.0) elif effect == "Cartoon": gray = cv2.cvtColor(original_img, cv2.COLOR_BGR2GRAY) gray = cv2.medianBlur(gray, 5) edges = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 9) color = cv2.bilateralFilter(original_img, 9, 250, 250) processed_img = cv2.bitwise_and(color, color, mask=edges) elif effect == "HDR Effect": processed_img = cv2.detailEnhance(original_img, sigma_s=12, sigma_r=0.15) # Display processed image if len(processed_img.shape) == 3: st.image(cv2.cvtColor(processed_img, cv2.COLOR_BGR2RGB)) else: st.image(processed_img, clamp=True) # Add download button for processed image if st.button("Download Processed Image"): if len(processed_img.shape) == 3: processed_img_rgb = cv2.cvtColor(processed_img, cv2.COLOR_BGR2RGB) else: processed_img_rgb = processed_img pil_img = Image.fromarray(processed_img_rgb) img_bytes = io.BytesIO() pil_img.save(img_bytes, format='PNG') st.download_button( label="Download Image", data=img_bytes.getvalue(), file_name="processed_image.png", mime="image/png" ) if __name__ == "__main__": main()