import csv import os.path import time import cv2 import gdown import numpy as np import streamlit as st import torch from PIL import Image def load_classes(csv_reader): """ Load classes from csv. :param csv_reader: csv :return: """ result = {} for line, row in enumerate(csv_reader): line += 1 try: class_name, class_id = row except ValueError: raise (ValueError('line {}: format should be \'class_name,class_id\''.format(line))) class_id = int(class_id) if class_name in result: raise ValueError('line {}: duplicate class name: \'{}\''.format(line, class_name)) result[class_name] = class_id return result @st.cache def draw_caption(image, box, caption): """ Draw caption and bbox on image. :param image: image :param box: bounding box :param caption: caption :return: """ b = np.array(box).astype(int) cv2.putText(image, caption, (b[0], b[1] - 10), cv2.FONT_HERSHEY_PLAIN, 1, (0, 0, 0), 2) cv2.putText(image, caption, (b[0], b[1] - 10), cv2.FONT_HERSHEY_PLAIN, 1, (255, 255, 255), 1) @st.cache def load_labels(): """ Loads labels. :return: """ with open("data/labels.csv", 'r') as f: classes = load_classes(csv.reader(f, delimiter=',')) labels = {} for key, value in classes.items(): labels[value] = key return labels def download_models(ids): """ Download all models. :param ids: name and links of models :return: """ # Download model from drive if not stored locally with st.spinner('Downloading models, this may take a minute...'): for key in ids: if not os.path.isfile(f"model/{key}.pt"): url = f"https://drive.google.com/uc?id={ids[key]}" gdown.download(url=url, output=f"model/{key}.pt") @st.cache(suppress_st_warning=True) def load_model(model_path, prefix: str = 'model/'): """ Load model. :param model_path: path to inference model :param prefix: model prefix if needed :return: """ # Load model if torch.cuda.is_available(): model = torch.load(f"{prefix}{model_path}.pt").to('cuda') else: model = torch.load(f"{prefix}{model_path}.pt", map_location=torch.device('cpu')) model = model.module.cpu() model.training = False model.eval() return model def process_img(model, image, labels, caption: bool = True, thickness=2): """ Process img given a model. :param caption: whether to use captions or not :param image: image to process :param model: inference model :param labels: given labels :param thickness: thickness of bboxes :return: """ image_orig = image.copy() rows, cols, cns = image.shape smallest_side = min(rows, cols) # Rescale the image min_side = 608 max_side = 1024 scale = min_side / smallest_side # Check if the largest side is now greater than max_side largest_side = max(rows, cols) if largest_side * scale > max_side: scale = max_side / largest_side # Resize the image with the computed scale image = cv2.resize(image, (int(round(cols * scale)), int(round((rows * scale))))) rows, cols, cns = image.shape pad_w = 32 - rows % 32 pad_h = 32 - cols % 32 new_image = np.zeros((rows + pad_w, cols + pad_h, cns)).astype(np.float32) new_image[:rows, :cols, :] = image.astype(np.float32) image = new_image.astype(np.float32) image /= 255 image -= [0.485, 0.456, 0.406] image /= [0.229, 0.224, 0.225] image = np.expand_dims(image, 0) image = np.transpose(image, (0, 3, 1, 2)) with torch.no_grad(): image = torch.from_numpy(image) if torch.cuda.is_available(): image = image.cuda() st = time.time() scores, classification, transformed_anchors = model(image.float()) elapsed_time = time.time() - st idxs = np.where(scores.cpu() > 0.5) for j in range(idxs[0].shape[0]): bbox = transformed_anchors[idxs[0][j], :] x1 = int(bbox[0] / scale) y1 = int(bbox[1] / scale) x2 = int(bbox[2] / scale) y2 = int(bbox[3] / scale) label_name = labels[int(classification[idxs[0][j]])] colors = { 'with_mask': (0, 255, 0), 'without_mask': (255, 0, 0), 'mask_weared_incorrect': (190, 100, 20) } cap = '{}'.format(label_name) if caption else '' draw_caption(image_orig, (x1, y1, x2, y2), cap) cv2.rectangle(image_orig, (x1, y1), (x2, y2), color=colors[label_name], thickness=int(1 * (smallest_side / 100))) return image_orig # Page config st.set_page_config(layout="centered") st.title("Face Mask Detection") st.write('Face Mask Detection on images, videos and webcam feed with ResNet[18~152] models. ') st.markdown(f"__Labels:__ with_mask, without_mask, mask_weared_incorrect") # Models drive ids ids = { 'resnet50_20': st.secrets['resnet50'], 'resnet152_20': st.secrets['resnet152'], } # Download all models from drive download_models(ids) # Split page into columns left, right = st.columns([5, 3]) # Model selection labels = load_labels() model_path = right.selectbox('Choose a model', options=[k for k in ids], index=0) model = load_model(model_path=model_path) if model_path != '' else None # Display example selection index = left.number_input('', min_value=0, max_value=852, value=495, help='Choose an image. ') # Uploader uploaded = st.file_uploader("Try it out with your own image!", type=['.jpg', '.png', '.jfif']) if uploaded is not None: # Convert file to image image = Image.open(uploaded) image = np.array(image) else: # Get corresponding image and transform it image = cv2.imread(f'data/validation/image/maksssksksss{str(index)}.jpg') image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # Process img with st.spinner('Please wait while the image is being processed... This may take a while. '): image = process_img(model, image, labels, caption=False) left.image(cv2.resize(image, (450, 300))) # Write labels dict and device on right right.write({ 'green': 'with_mask', 'orange': 'mask_weared_incorrect', 'red': 'without_mask' }) device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') right.write(device) captions = [image for image in os.listdir('data/examples/')] images = [Image.open(f'data/examples/{image}') for image in os.listdir('data/examples/')] # Display examples st.image(images, width=350)