ch0nny_log

[빅데이터분석] 딥러닝_21. 영상 인터페이스 구현 (+streamlit 생성) 본문

빅데이터 분석(with 아이티윌)/deep learning

[빅데이터분석] 딥러닝_21. 영상 인터페이스 구현 (+streamlit 생성)

chonny 2024. 10. 31. 11:06

 

 

Daum 카페

 

cafe.daum.net

 

예제1. streamlit 에서 영상 업로드후 play 시키기 

 

1. 깃허브에 app16.py 로 아래 코드 업로드

import streamlit as st

# 제목 설정
st.title("Video Player App")

# 파일 업로드
uploaded_file = st.file_uploader("Upload a video file", type=["mp4", "mov", "avi"])

# 파일이 업로드되었는지 확인
if uploaded_file is not None:
    # 비디오 플레이어
    st.video(uploaded_file)
else:
    st.write("Please upload a video file to play.")

2. streamlit 으로 app16 create app 만들기
Streamlit • A faster way to build and share data apps

 

Streamlit • A faster way to build and share data apps

Streamlit is an open-source Python framework for data scientists and AI/ML engineers to deliver interactive data apps – in only a few lines of code.

streamlit.io

3. 다운로드 받은 파일 browse files 에 업로드

yk.mp4
0.65MB

 

 


예제2. 원본 영상 옆에 결과 영상 나오게하기

 

1. app16.py 파일에 있는 코드 모두 지우고 아래 코드로 채우기

import streamlit as st

# 전체 레이아웃을 넓게 설정
st.set_page_config(layout="wide")

# 제목 설정
st.title("비디오 사물 검출 앱")

# 전체 레이아웃을 컨테이너로 감싸기
with st.container():
    col1, col2 = st.columns(2)  # 열을 균등하게 분배하여 넓게 표시

    # 파일 업로드
    uploaded_file = st.file_uploader("비디오 파일을 업로드하세요", type=["mp4", "mov", "avi"])

    with col1:
        st.header("원본 영상")
        if uploaded_file is not None:
            st.video(uploaded_file)
        else:
            st.write("원본 영상을 표시하려면 비디오 파일을 업로드하세요.")

    with col2:
        st.header("사물 검출 결과 영상")
        if "processed_video" in st.session_state:
            st.video(st.session_state["processed_video"])
        else:
            st.write("여기에 사물 검출 결과가 표시됩니다.")

# 사물 검출 버튼 추가
if st.button("사물 검출 실행"):
    if uploaded_file is not None:
        st.session_state["processed_video"] = uploaded_file
        st.success("사물 검출이 완료되어 오른쪽에 표시됩니다.")
    else:
        st.warning("사물 검출을 실행하려면 비디오 파일을 업로드하세요.")

 

2. streamlit 재구현


예제3. 파일 업로드하는 버튼을 위로 올리기

- 위와 동일하게 아래 코드로 수정 

import streamlit as st

# 전체 레이아웃을 넓게 설정
st.set_page_config(layout="wide")

# 제목 설정
st.title("프로젝트 제목 사물 검출 앱")

# 파일 업로드
uploaded_file = st.file_uploader("비디오 파일을 업로드하세요", type=["mp4", "mov", "avi"])


# 전체 레이아웃을 컨테이너로 감싸기
with st.container(): # with 절로 하나의 기능을 하는 코드를 묶어줌 (가독성 높이기)
    col1, col2 = st.columns(2)  # 열을 균등하게 분배하여 넓게 표시

    # 파일 업로드
    # uploaded_file = st.file_uploader("비디오 파일을 업로드하세요", type=["mp4", "mov", "avi"])

    with col1:
        st.header("원본 영상")   # col1 영역의 제목
        if uploaded_file is not None:  # 영상이 업로드가 되었다면
            st.video(uploaded_file)   # 영상 플레이해라 !
        else:
            st.write("원본 영상을 표시하려면 비디오 파일을 업로드하세요.")

    with col2:    
        st.header("사물 검출 결과 영상")  # col2 에 해당하는 영역의 제목
        if "processed_video" in st.session_state: # 사물검출 완료된 비디오가 있으면
            st.video(st.session_state["processed_video"]) # 그 비디오를 플레이해라
        else: 
            st.write("여기에 사물 검출 결과가 표시됩니다.")

