Team Ai
Apppublic

DavidNgoue/ComputerVision

sourceHugging Facemitupdated 2y agoView on Hugging Face
0likes
app.py637 linesDownload Raw Back to root
1import streamlit as st2import cv23import numpy as np4from PIL import Image5from io import BytesIO6import barcode7from barcode.writer import ImageWriter8import qrcode9import tempfile10 11def image_to_bytes(img):12    pil_image = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))13    buffer = BytesIO()14    pil_image.save(buffer, format="PNG")15    return buffer.getvalue()16 17def generate_barcode(link):18    code128 = barcode.get_barcode_class('code128')19    barcode_image = code128(link, writer=ImageWriter())20    buffer = BytesIO()21    barcode_image.write(buffer)22    return Image.open(buffer)23 24def generate_qrcode(link):25    qr = qrcode.QRCode(26        version=2,  27        error_correction=qrcode.constants.ERROR_CORRECT_L,  28        box_size=4,  29        border=2,    30    )31    qr.add_data(link)32    qr.make(fit=True)33    qr_image = qr.make_image(fill_color="black", back_color="white")34 35    small_qr_image = qr_image.resize((512, 512), Image.Resampling.LANCZOS)36 37    buffer = BytesIO()38    small_qr_image.save(buffer, format="PNG")39    return Image.open(buffer)40 41def add_custom_css():42    css = """43    <style>44        body {45            background: linear-gradient(135deg, #a8dadc, #f1faee);46            color: #1d3557;47            font-family: 'Arial', sans-serif;48            animation: backgroundAnimation 10s infinite alternate;49        }50 51        @keyframes backgroundAnimation {52            0% {53                background: linear-gradient(135deg, #a8dadc, #f1faee);54            }55            100% {56                background: linear-gradient(135deg, #f1faee, #457b9d);57            }58        }59 60        .stButton>button {61            background-color: #457b9d;62            color: white;63            border-radius: 5px;64            transition: transform 0.3s, background-color 0.3s;65            box-shadow: 2px 2px 6px rgba(0,0,0,0.2);66        }67 68        .stButton>button:hover {69            transform: scale(1.1);70            background-color: #1d3557;71        }72 73        .stSidebar {74            background: linear-gradient(to bottom, #87CEEB, #FFFFFF);75            color: white;76            font-size: 16px;77        }78 79        .stImage {80            animation: fadeIn 2s ease-in-out;81        }82 83        @keyframes fadeIn {84            0% {85                opacity: 0;86            }87            100% {88                opacity: 1;89            }90        }91 92        header, footer {93            background: #457b9d;94            color: white;95        }96 97        .stMarkdown {98            animation: slideIn 1s ease-out;99        }100 101        @keyframes slideIn {102            0% {103                transform: translateY(-20px);104                opacity: 0;105            }106            100% {107                transform: translateY(0);108                opacity: 1;109            }110        }111    </style>112    """113    st.markdown(css, unsafe_allow_html=True)114 115def add_custom_js():116    js = """117    <script>118        document.addEventListener('DOMContentLoaded', function() {119            const elements = document.querySelectorAll('.stButton>button');120            elements.forEach(button => {121                button.addEventListener('click', () => {122                    button.style.backgroundColor = '#a8dadc';123                    button.style.transform = 'rotate(360deg)';124                    setTimeout(() => button.style.transform = 'rotate(0deg)', 300);125                });126            });127        });128    </script>129    """130    st.markdown(js, unsafe_allow_html=True)131 132def main():133    st.set_page_config(page_title="ADS VISOR - Un autre regard", layout="wide")134 135    add_custom_css()136    add_custom_js()137 138 139    logo_path = "logo.jpg"  140    logo = Image.open(logo_path)141    st.image(logo, width=150, caption="ADS VISOR")142 143    st.title("ADS VISOR - Un autre regard")144 145    st.sidebar.header("Chargement de l'image")146    if "default_image" not in st.session_state:147        st.session_state["default_image"] = None148 149    uploaded_file = st.sidebar.file_uploader("Charge une image", type=["png", "jpg", "jpeg"])150    if uploaded_file is not None:151        image = Image.open(uploaded_file)152        st.session_state["default_image"] = np.array(image)153        st.sidebar.image(image, caption="Image par défaut", use_container_width=True)154 155    if st.session_state["default_image"] is None:156        st.sidebar.warning("Veuillez charger une image pour commencer.")157        return158 159    st.sidebar.header("Fonctionnalités")160    menu_option = st.sidebar.selectbox(161        "Choisissez une fonctionnalité",162        ["Accueil", "Niveaux de couleurs", "Cropping", "Rotation", "Floutage", "Contours", "Génération de Code-barres et QR Code", "Détection Faciale", "À propos de nous"],163        format_func=lambda x: {164            "Accueil": "🏠 Accueil",165            "Niveaux de couleurs": "🖼️ Niveaux de couleurs",166            "Cropping": "✂️ Cropping",167            "Rotation": "🔄 Rotation",168            "Floutage": "🌫️ Floutage",169            "Contours": "🔍 Contours",170            "Génération de Code-barres et QR Code": "📇 Codes numériques",171            "Détection Faciale": "🙂 Détection Faciale",172            "À propos de nous": "👨‍💻 À propos de nous"173        }.get(x, x)174    )175 176    image_np = st.session_state["default_image"]177 178    if menu_option == "Accueil":179        st.header("Bienvenue sur ADS VISOR")180        st.markdown(181            """182            <div class="stMarkdown">183                <h2>ADS VISOR est une application innovante pour analyser, transformer et explorer vos images. 🖼️✨</h2>184                <p>Que vous soyez un professionnel ou un passionné, découvrez un large éventail de fonctionnalités interactives !</p>185                <ul>186                    <li><b>Transformations d'image :</b> Couleurs, niveaux de gris, etc.</li>187                    <li><b>Découpage & Rotation :</b> Ajustez vos images à la perfection.</li>188                    <li><b>Détection Faciale :</b> Identifiez les visages automatiquement.</li>189                    <li><b>Codes-barres & QR Codes :</b> Génération rapide pour vos projets.</li>190                </ul>191            </div>192            """,193            unsafe_allow_html=True194        )195 196    # Niveaux de couleurs197    elif menu_option == "Niveaux de couleurs":198        st.subheader("Niveaux de couleurs")199 200        # Instructions générales sur les niveaux de couleurs201        st.markdown("""202        ### Légende : Fonctionnement des niveaux de couleurs203        - **Les niveaux de couleurs** permettent de manipuler les différentes composantes d'une image : **Rouge**, **Vert**, **Bleu**.204        - L'édition des canaux colorimétriques permet de visualiser une image en mettant en valeur une couleur spécifique.205        206        ### Options disponibles :207        - **Niveaux de Gris** : Convertit l'image en une seule nuance de gris, supprimant les informations de couleur.208        - **Rouge** : Filtre l'image pour ne conserver que la composante rouge.209        - **Vert** : Filtre l'image pour ne conserver que la composante verte.210        - **Jaune** : Combine les composantes rouge et verte pour donner une teinte jaune.211 212        ### Exemple :213        - Si vous choisissez l'option **Rouge**, vous obtiendrez une image où seules les nuances rouges sont visibles, les autres couleurs étant supprimées.214        """)215 216        # Création des onglets pour afficher les images dans différentes couleurs217        tab1, tab2, tab3, tab4 = st.tabs([218            "Gris 🖤", "Rouge ❤️", "Vert 💚", "Jaune 💛"219        ])220 221        # Onglet 1 : Gris222        with tab1:223            gray_image = cv2.cvtColor(image_np, cv2.COLOR_BGR2GRAY)224            st.image(gray_image, caption="Image en Niveaux de Gris", use_container_width=True)225            st.download_button(226                label="Télécharger l'image en gris",227                data=image_to_bytes(gray_image),228                file_name="image_gris.png",229                mime="image/png"230            )231 232        # Onglet 2 : Rouge233        with tab2:234            red = image_np.copy()235            red[:, :, 1:] = 0  # On met les canaux vert et bleu à zéro236            st.image(red, caption="Image Rouge", use_container_width=True)237            st.download_button(238                label="Télécharger l'image rouge",239                data=image_to_bytes(red),240                file_name="image_rouge.png",241                mime="image/png"242            )243 244        # Onglet 3 : Vert245        with tab3:246            green = image_np.copy()247            green[:, :, [0, 2]] = 0  # On met les canaux rouge et bleu à zéro248            st.image(green, caption="Image Verte", use_container_width=True)249            st.download_button(250                label="Télécharger l'image verte",251                data=image_to_bytes(green),252                file_name="image_verte.png",253                mime="image/png"254            )255 256        # Onglet 4 : Jaune257        with tab4:258            yellow = image_np.copy()259            yellow[:, :, 0] = 0  # On supprime la composante bleue260            st.image(yellow, caption="Image Jaune", use_container_width=True)261            st.download_button(262                label="Télécharger l'image jaune",263                data=image_to_bytes(yellow),264                file_name="image_jaune.png",265                mime="image/png"266            )267 268    elif menu_option == "Cropping":269        st.subheader("Cropping - Recadrage d'Image")270 271        # Explications pour les paramètres272        st.markdown(273            """274            ### Légende : Fonctionnement du recadrage275            Le recadrage d'une image consiste à extraire une portion rectangulaire de celle-ci. Pour cela, nous définissons les coordonnées des coins du rectangle :276 277            - **`x1` (Coordonnée x du coin supérieur gauche)** : Position horizontale du début du rectangle (0 ≤ `x1` < largeur-1).278            - **`y1` (Coordonnée y du coin supérieur gauche)** : Position verticale du début du rectangle (0 ≤ `y1` < hauteur-1).279            - **`x2` (Coordonnée x du coin inférieur droit)** : Position horizontale de la fin du rectangle (1 ≤ `x2` ≤ largeur).280            - **`y2` (Coordonnée y du coin inférieur droit)** : Position verticale de la fin du rectangle (1 ≤ `y2` ≤ hauteur).281 282            ### Exemple illustratif :283            Imaginons que vous avez une image de 300x300 pixels. Si vous entrez `x1=50`, `y1=50`, `x2=200`, et `y2=200`, l'image sera recadrée en un rectangle de taille 150x150 pixels, commençant à partir du pixel (50, 50) jusqu'à (200, 200).284 285            ### Étapes :286            1. Saisissez les coordonnées du rectangle que vous souhaitez extraire.287            2. Vous verrez une prévisualisation de la zone sélectionnée avant de la confirmer.288            3. Téléchargez l'image recadrée.289            """290        )291 292        # Entrée des coordonnées293        try:294            x1 = st.number_input("x1 (Coordonnée x du coin supérieur gauche)", 0, image_np.shape[1] - 1, step=1)295            y1 = st.number_input("y1 (Coordonnée y du coin supérieur gauche)", 0, image_np.shape[0] - 1, step=1)296            x2 = st.number_input("x2 (Coordonnée x du coin inférieur droit)", 1, image_np.shape[1], step=1)297            y2 = st.number_input("y2 (Coordonnée y du coin inférieur droit)", 1, image_np.shape[0], step=1)298 299            # Validation des coordonnées300            if x1 >= x2:301                st.error("`x2` doit être strictement supérieur à `x1`. Veuillez corriger vos entrées.")302            elif y1 >= y2:303                st.error("`y2` doit être strictement supérieur à `y1`. Veuillez corriger vos entrées.")304            else:305                # Calcul des dimensions du recadrage306                cropped_width = x2 - x1307                cropped_height = y2 - y1308 309                # Bouton pour générer l'image recadrée310                if st.button("Générer le Recadrage"):311                    # Recadrage de l'image312                    cropped = image_np[int(y1):int(y2), int(x1):int(x2)]313 314                    # Affichage de l'image recadrée315                    st.image(cropped, caption="Image Croppée", use_container_width=True)316 317                    # Affichage des informations sur la taille du rectangle318                    st.write(f"Le rectangle recadré mesure {cropped_width} pixels de large et {cropped_height} pixels de haut.")319 320                    # Bouton de téléchargement pour l'image recadrée321                    st.download_button(322                        label="⬇️ Télécharger l'image croppée",323                        data=image_to_bytes(cropped),324                        file_name="image_cropped.png",325                        mime="image/png"326                    )327 328        except Exception as e:329            st.error(f"Une erreur est survenue : {e}")330 331    # Rotation332    elif menu_option == "Rotation":333        st.subheader("Rotation")334 335        # Instructions sur la rotation336        st.markdown("""337        ### Légende : Fonctionnement de la rotation338        - La **rotation** d'une image consiste à tourner l'image autour de son centre selon un angle défini.339        - Vous pouvez choisir un angle prédéfini pour faire pivoter l'image à gauche ou à droite.340        341        ### Options disponibles :342        - Choisissez un angle parmi les options proposées : **45°, 90° ou 180°**.343        - L'image sera ensuite tournée dans le sens des aiguilles d'une montre.344 345        ### Exemple :346        - Si vous choisissez un angle de **90°**, l'image sera pivotée de 90 degrés dans le sens des aiguilles d'une montre par rapport à son centre.347        """)348        349        # Sélection de l'angle de rotation350        angle = st.selectbox("Angle de rotation", [45, 90, 180])351 352        # Calcul des dimensions et application de la rotation353        rows, cols, _ = image_np.shape354        rotation_matrix = cv2.getRotationMatrix2D((cols / 2, rows / 2), angle, 1)355        rotated = cv2.warpAffine(image_np, rotation_matrix, (cols, rows))356 357        # Affichage de l'image après rotation358        st.image(rotated, caption=f"Image Rotée de {angle} degrés", use_container_width=True)359 360        # Bouton de téléchargement pour l'image rotée361        st.download_button(362            label="Télécharger l'image rotée",363            data=image_to_bytes(rotated),364            file_name=f"image_rotated_{angle}.png",365            mime="image/png"366        )367 368    # Floutage369    elif menu_option == "Floutage":370        st.subheader("Floutage")371 372        # Instructions sur le floutage373        st.markdown("""374        ### Légende : Fonctionnement du floutage375        - Le **floutage** est utilisé pour adoucir une image en réduisant les détails fins, ce qui est utile pour l'anonymisation ou pour appliquer des effets esthétiques.376        - Vous pouvez ajuster l'intensité du flou en fonction du niveau choisi.377 378        ### Options disponibles :379        - Le **Niveau de flou (k)** détermine l'intensité du flou appliqué. Plus la valeur est élevée, plus l'image sera floutée.380        - Les valeurs sont comprises entre 1 (très léger flou) et 51 (flou maximal), et doivent être des nombres impairs.381 382        ### Exemple :383        - Si vous choisissez **k = 15**, l'image sera floutée de manière modérée, et vous pourrez voir l'effet de réduction de détails.384        """)385        386        # Sélection du niveau de flou387        blur_level = st.slider("Niveau de flou (k)", min_value=1, max_value=51, step=2, value=15)388 389        # Application du flou sur l'image390        blurred = cv2.GaussianBlur(image_np, (blur_level, blur_level), 0)391 392        # Affichage de l'image floutée393        st.image(blurred, caption=f"Image Floutée (k={blur_level})", use_container_width=True)394 395        # Bouton de téléchargement pour l'image floutée396        st.download_button(397            label="Télécharger l'image floutée",398            data=image_to_bytes(blurred),399            file_name=f"image_blurred_k{blur_level}.png",400            mime="image/png"401        )402 403    # Détection des contours404    elif menu_option == "Contours":405        st.subheader("Contours")406 407        # Instructions sur la détection des contours408        st.markdown("""409        ### Légende : Fonctionnement de la détection de contours410        - La **détection de contours** consiste à identifier les bords distincts de l'image, ce qui permet de mieux comprendre les structures présentes.411        - Elle est souvent utilisée dans des traitements d'image avancés comme la reconnaissance d'objets ou l'analyse d'images.412 413        ### Options disponibles :414        - L'image sera convertie en niveaux de gris, et ensuite un algorithme de détection de contours (Canny) sera appliqué pour extraire les bords de l'image.415        - Cela permet de visualiser les contours dans des images complexes.416 417        ### Exemple :418        - Après l'application de l'algorithme Canny, l'image ne contient que les contours des objets, ce qui peut être utilisé pour détecter des formes, des objets ou d'autres détails visuels importants.419        """)420        421        # Conversion de l'image en niveaux de gris pour la détection des contours422        gray = cv2.cvtColor(image_np, cv2.COLOR_BGR2GRAY)423        edges = cv2.Canny(gray, 100, 200)424 425        # Affichage de l'image avec les contours détectés426        st.image(edges, caption="Contours de l'Image", use_container_width=True)427 428        # Bouton de téléchargement pour l'image avec contours429        st.download_button(430            label="Télécharger l'image avec contours",431            data=image_to_bytes(edges),432            file_name=f"image_edges.png",433            mime="image/png"434        )435        436        437    elif menu_option == "Génération de Code-barres et QR Code":438        st.subheader("Génération de Code-barres et QR Code")439 440        # Légende pour guider l'utilisateur441        st.markdown(442            """443            🔄 **Instructions :**444            1. Saisis un lien ou un texte dans le champ ci-dessous.  445            2. Accède à l'onglet souhaité pour générer un **Code-barres** 📊 ou un **QR Code** 📱.  446            3. Clique sur le bouton **"Générer"** pour voir le résultat et utilise le bouton **⬇️ Télécharger** pour sauvegarder l'image.447            """448        )449 450        # Gestion de l'entrée utilisateur avec sauvegarde dans session_state451        if "link" not in st.session_state:452            st.session_state["link"] = ""453 454        # Entrée utilisateur455        link = st.text_input("🔗 Entre un lien ou un texte pour générer les codes", value=st.session_state["link"])456        if link:457            st.session_state["link"] = link458 459        # Création des onglets460        tab1, tab2 = st.tabs(["📊 Code-barres", "📱 QR Code"])461 462        # Onglet Code-barres463        with tab1:464            st.header("📊 Génération de Code-barres")465            if st.button("Générer le Code-barres"):466                if st.session_state["link"]:467                    try:468                        # Génération du code-barres469                        barcode_image = generate_barcode(st.session_state["link"])470                        st.image(barcode_image, caption="Code-barres généré", use_container_width=True)471 472                        # Bouton de téléchargement avec icône473                        barcode_buffer = BytesIO()474                        barcode_image.save(barcode_buffer, format="PNG")475                        barcode_buffer.seek(0)476                        st.download_button(477                            label="⬇️ Télécharger le Code-barres",478                            data=barcode_buffer,479                            file_name="barcode.png",480                            mime="image/png"481                        )482                    except Exception as e:483                        st.error(f"Une erreur est survenue lors de la génération : {e}")484                else:485                    st.warning("⚠️ Veuillez entrer un lien ou un texte valide.")486 487        # Onglet QR Code488        with tab2:489            st.header("📱 Génération de QR Code")490            if st.button("Générer le QR Code"):491                if st.session_state["link"]:492                    try:493                        # Génération du QR Code494                        qrcode_image = generate_qrcode(st.session_state["link"])495                        st.image(qrcode_image, caption="QR Code généré", use_container_width=True)496 497                        # Bouton de téléchargement avec icône498                        qrcode_buffer = BytesIO()499                        qrcode_image.save(qrcode_buffer, format="PNG")500                        qrcode_buffer.seek(0)501                        st.download_button(502                            label="⬇️ Télécharger le QR Code",503                            data=qrcode_buffer,504                            file_name="qrcode.png",505                            mime="image/png"506                        )507                    except Exception as e:508                        st.error(f"Une erreur est survenue lors de la génération : {e}")509                else:510                    st.warning("⚠️ Veuillez entrer un lien ou un texte valide.")511 512 513    elif menu_option == "Détection Faciale":514        st.subheader("Détection Faciale")515 516        # Sélectionner la source de l'image517        source_option = st.radio("Choisissez la source", ("Image Importée", "Autre Image", "Webcam", "Vidéo"))518 519        # Charger le classificateur de visages pré-entrainé de OpenCV520        face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")521 522        if source_option == "Image Importée":523            if st.session_state["default_image"] is not None:524                img = image_np525                gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)526                faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))527 528                # Dessiner des rectangles autour des visages529                for (x, y, w, h) in faces:530                    cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)531 532                st.image(img, caption="Image avec Visages Détectés", use_container_width=True)533 534        elif source_option == "Autre Image":535            uploaded_file_2 = st.file_uploader("Charge une autre image", type=["png", "jpg", "jpeg"])536            if uploaded_file_2 is not None:537                image_2 = Image.open(uploaded_file_2)538                img = np.array(image_2)539                gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)540                faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))541 542                # Dessiner des rectangles autour des visages543                for (x, y, w, h) in faces:544                    cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)545 546                st.image(img, caption="Autre Image avec Visages Détectés", use_container_width=True)547 548        elif source_option == "Webcam":549            stframe = st.empty()550            cap = cv2.VideoCapture(0)551 552            while True:553                ret, frame = cap.read()554                if not ret:555                    st.write("Erreur dans la lecture de la webcam.")556                    break557 558                gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)559                faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))560 561                # Dessiner des rectangles autour des visages562                for (x, y, w, h) in faces:563                    cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)564 565                # Affichage dans le streamlit566                stframe.image(frame, channels="BGR", use_container_width=True)567 568                if cv2.waitKey(1) & 0xFF == ord("q"):  # Quitter avec la touche 'q'569                    break570 571            cap.release()572 573        elif source_option == "Vidéo":574            video_file = st.file_uploader("Charge une vidéo", type=["mp4", "avi", "mov"])575            if video_file is not None:576                video_bytes = video_file.read()577                with tempfile.NamedTemporaryFile(delete=False) as tmp_file:578                    tmp_file.write(video_bytes)579                    video_path = tmp_file.name580 581                cap = cv2.VideoCapture(video_path)582                while cap.isOpened():583                    ret, frame = cap.read()584                    if not ret:585                        break586 587                    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)588                    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))589 590                    # Dessiner des rectangles autour des visages591                    for (x, y, w, h) in faces:592                        cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)593 594                    st.image(frame, caption="Vidéo avec Visages Détectés", use_container_width=True)595 596                cap.release()597        598    elif menu_option == "À propos de nous":599        st.header("À propos de nous")600        st.markdown(601            """602            <div style="text-align:center; font-family: Arial; margin: 20px 0;">603                <h2>Notre équipe</h2>604                <p>Nous sommes une équipe passionnée travaillant sur des solutions innovantes.</p>605            </div>606            <div style="display: flex; justify-content: center; gap: 30px; flex-wrap: wrap;">607                <div style="text-align: center;">608                    <img src="https://avatars.githubusercontent.com/u/1234567" alt="Ngoue David" style="width: 150px; height: 150px; border-radius: 50%;">609                    <h4>Ngoue David</h4>610                    <p>🎓 Master 2 IA & Big Data</p>611                    <p>📧 <a href="mailto:ngouedavidrogeryannick@gmail.com">ngouedavidrogeryannick@gmail.com</a></p>612                    <p>🌐 <a href="https://github.com/TheBeyonder237" target="_blank">Profil GitHub</a></p>613                </div>614                <div style="text-align: center;">615                    <img src="https://avatars.githubusercontent.com/u/7654321" alt="Bidzanga Armel" style="width: 150px; height: 150px; border-radius: 50%;">616                    <h4>Bidzanga Armel</h4>617                    <p>🎓 Master 2 IA & Big Data</p>618                    <p>📧 <a href="mailto:armelmbia08@gmail.com">armelmbia08@gmail.com</a></p>619                    <p>🌐 <a href="https://github.com/armelmbia10" target="_blank">Profil GitHub</a></p>620                </div>621                <div style="text-align: center;">622                    <img src="https://avatars.githubusercontent.com/u/9876543" alt="Nziou Serena" style="width: 150px; height: 150px; border-radius: 50%;">623                    <h4>Nziou Serena</h4>624                    <p>🎓 Master 2 Administration Systèmes</p>625                    <p>📧 <a href="mailto:serenakeliane@gmail.com">serenakeliane@gmail.com</a></p>626                    <p>🌐 <a href="https://github.com/serkeli0011" target="_blank">Profil GitHub</a></p>627                </div>628            </div>629            """,630            unsafe_allow_html=True631        )632 633 634 635if __name__ == "__main__":636    main()637