Instructions to use albitro/phoenix_detection with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- ultralytics
How to use albitro/phoenix_detection with ultralytics:
# Couldn't find a valid YOLO version tag. # Replace XX with the correct version. from ultralytics import YOLOvXX model = YOLOvXX.from_pretrained("albitro/phoenix_detection") source = 'http://images.cocodataset.org/val2017/000000039769.jpg' model.predict(source=source, save=True) - Notebooks
- Google Colab
- Kaggle
| #!/usr/bin/env python3 | |
| """ | |
| Dark Channel Prior (DCP) ๊ธฐ๋ฐ ๋ํค์ด์ฆ โ He et al., CVPR 2009. | |
| OpenCV์ ์์ ํ์ด ์์ด ์ง์ ๊ตฌํํ ๋ถ๋ถ. | |
| ROS์ ์์กดํ์ง ์์ผ๋ฏ๋ก ์ฃผํผํฐ/CLI์์ ๊ทธ๋๋ก importํด ํ๋ํ ์ ์์ต๋๋ค. | |
| ์ค์๊ฐ์ฑ ์ต์ ํ (๋ฐํ์์์ ๊ธฐ์ฌ ํฌ์ธํธ): | |
| - ๋๊ธฐ๊ด/ํฌ๊ณผ์จ ์ถ์ ์ ์ถ์ ํด์๋(๊ธฐ๋ณธ 1/4)์์ ์ํ โ ํฝ์ ์ 1/16 | |
| - guided filter๋ ์ถ์ ํด์๋์์ ์ํ ํ ํฌ๊ณผ์จ๋ง ์ ์ํ | |
| - ๋ณต์์๋ง ์๋ณธ ํด์๋์์ ๊ณ์ฐ (๋ฒกํฐ ์ฐ์ฐ 3์ค) | |
| - min filter๋ cv2.erode ๋ก ๋์ฒด (์ฌ๊ฐ ์ปค๋ erode == min filter, C ๊ตฌํ) | |
| ํ์ฌ ๋๋ฉ์ธ ํน์ ์ ํจ์ : | |
| - ํ์ค DCP์ ๋๊ธฐ๊ด A ์ถ์ ์ "๊ฐ์ฅ ๋ฐ์ ํฝ์ "์ ๊ณ ๋ฅด๋๋ฐ, ์ฐ๋ฆฌ ์ฅ๋ฉด์์๋ | |
| ๊ทธ๊ฒ **๋ถ์จ/ํ์ผ**์ผ ์ ์์ต๋๋ค. A๊ฐ ๊ณผ๋ ์ถ์ ๋๋ฉด ํ๋ฉด ์ ์ฒด๊ฐ ์ด๋์์ง๊ณ | |
| ์ ์ ๋ถ์จ ์ฃผ๋ณ์ด ๋ญ๊ฐ์ง๋๋ค. | |
| - ๋์: ์์ ํ๋ณด์ **ํ๊ท **์ ์ฌ์ฉ + a_max๋ก ์ํ ํด๋ฆฌํ + (์ต์ ) ํ๋ฉด ์๋จ | |
| ์์ญ๋ง ํ๋ณด๋ก ์ผ๊ธฐ(sky_ratio). ์๋ estimate_atmospheric_light ์ฐธ๊ณ . | |
| """ | |
| from __future__ import annotations | |
| import cv2 | |
| import numpy as np | |
| # ---------------------------------------------------------------- ๊ธฐ๋ณธ ์ฐ์ฐ | |
| def dark_channel(img: np.ndarray, patch: int) -> np.ndarray: | |
| """๋คํฌ ์ฑ๋: ์ฑ๋ ์ต์๊ฐ -> patch x patch ์ต์ ํํฐ. | |
| img: float32 (H, W, 3), 0~1 ๋ฒ์ | |
| """ | |
| min_ch = np.min(img, axis=2) | |
| if patch <= 1: | |
| return min_ch | |
| kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (patch, patch)) | |
| # erode(์ฌ๊ฐ ์ปค๋) == ์ต์ ํํฐ. ์ง์ ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์ง๋ฉด ์์ญ ๋ฐฐ ๋๋ฆผ. | |
| return cv2.erode(min_ch, kernel) | |
| def estimate_atmospheric_light( | |
| img: np.ndarray, | |
| dark: np.ndarray, | |
| top_ratio: float = 0.001, | |
| a_max: float = 0.92, | |
| sky_ratio: float = 1.0, | |
| ) -> np.ndarray: | |
| """๋๊ธฐ๊ด A ์ถ์ (1, 1, 3). | |
| top_ratio : ๋คํฌ ์ฑ๋ ์์ ๋ช %๋ฅผ ํ๋ณด๋ก ๋ณผ์ง (์๋ ผ๋ฌธ 0.1%) | |
| a_max : A ์ํ. ํ์ผ ๊ฐ์ ํฌํ ํฝ์ ์ด ์์์ ๋ ๊ณผ๋์ถ์ ๋ฐฉ์ง | |
| sky_ratio : ํ๋ณด๋ฅผ ์ด๋ฏธ์ง ์๋จ ๋ช ๋น์จ๋ก ์ ํํ ์ง (1.0 = ์ ํ ์์). | |
| ์งํ์ฃผ์ฐจ์ฅ์ฒ๋ผ ํ๋์ด ์์ผ๋ฉด 1.0 ์ ์ง, ์ฐ๊ธฐ๊ฐ ์์ ๊น๋ฆฌ๋ฉด 0.5~0.7. | |
| """ | |
| h, w = dark.shape | |
| limit = h if sky_ratio >= 1.0 else max(1, int(h * sky_ratio)) | |
| dark_roi = dark[:limit] | |
| img_roi = img[:limit] | |
| n = max(int(dark_roi.size * top_ratio), 1) | |
| flat_dark = dark_roi.ravel() | |
| idx = np.argpartition(flat_dark, -n)[-n:] | |
| candidates = img_roi.reshape(-1, 3)[idx] # (n, 3) | |
| # ์๋ ผ๋ฌธ์ ํ๋ณด ์ค "๊ฐ์ฅ ๋ฐ์ ํ ํฝ์ "์ ์ฐ์ง๋ง, ์คํํ๋ฌ/ํ์ผ ํ ์ ์ | |
| # ํต์งธ๋ก ๋๋ ค๊ฐ๋๋ค. ํ๊ท ์ด ํจ์ฌ ์์ ์ ์ ๋๋ค. | |
| a = candidates.mean(axis=0) | |
| a = np.clip(a, 1e-3, a_max) | |
| return a.reshape(1, 1, 3).astype(np.float32) | |
| def guided_filter(guide: np.ndarray, src: np.ndarray, radius: int, eps: float) -> np.ndarray: | |
| """๋จ์ผ ์ฑ๋ guided filter (He et al.). | |
| cv2.ximgproc.guidedFilter ๋ opencv-contrib-python ์ด ์์ด์ผ ์ธ ์ ์์ด์ | |
| box filter 5๋ฒ์ผ๋ก ์ง์ ๊ตฌํํ์ต๋๋ค. ์์กด์ฑ ์์ด ๋์ํฉ๋๋ค. | |
| """ | |
| guide = guide.astype(np.float32) | |
| src = src.astype(np.float32) | |
| k = (2 * radius + 1, 2 * radius + 1) | |
| mean_i = cv2.blur(guide, k) | |
| mean_p = cv2.blur(src, k) | |
| corr_i = cv2.blur(guide * guide, k) | |
| corr_ip = cv2.blur(guide * src, k) | |
| var_i = corr_i - mean_i * mean_i | |
| cov_ip = corr_ip - mean_i * mean_p | |
| a = cov_ip / (var_i + eps) | |
| b = mean_p - a * mean_i | |
| mean_a = cv2.blur(a, k) | |
| mean_b = cv2.blur(b, k) | |
| return mean_a * guide + mean_b | |
| # ---------------------------------------------------------------- ๋ฉ์ธ ํด๋์ค | |
| class DarkChannelDehazer: | |
| """DCP ๋ํค์ด์ . | |
| process(bgr_uint8) -> bgr_uint8 ํํ๋ก ์๋๋ค. | |
| ํ๋ผ๋ฏธํฐ๋ ROS ํ๋ผ๋ฏธํฐ์์ ๊ทธ๋๋ก ๊ฐ์๋ผ์ธ ์ ์๊ฒ attribute๋ก ๋ ธ์ถ. | |
| """ | |
| def __init__( | |
| self, | |
| omega: float = 0.95, | |
| t0: float = 0.1, | |
| patch: int = 15, | |
| scale: float = 0.25, | |
| use_guided: bool = True, | |
| guided_radius: int = 8, | |
| guided_eps: float = 1e-3, | |
| a_top_ratio: float = 0.001, | |
| a_max: float = 0.92, | |
| sky_ratio: float = 1.0, | |
| a_smoothing: float = 0.0, | |
| ): | |
| self.omega = omega # 1.0์ผ๋ก ํ๋ฉด ์๊ทผ๊ฐ์ด ์ฌ๋ผ์ ธ ๋ถ์์ฐ์ค๋ฌ์ (์๋ ผ๋ฌธ 0.95) | |
| self.t0 = t0 # ํฌ๊ณผ์จ ํํ. ๋ฎ์์๋ก ์งํ ์ฐ๊ธฐ๊น์ง ๋ณต์ํ์ง๋ง ๋ ธ์ด์ฆ ํญ๋ฐ | |
| self.patch = patch # ์๋ณธ ํด์๋ ๊ธฐ์ค ํจ์น ํฌ๊ธฐ | |
| self.scale = scale # ์ถ์ ๋จ๊ณ ์ถ์ ๋ฐฐ์จ (โ ์ค์๊ฐ์ฑ์ ํต์ฌ) | |
| self.use_guided = use_guided | |
| self.guided_radius = guided_radius # ์ถ์ ํด์๋ ๊ธฐ์ค ๋ฐ๊ฒฝ | |
| self.guided_eps = guided_eps | |
| self.a_top_ratio = a_top_ratio | |
| self.a_max = a_max | |
| self.sky_ratio = sky_ratio | |
| # ๋๊ธฐ๊ด A์ ํ๋ ์ ๊ฐ EMA ๊ณ์ (0 = ๋, 0.9 = ๊ฐํ ํํ). | |
| # A๋ ๋งค ํ๋ ์ ์ฅ๋ฉด ๋ด์ฉ์์ ์ถ์ ๋๋ฏ๋ก, ๋ก๋ด์ด ์์ง์ฌ ํ๋ฉด์ด ๋ฐ๋๋ฉด | |
| # **๊ฐ์ ๋ถ์จ๊ฐ ํ๋ ์๋ง๋ค ๋ค๋ฅธ ๋ฐ๊ธฐ๋ก ๋ณต์**๋ฉ๋๋ค. ์์์์๋ ๊น๋นก์์ผ๋ก | |
| # ๋ณด์ด๊ณ , YOLO ํ์ต ๋ฐ์ดํฐ๋ก ์ฐ๋ฉด ๊ฐ์ ๋ฌผ์ฒด์ ์ธํ ๋ถ์ฐ์ด ์ปค์ง๋๋ค. | |
| # ์๊ณ์ด(๋์์ยทrosbagยท์ค์ฃผํ)์๋ ์ผ๊ณ , ์ ์ง์์ ๋น๊ต ์คํ์๋ ๋๋๋ค. | |
| # โ ์ผ๋ฉด ์ถ๋ ฅ์ด ์ด์ ํ๋ ์์ ์์กดํ๋ฏ๋ก ๋ ์ด์ ๊ฒฐ์ ๋ก ์ ์ด์ง ์์ต๋๋ค. | |
| self.a_smoothing = a_smoothing | |
| self._a_ema: np.ndarray | None = None | |
| self.last_transmission: np.ndarray | None = None # ๋๋ฒ๊น /๋ฐํ ๊ทธ๋ฆผ์ฉ | |
| self.last_a: np.ndarray | None = None | |
| def reset_state(self) -> None: | |
| """ํ๋ ์ ๊ฐ ๋์ ์ํ ์ด๊ธฐํ. ์ bag/์์์ ์์ํ ๋ ํธ์ถ.""" | |
| self._a_ema = None | |
| # ------------------------------------------------------------------ | |
| def _patch_for_scale(self) -> int: | |
| """์ถ์๋ณธ์์ ๊ฐ์ '์ค์ ๊ณต๊ฐ ๋ฒ์'๋ฅผ ๋ฎ๋๋ก ํจ์น๋ ํจ๊ป ์ค์ ๋๋ค. | |
| ์ด๊ฑธ ์ ์ค์ด๋ฉด ์ถ์๋ณธ์์ ํจ์น๊ฐ ์๋์ ์ผ๋ก ๋๋ฌด ์ปค์ ธ ํฌ๊ณผ์จ์ด | |
| ๊ณผ๋ํ๊ฒ ๋ญ๊ฐ์ง๋๋ค(halo). | |
| """ | |
| p = int(round(self.patch * self.scale)) | |
| p = max(3, p) | |
| return p if p % 2 == 1 else p + 1 | |
| def estimate_transmission(self, img_f: np.ndarray) -> tuple[np.ndarray, np.ndarray]: | |
| """์ถ์๋ณธ์์ A์ ํฌ๊ณผ์จ t๋ฅผ ์ถ์ ํด (t_full, A) ๋ฐํ. img_f: float32 0~1 ์๋ณธ ํด์๋.""" | |
| h, w = img_f.shape[:2] | |
| if self.scale < 1.0: | |
| small = cv2.resize(img_f, None, fx=self.scale, fy=self.scale, | |
| interpolation=cv2.INTER_AREA) | |
| else: | |
| small = img_f | |
| patch_s = self._patch_for_scale() | |
| dark = dark_channel(small, patch_s) | |
| a = estimate_atmospheric_light(small, dark, self.a_top_ratio, | |
| self.a_max, self.sky_ratio) | |
| # ํ๋ ์ ๊ฐ ํํ โ t ๊ณ์ฐ์ ๋ค์ด๊ฐ๊ธฐ **์ ์** ์ ์ฉํด์ผ | |
| # ํฌ๊ณผ์จ๊น์ง ํจ๊ป ์์ ๋ฉ๋๋ค. | |
| if self.a_smoothing > 0.0: | |
| if self._a_ema is None: | |
| self._a_ema = a.copy() | |
| else: | |
| k = float(np.clip(self.a_smoothing, 0.0, 0.99)) | |
| self._a_ema = k * self._a_ema + (1.0 - k) * a | |
| a = self._a_ema | |
| # t = 1 - omega * darkchannel(I / A) | |
| t_small = 1.0 - self.omega * dark_channel(small / a, patch_s) | |
| if self.use_guided: | |
| gray = cv2.cvtColor((small * 255).astype(np.uint8), cv2.COLOR_BGR2GRAY) | |
| gray = gray.astype(np.float32) / 255.0 | |
| t_small = guided_filter(gray, t_small, self.guided_radius, self.guided_eps) | |
| if self.scale < 1.0: | |
| # ํฌ๊ณผ์จ์ ์ ์ฃผํ ์ ํธ๋ผ ์ ์ํํด๋ ์์ค์ด ๊ฑฐ์ ์์ต๋๋ค. ์ด๊ฒ ์ฑ๋ฆฝํ๋ | |
| # ๋๋ถ์ ์ถ์ ์ถ์ ์ด ์ ๋นํ๋ฉ๋๋ค. | |
| t = cv2.resize(t_small, (w, h), interpolation=cv2.INTER_LINEAR) | |
| else: | |
| t = t_small | |
| return t, a | |
| # ------------------------------------------------------------------ | |
| def process(self, bgr: np.ndarray) -> np.ndarray: | |
| """bgr uint8 -> ๋ํค์ด์ฆ๋ bgr uint8.""" | |
| img_f = bgr.astype(np.float32) / 255.0 | |
| t, a = self.estimate_transmission(img_f) | |
| t = np.clip(t, self.t0, 1.0) | |
| self.last_transmission = t | |
| self.last_a = a | |
| # ๋ณต์์ J = (I - A) / t + A | |
| out = (img_f - a) / t[..., None] + a | |
| return np.clip(out * 255.0, 0, 255).astype(np.uint8) | |
| def process_lowlight( | |
| self, | |
| bgr: np.ndarray, | |
| omega: float = 0.8, | |
| t0: float = 0.25, | |
| ) -> np.ndarray: | |
| """์ ์กฐ๋ ๋ณด์ (์ต์ ). | |
| "์ ์กฐ๋ ์์์ ๋ฐ์ ํ๋ฉด ํค์ด์ฆ ์์๊ณผ ํต๊ณ์ ์ผ๋ก ๋ฎ๋๋ค"๋ ๊ด์ฐฐ | |
| (Dong et al., 2011)์ ์ด์ฉํด **๊ฐ์ DCP ์ฝ๋๋ฅผ ์ฌ์ฌ์ฉ**ํฉ๋๋ค. | |
| ๋ฐ์ -> ๋ํค์ด์ฆ -> ๋ฐ์ | |
| CLAHE๊ฐ ๊ตญ์ ๋๋น๋ฅผ ์ฌ๋ฆฌ๋ ๊ฒ๊ณผ ๋ฌ๋ฆฌ ์ด์ชฝ์ ์ ์ญ ๋ฐ๊ธฐ๋ฅผ ๋์ด์ฌ๋ฆฝ๋๋ค. | |
| โ ๋ฐ์ ์์ญ์ ํ๋ผ๋ฏธํฐ๊ฐ ๋ฌ๋ผ์ผ ํฉ๋๋ค (๋ก์ปฌ ํ ์คํธ๋ก ๋ฐ๊ฒฌํ ํจ์ ). | |
| - `a_max`: ์ฐ๊ธฐ ์์ญ์์๋ ํ์ผ์ ๋๋ฆฌ๋ ๊ฑธ ๋ง์ผ๋ ค 0.92๋ก ์กฐ์์ง๋ง, | |
| ๋ฐ์ ์์์ ์๋ ์ ์ฒด๊ฐ ๋ฐ์ A๊ฐ ์ ๋ง 1.0 ๊ทผ์ฒ์ ๋๋ค. ์ฌ๊ธฐ์ | |
| 0.92๋ฅผ ๊ทธ๋๋ก ์ฐ๋ฉด t๊ฐ ํํ๊น์ง ๋๋ ค ๊ฒฐ๊ณผ๊ฐ **์คํ๋ ค ์ด๋์์ง๋๋ค.** | |
| - `omega`/`t0`: ๋ฐ์ ์์์ 'ํค์ด์ฆ'๊ฐ ํ๋ฉด ์ ์ฒด์ ๊น๋ฆฐ ์ํ๋ผ | |
| ๊ฐ๋๋ฅผ ๋ฎ์ถ๊ณ (0.8) ํํ์ ์ฌ๋ ค์ผ(0.25) ๋ ธ์ด์ฆ๊ฐ ์ ํฐ์ง๋๋ค. | |
| ์กฐ๋ช ์ด ์์ ํ ๊บผ์ง ๊ตฌ๊ฐ์์๋ง ์ผ์ธ์. ๋ ธ์ด์ฆ๊ฐ ๊ฐ์ด ์ฆํญ๋ฉ๋๋ค. | |
| """ | |
| sub = DarkChannelDehazer( | |
| omega=omega, | |
| t0=t0, | |
| patch=self.patch, | |
| scale=self.scale, | |
| use_guided=self.use_guided, | |
| guided_radius=self.guided_radius, | |
| guided_eps=self.guided_eps, | |
| a_top_ratio=self.a_top_ratio, | |
| a_max=1.0, # โ ๋ฐ์ ์์ญ์์๋ ์กฐ์ด์ง ์์ | |
| sky_ratio=1.0, # ๋ฐ์ ๋๋ฉด ์/์๋ ์๋ฏธ๊ฐ ๋ค์งํ ์ ํ์ด ๋ฌด์๋ฏธ | |
| ) | |
| out = 255 - sub.process(255 - bgr) | |
| self.last_transmission = sub.last_transmission | |
| self.last_a = sub.last_a | |
| return out | |
| # ---------------------------------------------------------------- CLAHE | |
| class ClaheEnhancer: | |
| """LAB์ L ์ฑ๋์๋ง CLAHE. | |
| BGR ๊ฐ ์ฑ๋์ ๋ฐ๋ก ๊ฑธ๋ฉด ์ฑ๋๋ณ ํ์คํ ๊ทธ๋จ์ด ์ ๊ฐ๊ฐ ๋์ด๋ **์์ด ํ์ด์ง๋๋ค**. | |
| LAB๋ ๋ฐ๊ธฐ(L)์ ์(a,b)์ด ๋ถ๋ฆฌ๋ผ ์์ด L๋ง ๊ฑด๋๋ฆฌ๋ฉด ์์์ ๋ณด์กด๋ฉ๋๋ค. | |
| """ | |
| def __init__(self, clip_limit: float = 2.0, tile_grid: tuple[int, int] = (8, 8)): | |
| self.clip_limit = clip_limit | |
| self.tile_grid = tuple(tile_grid) | |
| self._clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=self.tile_grid) | |
| def update(self, clip_limit: float, tile_grid) -> None: | |
| self.clip_limit = clip_limit | |
| self.tile_grid = tuple(tile_grid) | |
| self._clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=self.tile_grid) | |
| def process(self, bgr: np.ndarray) -> np.ndarray: | |
| lab = cv2.cvtColor(bgr, cv2.COLOR_BGR2LAB) | |
| lab[:, :, 0] = self._clahe.apply(lab[:, :, 0]) | |
| return cv2.cvtColor(lab, cv2.COLOR_LAB2BGR) | |
| def apply_gamma(bgr: np.ndarray, gamma: float) -> np.ndarray: | |
| """๊ฐ๋ง ๋ณด์ : out = (in/255)^gamma * 255. | |
| **gamma < 1 ์ด๋ฉด ๋ฐ์์ง๊ณ , gamma > 1 ์ด๋ฉด ์ด๋์์ง๋๋ค.** | |
| ๊ฐ๋ง๋ ๋ฌธํ๋ง๋ค ์ง์๋ฅผ ๋ค์ง์ด ์ฐ๋ ๊ฒฝ์ฐ๊ฐ ์์ด(์ด๋ค ์ฝ๋๋ 1/gamma๋ฅผ ์) | |
| ๋ฐฉํฅ์ด ๋ฐ๋๊ฐ ๋๊ธฐ ์ฝ์ต๋๋ค. ์ฌ๊ธฐ์๋ ์ ์์ผ๋ก ๊ณ ์ ํ๊ณ | |
| tests/test_dehaze.py::TestGamma ๊ฐ ๋ฐฉํฅ์ ์ ๊ฐ๋ก๋๋ค. | |
| LUT 256๊ฐ ์กฐํ๋ผ ํด์๋์ ๋ฌด๊ดํ๊ฒ ๋น์ฉ์ด ๊ฑฐ์ 0์ ๋๋ค. | |
| """ | |
| if abs(gamma - 1.0) < 1e-3: | |
| return bgr | |
| g = max(gamma, 1e-3) | |
| lut = np.clip((np.arange(256) / 255.0) ** g * 255.0, 0, 255).astype(np.uint8) | |
| return cv2.LUT(bgr, lut) | |