# 사물 검출 버튼 추가
if st.button("사물 검출 실행"):  # 사물검출 실행이라는 버튼을 누르면
    if uploaded_file is not None:  # 업로드된 영상이 있다면
        st.session_state["processed_video"] = uploaded_file # 검출된 영상을 사용
        st.success("사물 검출이 완료되어 오른쪽에 표시됩니다.") # 이 메세지 출력
    else:
        st.warning("사물 검출을 실행하려면 비디오 파일을 업로드하세요.")


예제4. 원본영상 옆에 빈화면 표시

- 위와 동일하게 아래 코드로 수정 

import streamlit as st

# 전체 레이아웃을 넓게 설정
st.set_page_config(layout="wide")

# 제목 설정
st.title("프로젝트 제목 사물 검출 앱")

# 파일 업로드
uploaded_file = st.file_uploader("비디오 파일을 업로드하세요", type=["mp4", "mov", "avi"])


# 전체 레이아웃을 컨테이너로 감싸기
with st.container(): # with 절로 하나의 기능을 하는 코드를 묶어줌 (가독성 높이기)
    col1, col2 = st.columns(2)  # 열을 균등하게 분배하여 넓게 표시

    # 파일 업로드
    # uploaded_file = st.file_uploader("비디오 파일을 업로드하세요", type=["mp4", "mov", "avi"])

    with col1:
        st.header("원본 영상")   # col1 영역의 제목
        if uploaded_file is not None:  # 영상이 업로드가 되었다면
            st.video(uploaded_file)   # 영상 플레이해라 !
        else:
            st.write("원본 영상을 표시하려면 비디오 파일을 업로드하세요.")


    with col2:    
        st.header("사물 검출 결과 영상")  # col2 에 해당하는 영역의 제목
        result_placeholder = st.empty()
        if "processed_video" in st.session_state: # 사물검출 완료된 비디오가 있으면
            st.video(st.session_state["processed_video"]) # 그 비디오를 플레이해라
        else: 
            #st.write("여기에 사물 검출 결과가 표시됩니다.")
            result_placeholder.markdown(
                """
                <div style='width:100%; height:620px; background-color:#d3d3d3; display:flex; align-items:center; justify-content:center; border-radius:5px;'>
                    <p style='color:#888;'>여기에 사물 검출 결과가 표시됩니다.</p>
                </div>
                """,
                unsafe_allow_html=True,
            )


# 사물 검출 버튼 추가
if st.button("사물 검출 실행"):  # 사물검출 실행이라는 버튼을 누르면
    if uploaded_file is not None:  # 업로드된 영상이 있다면
        st.session_state["processed_video"] = uploaded_file # 검출된 영상을 사용
        st.success("사물 검출이 완료되어 오른쪽에 표시됩니다.") # 이 메세지 출력
    else:
        st.warning("사물 검출을 실행하려면 비디오 파일을 업로드하세요.")


예제5. 사물검출 버튼 색깔 변경하기(오류 이슈로 일반 패스_바로 예제6으)

import streamlit as st

# 전체 레이아웃을 넓게 설정
st.set_page_config(layout="wide")

# 제목 설정
st.title("비디오 사물 검출 앱")

# 파일 업로드 버튼을 상단으로 이동
uploaded_file = st.file_uploader("비디오 파일을 업로드하세요", type=["mp4", "mov", "avi"])

# 전체 레이아웃을 컨테이너로 감싸기
with st.container():
    col1, col2 = st.columns(2)  # 열을 균등하게 분배하여 넓게 표시

    with col1:
        st.header("원본 영상")
        if uploaded_file is not None:
            st.video(uploaded_file)
        else:
            st.write("원본 영상을 표시하려면 비디오 파일을 업로드하세요.")

    with col2:
        st.header("사물 검출 결과 영상")
        # 사물 검출 결과가 나타날 자리 확보 및 고정 높이 회색 박스 스타일 추가
        result_placeholder = st.empty()
        if "processed_video" in st.session_state and st.session_state["processed_video"] is not None:
            result_placeholder.video(st.session_state["processed_video"])
        else:
            result_placeholder.markdown(
                """
                <div style='width:100%; height:620px; background-color:#d3d3d3; display:flex; align-items:center; justify-content:center; border-radius:5px;'>
                    <p style='color:#888;'>여기에 사물 검출 결과가 표시됩니다.</p>
                </div>
                """,
                unsafe_allow_html=True,
            )

