Download app.py from Passman22231/Motion-Track_Shape_Censor: direct link, hf CLI and curl.
- Browser
- Download file 11.1 kB
-
https://huggingface.co/spaces/Passman22231/Motion-Track_Shape_Censor/resolve/main/app.py
- Command line
-
hf download hf://spaces/Passman22231/Motion-Track_Shape_Censor/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Passman22231/Motion-Track_Shape_Censor/resolve/main/app.py
11.1 kB
| """ | |
| Motion-Track Shape Censor (CPU ์ ์ฉ, GPU ๋ถํ์) | |
| ํด๋ฆญํ ์ง์ ์ OpenCV ๊ดํํ๋ฆ์ผ๋ก ์ถ์ ํด์ ํฐ ์/๋ค๋ชจ๋ฅผ ์์ฐ๋ ๊ฒฝ๋ ๊ฒ์ด ๋๊ตฌ. | |
| SAM ๊ณ์ด์ ํํ ๋ฐฑ์ ๋ง์คํน์ด ๋ชป ์ก๋ ๋ถ๋ถ์ '๋ํ'์ผ๋ก ๋ณด์กฐ ๊ฒ์ดํ๋ ์ฉ๋. | |
| ํ๊น ํ์ด์ค ๋ฌด๋ฃ CPU Basic Space์ ์ฌ๋ฆฌ๋ฉด ์์ ์๊ธ 0์์ผ๋ก ํญ์ ์ฌ์ฉ ๊ฐ๋ฅ. | |
| """ | |
| import os | |
| import tempfile | |
| import cv2 | |
| import numpy as np | |
| import gradio as gr | |
| MAX_SECONDS = 12 # ์ฒ๋ฆฌ ์ํ (์งง์ ํด๋ฆฝ ์ ์ฉ) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ์ํ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| STATE = { | |
| "video": None, | |
| "frames": [], # RGB ํ๋ ์ ๋ฆฌ์คํธ | |
| "gray": [], # ๊ทธ๋ ์ด์ค์ผ์ผ(๊ดํํ๋ฆ์ฉ) | |
| "fps": 24.0, | |
| "w": 0, | |
| "h": 0, | |
| } | |
| def _load_video(video_path): | |
| if not video_path: | |
| return None, gr.update(maximum=1, value=0), [], gr.update(open=True) | |
| cap = cv2.VideoCapture(video_path) | |
| fps = cap.get(cv2.CAP_PROP_FPS) or 24.0 | |
| frames, grays = [], [] | |
| max_frames = int(fps * MAX_SECONDS) | |
| while len(frames) < max_frames: | |
| ok, fr = cap.read() | |
| if not ok: | |
| break | |
| rgb = cv2.cvtColor(fr, cv2.COLOR_BGR2RGB) | |
| frames.append(rgb) | |
| grays.append(cv2.cvtColor(fr, cv2.COLOR_BGR2GRAY)) | |
| cap.release() | |
| if not frames: | |
| return None, gr.update(maximum=1, value=0), [], gr.update(open=True) | |
| STATE.update(video=video_path, frames=frames, gray=grays, fps=fps, | |
| w=frames[0].shape[1], h=frames[0].shape[0]) | |
| # ์์ ๋ก๋๋๋ฉด ์ ๋ก๋ ํจ๋ ์ ๊ธฐ | |
| return frames[0], gr.update(maximum=len(frames) - 1, value=0), [], gr.update(open=False) | |
| def _draw_points(idx, clicks): | |
| """ํ์ฌ ํ๋ ์์ ํด๋ฆญ ์ง์ ํ์.""" | |
| if not STATE["frames"]: | |
| return None | |
| idx = int(np.clip(idx, 0, len(STATE["frames"]) - 1)) | |
| img = STATE["frames"][idx].copy() | |
| for (x, y) in (clicks or []): | |
| cv2.circle(img, (int(x), int(y)), 6, (0, 200, 0), -1) | |
| cv2.circle(img, (int(x), int(y)), 6, (255, 255, 255), 1) | |
| return img | |
| def _click(idx, clicks, evt: gr.SelectData): | |
| x, y = evt.index[0], evt.index[1] | |
| clicks = (clicks or []) + [[x, y]] | |
| return _draw_points(idx, clicks), clicks | |
| def _reset(idx): | |
| return _draw_points(idx, []), [] | |
| def _preview(idx, clicks, mask_mode, shape_size): | |
| """ํ์ฌ ํ๋ ์์ ๋ํ์ ์์ด ๋ฏธ๋ฆฌ๋ณด๊ธฐ.""" | |
| if not STATE["frames"]: | |
| return None, "์์์ ์ ๋ก๋ํ์ธ์." | |
| if not clicks: | |
| return None, "๊ฐ๋ฆด ์ง์ ์ ํด๋ฆญํ์ธ์." | |
| idx = int(np.clip(idx, 0, len(STATE["frames"]) - 1)) | |
| img = STATE["frames"][idx].copy() | |
| s = int(shape_size) | |
| for (x, y) in clicks: | |
| x, y = int(x), int(y) | |
| if mask_mode == "ํฐ ์": | |
| cv2.circle(img, (x, y), s, (255, 255, 255), -1) | |
| else: | |
| cv2.rectangle(img, (x - s, y - s), (x + s, y + s), (255, 255, 255), -1) | |
| return img, "ํฐ ๋ํ์ด ๊ฐ๋ ค์ง ์์น์ ๋๋ค. ์์นยทํฌ๊ธฐ๋ฅผ ํ์ธํ๊ณ ์ฒ๋ฆฌ ์คํ์ ๋๋ฅด์ธ์." | |
| def _track_points(start_idx, points, error_threshold=30.0): | |
| """ | |
| start_idx ํ๋ ์์ points๋ฅผ ๊ดํํ๋ฆ์ผ๋ก ์ ์ฒด ํ๋ ์์ ๋ํด ์ถ์ . | |
| ๋ฐํ: {frame_idx: [(x, y, visible), ...]} โ ๊ฐ ํ๋ ์์์์ ์ ์์น + ๊ฐ์์ฑ. | |
| ์์ผ๋ก(โ๋) + ๋ค๋ก(โ์ฒ์) ์๋ฐฉํฅ ์ถ์ . | |
| visible=False๋ฉด ๊ทธ ํ๋ ์์์ ์ถ์ ์ ๋์น ๊ฒ(๋ถ์๊ฐ ๊ฐ๋ ค์ง/์ฌ๋ผ์ง) โ ๋ํ ์จ๊น. | |
| """ | |
| grays = STATE["gray"] | |
| n = len(grays) | |
| npts = len(points) | |
| tracked = {start_idx: [(float(p[0]), float(p[1]), True) for p in points]} | |
| lk_params = dict(winSize=(21, 21), maxLevel=3, | |
| criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 20, 0.03)) | |
| def run(direction): | |
| pts = np.array(points, dtype=np.float32).reshape(-1, 1, 2) | |
| rng = range(start_idx + 1, n) if direction > 0 else range(start_idx - 1, -1, -1) | |
| prev_pts = pts | |
| prev_idx = start_idx | |
| # ํ ๋ฒ ๋์น ์ ์ ๊ทธ ๋ฐฉํฅ์ผ๋ก ๊ณ์ ์จ๊น (ํ ์ฌ๋ผ์ง ๋ค ์ ์ง) | |
| lost = [False] * npts | |
| for f in rng: | |
| new_pts, st, err = cv2.calcOpticalFlowPyrLK( | |
| grays[prev_idx], grays[f], prev_pts, None, **lk_params) | |
| if new_pts is None: | |
| new_pts = prev_pts | |
| st = np.zeros((npts, 1), dtype=np.uint8) | |
| err = np.full((npts, 1), 999.0, dtype=np.float32) | |
| row = [] | |
| for i in range(npts): | |
| ok = (st is not None and int(st[i][0]) == 1) | |
| e = float(err[i][0]) if err is not None else 999.0 | |
| # ์ถ์ ์คํจ ๋๋ ์ค์ฐจ๊ฐ ์๊ณ๊ฐ ์ด๊ณผ โ ์ด ์ ์ ์ฌ๋ผ์ง ๊ฒ์ผ๋ก ๊ฐ์ฃผ | |
| if (not ok) or (e > error_threshold): | |
| lost[i] = True | |
| x = float(np.clip(new_pts[i, 0, 0], 0, STATE["w"] - 1)) | |
| y = float(np.clip(new_pts[i, 0, 1], 0, STATE["h"] - 1)) | |
| visible = not lost[i] | |
| row.append((x, y, visible)) | |
| new_pts[i, 0, 0] = x | |
| new_pts[i, 0, 1] = y | |
| # ๊ธฐ์กด ๊ฐ์ด ์์ผ๋ฉด(๋ฐ๋ ๋ฐฉํฅ์์ ์ฑ์์ง ๊ฒฝ์ฐ) ๊ฐ์์ฑ์ OR๋ก ํฉ์นจ | |
| if f in tracked: | |
| merged = [] | |
| for a, b in zip(tracked[f], row): | |
| merged.append((b[0], b[1], a[2] or b[2])) | |
| tracked[f] = merged | |
| else: | |
| tracked[f] = row | |
| prev_pts = new_pts | |
| prev_idx = f | |
| run(+1) # ์์ผ๋ก | |
| run(-1) # ๋ค๋ก (์๋ฐฉํฅ) | |
| return tracked | |
| def _render(start_idx, clicks, mask_mode, shape_size, sensitivity): | |
| if not STATE["frames"]: | |
| return None, "์์์ ์ ๋ก๋ํ์ธ์." | |
| if not clicks: | |
| return None, "๊ฐ๋ฆด ์ง์ ์ ํด๋ฆญํ์ธ์." | |
| start_idx = int(np.clip(start_idx, 0, len(STATE["frames"]) - 1)) | |
| points = [[float(x), float(y)] for (x, y) in clicks] | |
| # ๋ฏผ๊ฐ๋ โ ์ค์ฐจ ์๊ณ๊ฐ ๋ณํ (๋ฏผ๊ฐ๋ ๋์์๋ก ์ฝ๊ฒ ์ฌ๋ผ์ง = ์๊ณ๊ฐ ๋ฎ์) | |
| # ์ฌ๋ผ์ด๋ 1(๋๊ฐ)~100(๋ฏผ๊ฐ) โ threshold 60~8 | |
| error_threshold = 60.0 - (float(sensitivity) - 1) * (52.0 / 99.0) | |
| # ์ ์ถ์ (๊ฐ์์ฑ ํฌํจ) | |
| tracked = _track_points(start_idx, points, error_threshold=error_threshold) | |
| # ๋ ๋ | |
| tmp = tempfile.mkdtemp() | |
| # ๊ณ ์ ํ์ผ๋ช : ํ์์คํฌํ + ์งง์ ๋๋ค (๋ถ๋ฅ/๊ฒน์นจ๋ฐฉ์ง ํธ์) | |
| import datetime, uuid | |
| stamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") | |
| rand = uuid.uuid4().hex[:4] | |
| out_path = os.path.join(tmp, f"censored_{stamp}_{rand}.mp4") | |
| fourcc = cv2.VideoWriter_fourcc(*"mp4v") | |
| vw = cv2.VideoWriter(out_path, fourcc, STATE["fps"], (STATE["w"], STATE["h"])) | |
| s = int(shape_size) | |
| for f in range(len(STATE["frames"])): | |
| # BGR๋ก ๋ณํํด์ ๊ธฐ๋ก | |
| frame_bgr = cv2.cvtColor(STATE["frames"][f], cv2.COLOR_RGB2BGR).copy() | |
| pts = tracked.get(f, []) | |
| for pt in pts: | |
| x, y, visible = int(pt[0]), int(pt[1]), pt[2] | |
| if not visible: | |
| continue # ์ถ์ ๋์น ํ๋ ์ โ ๋ํ ์จ๊น (ํ ์ฌ๋ผ์ง) | |
| if mask_mode == "ํฐ ์": | |
| cv2.circle(frame_bgr, (x, y), s, (255, 255, 255), -1) | |
| else: | |
| cv2.rectangle(frame_bgr, (x - s, y - s), (x + s, y + s), (255, 255, 255), -1) | |
| vw.write(frame_bgr) | |
| vw.release() | |
| return out_path, f"์๋ฃ! {len(clicks)}๊ฐ ์ง์ ์ถ์ . ๊ฐ๋ ค์ง๋ฉด ๋ํ์ด ์๋์ผ๋ก ์ฌ๋ผ์ง๋๋ค." | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # UI | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| with gr.Blocks(title="Motion-Track Shape Censor") as demo: | |
| gr.Markdown(""" | |
| ### Motion-Track Shape Censor (๋ํ ๊ฒ์ด ยท CPU ์ ์ฉ) | |
| ํด๋ฆญํ ์ง์ ์ ๊ดํํ๋ฆ์ผ๋ก ์ถ์ ํด์ ํฐ ์/๋ค๋ชจ๋ฅผ ์์๋๋ค. | |
| ํํ ๋ฐฑ์ ๋ง์คํน์ด ๋ชป ์ก๋ ๋ถ๋ถ์ ๋ํ์ผ๋ก ๋ณด์กฐ ๊ฒ์ดํ๋ ์ฉ๋. **GPU ๋ถํ์.** | |
| """) | |
| clicks_st = gr.State([]) | |
| with gr.Row(): | |
| with gr.Column(): | |
| with gr.Accordion("์ ๋ ฅ ์์ (์ ๋ก๋/๊ต์ฒดํ๋ ค๋ฉด ํผ์น๊ธฐ)", open=True) as upload_acc: | |
| vid = gr.Video(label="์ ๋ ฅ ์์ (mp4)") | |
| frame_img = gr.Image(label="ํ๋ ์ โ ๊ฐ๋ฆด ์ง์ ํด๋ฆญ (์ฌ๋ฌ ๊ฐ ๊ฐ๋ฅ)", interactive=True) | |
| frame_slider = gr.Slider(0, 1, value=0, step=1, | |
| label="ํ๋ ์ ์ ํ (๋์์ด ์ ๋ณด์ด๋ ์ง์ )") | |
| reset_btn = gr.Button("์ ์ด๊ธฐํ") | |
| with gr.Column(): | |
| preview_img = gr.Image(label="๋ฏธ๋ฆฌ๋ณด๊ธฐ (ํฐ ๋ํ = ๊ฐ๋ ค์ง ์์น)") | |
| mask_mode = gr.Radio(["ํฐ ์", "ํฐ ๋ค๋ชจ"], value="ํฐ ์", label="๋ํ ์ข ๋ฅ") | |
| shape_size = gr.Slider(10, 200, value=50, step=5, | |
| label="๋ํ ํฌ๊ธฐ (์=๋ฐ์ง๋ฆ / ๋ค๋ชจ=๋ฐ์ชฝ ๋ณ, px)") | |
| sensitivity = gr.Slider(1, 100, value=50, step=1, | |
| label="์ฌ๋ผ์ง ๋ฏผ๊ฐ๋ (๋์์๋ก ๊ฐ๋ ค์ง ๋ ๋นจ๋ฆฌ ์ฌ๋ผ์ง)") | |
| with gr.Row(): | |
| preview_btn = gr.Button("๋ฏธ๋ฆฌ๋ณด๊ธฐ") | |
| go_btn = gr.Button("์ฒ๋ฆฌ ์คํ", variant="primary") | |
| status = gr.Textbox(label="์ํ", interactive=False) | |
| result_video = gr.Video(label="๊ฒฐ๊ณผ ์์ (์๋ณธ + ํฐ ๋ํ)", height=280) | |
| vid.change(_load_video, inputs=vid, | |
| outputs=[frame_img, frame_slider, clicks_st, upload_acc]) | |
| frame_slider.change(_draw_points, inputs=[frame_slider, clicks_st], outputs=frame_img) | |
| frame_img.select(_click, inputs=[frame_slider, clicks_st], outputs=[frame_img, clicks_st]) | |
| reset_btn.click(_reset, inputs=frame_slider, outputs=[frame_img, clicks_st]) | |
| preview_btn.click(_preview, inputs=[frame_slider, clicks_st, mask_mode, shape_size], | |
| outputs=[preview_img, status]) | |
| go_btn.click(_render, inputs=[frame_slider, clicks_st, mask_mode, shape_size, sensitivity], | |
| outputs=[result_video, status]) | |
| gr.Markdown(""" | |
| **์ฌ์ฉ๋ฒ**: ์์ ์ ๋ก๋ โ ํ๋ ์ ์ด๋ โ ๊ฐ๋ฆด ์ง์ ํด๋ฆญ(์ฌ๋ฌ ๊ฐ OK) โ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ก ํ์ธ โ ์ฒ๋ฆฌ ์คํ | |
| ยท ๊ฐ ์ ์ ์๋ค๋ก ์ถ์ ํด์ ๋ํ์ด ๋์์ ๋ฐ๋ผ๊ฐ๋๋ค. | |
| ยท **๋ถ์๊ฐ ์ ๋ฑ์ผ๋ก ๊ฐ๋ ค์ง๋ฉด ๋ํ์ด ํ ์ฌ๋ผ์ง๋๋ค** โ ์ ์ ๋๋ฉด '์ฌ๋ผ์ง ๋ฏผ๊ฐ๋'๋ฅผ ์กฐ์ ํ์ธ์. | |
| ยท ๋์์ด ๊ธํ์ ํ๊ฑฐ๋ ํ๋ฉด์ ๋ฒ์ด๋๋ฉด ์ถ์ ์ด ํ๋ค๋ฆด ์ ์์ด์(์งง์ ํด๋ฆฝ ๊ถ์ฅ). | |
| """) | |
| if __name__ == "__main__": | |
| demo.queue(max_size=10).launch() |