# 버튼 스타일 설정
st.markdown(
    """
    <style>
    .stButton > button {
        background-color: #4d4d4d;  /* 진한 회색 */
        color: #ffffff;             /* 흰색 텍스트 */
        font-weight: bold;          /* 굵은 글씨 */
        padding: 12px 24px;
        font-size: 16px;
        border: none;
        border-radius: 8px;
        cursor: pointer;
        transition: background-color 0.3s;
    }
    .stButton > button:hover {
        background-color: #333333;  /* 호버 시 더 진한 회색 */
    }
    </style>
    """,
    unsafe_allow_html=True
)

# 사물 검출 버튼 추가 및 클릭 이벤트 처리
if st.button("사물 검출 실행"):
    if uploaded_file is not None:
        # 여기에 사물 검출을 수행하는 코드를 추가하고, 결과를 st.session_state["processed_video"]에 저장
        st.session_state["processed_video"] = None  # 실제 결과 영상으로 바꿔야 함
        result_placeholder.markdown(
            "<div style='width:100%; height:620px; background-color:#d3d3d3; display:flex; align-items:center; justify-content:center; border-radius:5px;'>"
            "<p style='color:#888;'>사물 검출 결과 영상이 여기에 표시됩니다.</p>"
            "</div>",
            unsafe_allow_html=True,
        )
        st.success("사물 검출이 완료되어 오른쪽에 표시됩니다.")
    else:
        st.warning("사물 검출을 실행하려면 비디오 파일을 업로드하세요.")

 

 


예제6. 모델 불러와서 사물 검출 구현하기

1. 깃허브 '수익화를 위한 requirements.txt 수정'

sentence_transformers
pandas
streamlit
opencv-python-headless
ultralytics
ffmpeg-python
ffmpeg
moviepy

 

 

2. app16.py 코드 수정 

import streamlit as st
from ultralytics import YOLO
import tempfile
import cv2
import os

# 전체 레이아웃을 넓게 설정
st.set_page_config(layout="wide")

# 제목 설정
st.title("프로젝트 제목 사물 검출 앱")

# 모델 파일 업로드
model_file = st.file_uploader("모델 파일을 업로드하세요", type=["pt"])
if model_file:
    with tempfile.NamedTemporaryFile(delete=False, suffix=".pt") as temp_model_file:
        temp_model_file.write(model_file.read())
        model_path = temp_model_file.name
    model = YOLO(model_path)
    st.success("모델이 성공적으로 로드되었습니다.")

# 비디오 파일 업로드
uploaded_file = st.file_uploader("비디오 파일을 업로드하세요", type=["mp4", "mov", "avi"])

# 전체 레이아웃을 컨테이너로 감싸기
with st.container():  # 코드 가독성을 높이기 위해 컨테이너로 묶음
    col1, col2 = st.columns(2)  # 열을 균등하게 분배하여 넓게 표시

    # 원본 영상 표시
    with col1:
        st.header("원본 영상")  # col1 영역의 제목
        if uploaded_file is not None:  # 영상이 업로드되었는지 확인
            st.video(uploaded_file)  # 원본 영상 표시
        else:
            st.write("원본 영상을 표시하려면 비디오 파일을 업로드하세요.")

    # 사물 검출 결과 영상 표시
    with col2:
        st.header("사물 검출 결과 영상")  # col2 영역의 제목
        result_placeholder = st.empty()  # 빈 영역 확보
        if "processed_video" in st.session_state:  # 사물 검출 결과가 있으면
            result_placeholder.video(st.session_state["processed_video"])  # 결과 영상 표시
        else:
            # 검출 결과가 없을 때 회색 박스 표시
            result_placeholder.markdown(
                """
                <div style='width:100%; height:620px; background-color:#d3d3d3; display:flex; align-items:center; justify-content:center; border-radius:5px;'>
                    <p style='color:#888;'>여기에 사물 검출 결과가 표시됩니다.</p>
                </div>
                """,
                unsafe_allow_html=True,
            )

# 사물 검출 실행 버튼 추가
if st.button("사물 검출 실행"):
    if uploaded_file is None:
        st.warning("사물 검출을 실행하려면 비디오 파일을 업로드하세요.")
    elif model_file is None:
        st.warning("사물 검출을 실행하려면 모델 파일을 업로드하세요.")
    else:
        # 임시 파일 경로 생성
        with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as temp_output:
            output_path = temp_output.name

        with tempfile.NamedTemporaryFile(delete=False) as temp_input:
            temp_input.write(uploaded_file.read())
            temp_input_path = temp_input.name

        # 원본 비디오를 읽기
        cap = cv2.VideoCapture(temp_input_path)
        fourcc = cv2.VideoWriter_fourcc(*'XVID')
        fps = cap.get(cv2.CAP_PROP_FPS)
        width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))

        # 프레임 단위로 사물 검출 수행
        frame_count = 0
        while cap.isOpened():
            ret, frame = cap.read()
            if not ret:
                break

            # YOLO 모델로 예측 수행
            results = model(frame)
            detections = results[0].boxes if len(results) > 0 else []

            if len(detections) > 0:
                for box in detections:
                    x1, y1, x2, y2 = map(int, box.xyxy[0])
                    confidence = box.conf[0]
                    class_id = int(box.cls[0])
                    class_name = model.names[class_id]
                    label = f"{class_name} {confidence:.2f}"

                    # 검출된 객체의 바운딩 박스 및 라벨 표시
                    cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
                    cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
            else:
                # 검출 결과가 없을 때 로그 출력
                st.write(f"Frame {frame_count}: No detections")

            out.write(frame)
            frame_count += 1

        # 비디오 객체 해제
        cap.release()
        out.release()

        # 결과 비디오를 세션 상태에 저장하여 표시
        st.session_state["processed_video"] = output_path
        result_placeholder.video(output_path)
        st.success("사물 검출이 완료되어 오른쪽에 표시됩니다.")

 

 


예제 7. 디텍션 결과 디버깅하기

- 본 코드에 아래 코드 추가

import streamlit as st
from ultralytics import YOLO
import tempfile
import cv2
import os

# 전체 레이아웃을 넓게 설정
st.set_page_config(layout="wide")

# 제목 설정
st.title("비디오 사물 검출 앱")

# 모델 파일 업로드
model_file = st.file_uploader("모델 파일을 업로드하세요", type=["pt"])
if model_file:
    with tempfile.NamedTemporaryFile(delete=False, suffix=".pt") as temp_model_file:
        temp_model_file.write(model_file.read())
        model_path = temp_model_file.name
    model = YOLO(model_path)
    st.success("모델이 성공적으로 로드되었습니다.")

# 비디오 파일 업로드
uploaded_file = st.file_uploader("비디오 파일을 업로드하세요", type=["mp4", "mov", "avi"])

# 전체 레이아웃을 컨테이너로 감싸기
with st.container():
    col1, col2 = st.columns(2)

    with col1:
        st.header("원본 영상")
        if uploaded_file is not None:
            st.video(uploaded_file)
        else:
            st.write("원본 영상을 표시하려면 비디오 파일을 업로드하세요.")

    with col2:
        st.header("사물 검출 결과 영상")
        result_placeholder = st.empty()
        if "processed_video" in st.session_state and st.session_state["processed_video"] is not None:
            result_placeholder.video(st.session_state["processed_video"])
        else:
            result_placeholder.markdown(
                """
                <div style='width:100%; height:620px; background-color:#d3d3d3; display:flex; align-items:center; justify-content:center; border-radius:5px;'>
                    <p style='color:#888;'>여기에 사물 검출 결과가 표시됩니다.</p>
                </div>
                """,
                unsafe_allow_html=True,
            )

# 버튼 스타일 설정
st.markdown(
    """
    <style>
    .stButton > button {
        background-color: #4d4d4d;
        color: #ffffff;
        font-weight: bold;
        padding: 12px 24px;
        font-size: 16px;
        border: none;
        border-radius: 8px;
        cursor: pointer;
        transition: background-color 0.3s;
    }
    .stButton > button:hover {
        background-color: #333333;
    }
    </style>
    """,
    unsafe_allow_html=True
)

# 사물 검출 버튼 클릭 이벤트 처리
if st.button("사물 검출 실행") and uploaded_file and model_file:
    with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as temp_output:
        output_path = temp_output.name

    with tempfile.NamedTemporaryFile(delete=False) as temp_input:
        temp_input.write(uploaded_file.read())
        temp_input_path = temp_input.name

    cap = cv2.VideoCapture(temp_input_path)
    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    fps = cap.get(cv2.CAP_PROP_FPS)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))

    frame_count = 0
    while cap.isOpened():
        ret, frame = cap.read()
        if not ret:
            break

        # YOLO 모델로 예측 수행 및 디버깅
        results = model(frame)
        detections = results[0].boxes if len(results) > 0 else []

        if len(detections) > 0:
            for box in detections:
                x1, y1, x2, y2 = map(int, box.xyxy[0])
                confidence = box.conf[0]
                class_id = int(box.cls[0])
                class_name = model.names[class_id]
                label = f"{class_name} {confidence:.2f}"

                cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
                cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
            st.write(f"Frame {frame_count}: {len(detections)} detections")
        else:
            # 검출 결과가 없을 때도 원본 프레임을 저장
            st.write(f"Frame {frame_count}: No detections - Original frame saved")

        # 원본 또는 검출된 프레임을 그대로 저장
        out.write(frame)
        frame_count += 1

    cap.release()
    out.release()

    # 결과 비디오를 st.session_state에 저장하여 스트림릿에 표시
    st.session_state["processed_video"] = output_path
    result_placeholder.video(output_path)
    st.success("사물 검출이 완료되어 오른쪽에 표시됩니다.")

 # 다운로드 링크 제공
    with open(output_path, "rb") as file:
        st.download_button(
            label="결과 영상 다운로드",
            data=file,
            file_name="detected_video.avi",
            mime="video/avi"
        )

예제8. 결과 다운로드 하기

detected_video.avi
4.34MB

 

 

 

비디오 사물 검출 및 재인코딩 앱 코드

import streamlit as st
from ultralytics import YOLO
import tempfile
import cv2
from moviepy.editor import VideoFileClip
import os

# 페이지 레이아웃 설정
st.set_page_config(layout="wide")

# 제목
st.title("비디오 사물 검출 및 재인코딩 앱")

# 모델 파일 업로드
model_file = st.file_uploader("모델 파일을 업로드하세요", type=["pt"])
if model_file:
    with tempfile.NamedTemporaryFile(delete=False, suffix=".pt") as temp_model_file:
        temp_model_file.write(model_file.read())
        model_path = temp_model_file.name
    model = YOLO(model_path)
    st.success("모델이 성공적으로 로드되었습니다.")

# 비디오 파일 업로드
uploaded_file = st.file_uploader("비디오 파일을 업로드하세요", type=["mp4", "mov", "avi"])

# 원본 영상 표시
if uploaded_file is not None:
    st.header("원본 영상")
    st.video(uploaded_file)

# 사물 검출 실행 버튼
if st.button("사물 검출 실행") and uploaded_file and model_file:
    # 임시 파일 경로 생성
    with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as temp_output:
        output_path = temp_output.name

    # 원본 비디오 파일을 임시 파일로 저장
    with tempfile.NamedTemporaryFile(delete=False) as temp_input:
        temp_input.write(uploaded_file.read())
        temp_input_path = temp_input.name

    # 비디오 처리 시작
    cap = cv2.VideoCapture(temp_input_path)
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    fps = cap.get(cv2.CAP_PROP_FPS)
    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    out = cv2.VideoWriter(output_path, fourcc, fps, (width, height))

    # 프레임별로 사물 검출 수행
    while cap.isOpened():
        ret, frame = cap.read()
        if not ret:
            break

        # YOLO 모델로 예측 수행
        results = model(frame)
        detections = results[0].boxes if len(results) > 0 else []

        # 검출된 객체에 대해 바운딩 박스 그리기
        for box in detections:
            x1, y1, x2, y2 = map(int, box.xyxy[0])
            confidence = box.conf[0]
            class_id = int(box.cls[0])
            class_name = model.names[class_id]
            label = f"{class_name} {confidence:.2f}"

            cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
            cv2.putText(frame, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

        out.write(frame)

    cap.release()
    out.release()

    # moviepy를 사용해 재인코딩 수행
    st.header("재인코딩된 결과 영상")
    reencoded_path = output_path.replace(".mp4", "_reencoded.mp4")
    clip = VideoFileClip(output_path)
    clip.write_videofile(reencoded_path, codec="libx264", audio_codec="aac")

    # 재인코딩된 비디오 다운로드 버튼 제공
    with open(reencoded_path, "rb") as file:
        st.download_button(
            label="재인코딩된 결과 영상 다운로드",
            data=file,
            file_name="reencoded_video.mp4",
            mime="video/mp4"
        )

# 결과 영상 재생을 위해 업로드
uploaded_result = st.file_uploader("결과 영상을 업로드하세요", type=["mp4"])
if uploaded_result is not None:
    st.header("사물 검출 결과 영상")
    st.video(uploaded_result)