Add testing framework, example images, and improved filters
Browse files- .gitignore +2 -0
- data/251015/251015_01-1.jpg +3 -0
- data/251015/251015_01.jpg +3 -0
- data/251015/251015_02-1.jpg +3 -0
- data/251015/251015_02.jpg +3 -0
- data/251015/251015_03-1.jpg +3 -0
- data/251015/251015_03.jpg +3 -0
- data/251015/251015_04-1.jpg +3 -0
- data/251015/251015_04.jpg +3 -0
- data/251015/251015_05-1.jpg +3 -0
- data/251015/251015_05.jpg +3 -0
- data/251015/251015_06-1.jpg +3 -0
- data/251015/251015_06.jpg +3 -0
- data/251015/251015_07-1.jpg +3 -0
- data/251015/251015_07.jpg +3 -0
- data/251015/251015_08-1.jpg +3 -0
- data/251015/251015_08.jpg +3 -0
- data/251015/251015_09-1.jpg +3 -0
- data/251015/251015_09.jpg +3 -0
- data/251015/251015_10-1.jpg +3 -0
- data/251015/251015_10.jpg +3 -0
- docs/detection_testing_and_validation.md +931 -0
- docs/measurement_shrimp_detection_strategy.md +400 -0
- docs/rt_detr_smart_filtering_strategy.md +1252 -0
- docs/testing_framework_guide.md +404 -0
- docs/universal_shrimp_detection_strategy.md +883 -0
- interactive_validation.py +332 -0
- requirements.txt +3 -0
- test_quantitative_evaluation.py +324 -0
- test_visual_validation.py +339 -0
.gitignore
CHANGED
|
@@ -89,6 +89,8 @@ data/**/*.bmp
|
|
| 89 |
# ํ์ง๋ง ์ํ/ํ
์คํธ ์ด๋ฏธ์ง๋ ํฌํจ (์์ธ)
|
| 90 |
!data/samples/
|
| 91 |
!data/test/
|
|
|
|
|
|
|
| 92 |
!test_*.jpg
|
| 93 |
!test_*.png
|
| 94 |
!sample_*.jpg
|
|
|
|
| 89 |
# ํ์ง๋ง ์ํ/ํ
์คํธ ์ด๋ฏธ์ง๋ ํฌํจ (์์ธ)
|
| 90 |
!data/samples/
|
| 91 |
!data/test/
|
| 92 |
+
!data/251015/*.jpg
|
| 93 |
+
!data/251015/*.png
|
| 94 |
!test_*.jpg
|
| 95 |
!test_*.png
|
| 96 |
!sample_*.jpg
|
data/251015/251015_01-1.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_01.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_02-1.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_02.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_03-1.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_03.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_04-1.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_04.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_05-1.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_05.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_06-1.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_06.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_07-1.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_07.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_08-1.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_08.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_09-1.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_09.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_10-1.jpg
ADDED
|
Git LFS Details
|
data/251015/251015_10.jpg
ADDED
|
Git LFS Details
|
docs/detection_testing_and_validation.md
ADDED
|
@@ -0,0 +1,931 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐งช ์์ฐ ๊ฒ์ถ ํ
์คํธ ๋ฐ ๊ฒ์ ๊ฐ์ด๋
|
| 2 |
+
|
| 3 |
+
## ๐ ๋ชฉ์ฐจ
|
| 4 |
+
1. [ํ
์คํธ ์ค๋น](#ํ
์คํธ-์ค๋น)
|
| 5 |
+
2. [ํ
์คํธ ์คํ](#ํ
์คํธ-์คํ)
|
| 6 |
+
3. [๊ฒฐ๊ณผ ๊ฒ์ ๋ฐฉ๋ฒ](#๊ฒฐ๊ณผ-๊ฒ์-๋ฐฉ๋ฒ)
|
| 7 |
+
4. [์ฑ๋ฅ ํ๊ฐ](#์ฑ๋ฅ-ํ๊ฐ)
|
| 8 |
+
5. [๊ฐ์ ๋ฐฉํฅ](#๊ฐ์ -๋ฐฉํฅ)
|
| 9 |
+
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
## ๐ฏ ํ
์คํธ ์ค๋น
|
| 13 |
+
|
| 14 |
+
### 1. ํ
์คํธ ๋ฐ์ดํฐ์
๊ตฌ์ฑ
|
| 15 |
+
|
| 16 |
+
#### ํด๋ ๊ตฌ์กฐ
|
| 17 |
+
```
|
| 18 |
+
test_dataset/
|
| 19 |
+
โโโ positive/ # ์์ฐ ์๋ ์ด๋ฏธ์ง
|
| 20 |
+
โ โโโ clean/ # ๋ฐฐ๊ฒฝ ๊นจ๋ (10์ฅ)
|
| 21 |
+
โ โ โโโ shrimp_001.jpg
|
| 22 |
+
โ โ โโโ ...
|
| 23 |
+
โ โโโ with_ruler/ # ์ ํฌํจ (10์ฅ)
|
| 24 |
+
โ โโโ with_hand/ # ์ ํฌํจ (10์ฅ)
|
| 25 |
+
โ โโโ complex_background/ # ๋ณต์กํ ๋ฐฐ๊ฒฝ (10์ฅ)
|
| 26 |
+
โ โโโ various_positions/ # ๋ค์ํ ์์น (10์ฅ)
|
| 27 |
+
โ
|
| 28 |
+
โโโ negative/ # ์์ฐ ์๋ ์ด๋ฏธ์ง (10์ฅ)
|
| 29 |
+
โ โโโ only_ruler.jpg
|
| 30 |
+
โ โโโ only_hand.jpg
|
| 31 |
+
โ โโโ ...
|
| 32 |
+
โ
|
| 33 |
+
โโโ ground_truth/ # ์ ๋ต ๋ผ๋ฒจ
|
| 34 |
+
โโโ annotations.json # ๋ฐ์ด๋ฉ ๋ฐ์ค ์ขํ
|
| 35 |
+
โโโ metadata.csv # ๋ฉํ๋ฐ์ดํฐ
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
#### annotations.json ํ์
|
| 39 |
+
```json
|
| 40 |
+
{
|
| 41 |
+
"images": [
|
| 42 |
+
{
|
| 43 |
+
"file_name": "shrimp_001.jpg",
|
| 44 |
+
"width": 1920,
|
| 45 |
+
"height": 1080,
|
| 46 |
+
"annotations": [
|
| 47 |
+
{
|
| 48 |
+
"bbox": [100, 200, 500, 280],
|
| 49 |
+
"category": "shrimp",
|
| 50 |
+
"difficult": false
|
| 51 |
+
}
|
| 52 |
+
]
|
| 53 |
+
}
|
| 54 |
+
]
|
| 55 |
+
}
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
---
|
| 59 |
+
|
| 60 |
+
## ๐ฌ ํ
์คํธ ์คํ
|
| 61 |
+
|
| 62 |
+
### ๋ฐฉ๋ฒ 1: ์๊ฐ์ ๊ฒ์์ฉ ํ
์คํธ (์ถ์ฒ)
|
| 63 |
+
|
| 64 |
+
**๋ชฉ์ **: ๊ฐ ํํฐ ๋จ๊ณ๋ณ๋ก ์๊ฐ์ ์ผ๋ก ํ์ธ
|
| 65 |
+
|
| 66 |
+
```python
|
| 67 |
+
# test_visual_validation.py
|
| 68 |
+
"""
|
| 69 |
+
์๊ฐ์ ๊ฒ์์ฉ ํ
์คํธ ์คํฌ๋ฆฝํธ
|
| 70 |
+
๊ฐ ํํฐ ๋จ๊ณ๋ณ๋ก ์ค๊ฐ ๊ฒฐ๊ณผ ์ ์ฅ
|
| 71 |
+
"""
|
| 72 |
+
|
| 73 |
+
import cv2
|
| 74 |
+
import os
|
| 75 |
+
import json
|
| 76 |
+
from pathlib import Path
|
| 77 |
+
from universal_shrimp_filter import UniversalShrimpFilter
|
| 78 |
+
|
| 79 |
+
def run_visual_test(test_image_dir, output_dir):
|
| 80 |
+
"""
|
| 81 |
+
์๊ฐ์ ๊ฒ์์ฉ ํ
์คํธ
|
| 82 |
+
|
| 83 |
+
Args:
|
| 84 |
+
test_image_dir: ํ
์คํธ ์ด๋ฏธ์ง ํด๋
|
| 85 |
+
output_dir: ๊ฒฐ๊ณผ ์ ์ฅ ํด๋
|
| 86 |
+
"""
|
| 87 |
+
|
| 88 |
+
# ์ถ๋ ฅ ํด๋ ์์ฑ
|
| 89 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 90 |
+
|
| 91 |
+
# ํํฐ ์ด๊ธฐํ
|
| 92 |
+
shrimp_filter = UniversalShrimpFilter()
|
| 93 |
+
|
| 94 |
+
# ๋ชจ๋ ํ
์คํธ ์ด๋ฏธ์ง ์ฒ๋ฆฌ
|
| 95 |
+
test_images = list(Path(test_image_dir).glob("*.jpg")) + \
|
| 96 |
+
list(Path(test_image_dir).glob("*.png"))
|
| 97 |
+
|
| 98 |
+
results = []
|
| 99 |
+
|
| 100 |
+
for img_path in test_images:
|
| 101 |
+
print(f"\n{'='*60}")
|
| 102 |
+
print(f"Testing: {img_path.name}")
|
| 103 |
+
print(f"{'='*60}")
|
| 104 |
+
|
| 105 |
+
# ์ด๋ฏธ์ง ๋ก๋
|
| 106 |
+
image = cv2.imread(str(img_path))
|
| 107 |
+
if image is None:
|
| 108 |
+
print(f"โ ๏ธ Failed to load image: {img_path}")
|
| 109 |
+
continue
|
| 110 |
+
|
| 111 |
+
# RT-DETR ๊ฒ์ถ (์ค์ ๋ก๋ ๋ชจ๋ธ ์คํ, ์ฌ๊ธฐ์๋ ์์)
|
| 112 |
+
rtdetr_detections = run_rtdetr(image) # ๋ณ๋ ํจ์
|
| 113 |
+
|
| 114 |
+
print(f"RT-DETR detected: {len(rtdetr_detections)} objects")
|
| 115 |
+
|
| 116 |
+
# ํํฐ๋ง ์ ์ฉ (๋จ๊ณ๋ณ ์ ์ฅ)
|
| 117 |
+
filtered_detections = shrimp_filter.filter_with_stages(
|
| 118 |
+
image, rtdetr_detections, save_stages=True
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
# ๊ฒฐ๊ณผ ์๊ฐํ
|
| 122 |
+
result_image = visualize_detections(image, filtered_detections)
|
| 123 |
+
|
| 124 |
+
# ๊ฒฐ๊ณผ ์ ์ฅ
|
| 125 |
+
output_path = os.path.join(output_dir, f"result_{img_path.name}")
|
| 126 |
+
cv2.imwrite(output_path, result_image)
|
| 127 |
+
|
| 128 |
+
# ๊ฒฐ๊ณผ ๊ธฐ๋ก
|
| 129 |
+
results.append({
|
| 130 |
+
'image': img_path.name,
|
| 131 |
+
'rtdetr_count': len(rtdetr_detections),
|
| 132 |
+
'filtered_count': len(filtered_detections),
|
| 133 |
+
'detections': filtered_detections
|
| 134 |
+
})
|
| 135 |
+
|
| 136 |
+
print(f"โ
Final detections: {len(filtered_detections)}")
|
| 137 |
+
for i, det in enumerate(filtered_detections, 1):
|
| 138 |
+
print(f" #{i}: score={det.get('total_score', 0):.1f}/100, "
|
| 139 |
+
f"confidence={det['confidence']:.2f}")
|
| 140 |
+
|
| 141 |
+
# ๊ฒฐ๊ณผ ์์ฝ ์ ์ฅ
|
| 142 |
+
with open(os.path.join(output_dir, 'test_results.json'), 'w') as f:
|
| 143 |
+
json.dump(results, f, indent=2)
|
| 144 |
+
|
| 145 |
+
print(f"\n{'='*60}")
|
| 146 |
+
print(f"โ
Test completed! Results saved to: {output_dir}")
|
| 147 |
+
print(f"{'='*60}")
|
| 148 |
+
|
| 149 |
+
return results
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def visualize_detections(image, detections):
|
| 153 |
+
"""
|
| 154 |
+
๊ฒ์ถ ๊ฒฐ๊ณผ ์๊ฐํ
|
| 155 |
+
|
| 156 |
+
Args:
|
| 157 |
+
image: ์๋ณธ ์ด๋ฏธ์ง
|
| 158 |
+
detections: ๊ฒ์ถ ๊ฒฐ๊ณผ
|
| 159 |
+
|
| 160 |
+
Returns:
|
| 161 |
+
์๊ฐํ๋ ์ด๋ฏธ์ง
|
| 162 |
+
"""
|
| 163 |
+
import cv2
|
| 164 |
+
import numpy as np
|
| 165 |
+
|
| 166 |
+
result = image.copy()
|
| 167 |
+
|
| 168 |
+
for i, det in enumerate(detections, 1):
|
| 169 |
+
bbox = det['bbox']
|
| 170 |
+
x1, y1, x2, y2 = map(int, bbox)
|
| 171 |
+
|
| 172 |
+
# ๋ฐ์ด๋ฉ ๋ฐ์ค
|
| 173 |
+
color = (0, 255, 0) # ๋
น์
|
| 174 |
+
cv2.rectangle(result, (x1, y1), (x2, y2), color, 3)
|
| 175 |
+
|
| 176 |
+
# ๋ผ๋ฒจ
|
| 177 |
+
score = det.get('total_score', 0)
|
| 178 |
+
confidence = det['confidence']
|
| 179 |
+
label = f"#{i} Score:{score:.1f} Conf:{confidence:.2f}"
|
| 180 |
+
|
| 181 |
+
# ๋ผ๋ฒจ ๋ฐฐ๊ฒฝ
|
| 182 |
+
(label_w, label_h), _ = cv2.getTextSize(
|
| 183 |
+
label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2
|
| 184 |
+
)
|
| 185 |
+
cv2.rectangle(result, (x1, y1 - label_h - 10),
|
| 186 |
+
(x1 + label_w, y1), color, -1)
|
| 187 |
+
|
| 188 |
+
# ๋ผ๋ฒจ ํ
์คํธ
|
| 189 |
+
cv2.putText(result, label, (x1, y1 - 5),
|
| 190 |
+
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 0), 2)
|
| 191 |
+
|
| 192 |
+
# ์ ์ฒด ์์ฝ (์๋จ)
|
| 193 |
+
summary = f"Detections: {len(detections)}"
|
| 194 |
+
cv2.putText(result, summary, (10, 30),
|
| 195 |
+
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), 3)
|
| 196 |
+
|
| 197 |
+
return result
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def run_rtdetr(image):
|
| 201 |
+
"""
|
| 202 |
+
RT-DETR ๊ฒ์ถ ์คํ
|
| 203 |
+
|
| 204 |
+
Args:
|
| 205 |
+
image: ์
๋ ฅ ์ด๋ฏธ์ง
|
| 206 |
+
|
| 207 |
+
Returns:
|
| 208 |
+
๊ฒ์ถ ๊ฒฐ๊ณผ ๋ฆฌ์คํธ
|
| 209 |
+
"""
|
| 210 |
+
from transformers import RTDetrForObjectDetection, RTDetrImageProcessor
|
| 211 |
+
import torch
|
| 212 |
+
from PIL import Image
|
| 213 |
+
|
| 214 |
+
# ๋ชจ๋ธ ๋ก๋ (์บ์)
|
| 215 |
+
if not hasattr(run_rtdetr, 'model'):
|
| 216 |
+
processor = RTDetrImageProcessor.from_pretrained("PekingU/rtdetr_r50vd_coco_o365")
|
| 217 |
+
model = RTDetrForObjectDetection.from_pretrained("PekingU/rtdetr_r50vd_coco_o365")
|
| 218 |
+
model.eval()
|
| 219 |
+
run_rtdetr.processor = processor
|
| 220 |
+
run_rtdetr.model = model
|
| 221 |
+
|
| 222 |
+
processor = run_rtdetr.processor
|
| 223 |
+
model = run_rtdetr.model
|
| 224 |
+
|
| 225 |
+
# ์ด๋ฏธ์ง ๋ณํ
|
| 226 |
+
pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
|
| 227 |
+
|
| 228 |
+
# ์ถ๋ก
|
| 229 |
+
inputs = processor(images=pil_image, return_tensors="pt")
|
| 230 |
+
with torch.no_grad():
|
| 231 |
+
outputs = model(**inputs)
|
| 232 |
+
|
| 233 |
+
# ํ์ฒ๋ฆฌ
|
| 234 |
+
target_sizes = torch.tensor([pil_image.size[::-1]])
|
| 235 |
+
results = processor.post_process_object_detection(
|
| 236 |
+
outputs,
|
| 237 |
+
target_sizes=target_sizes,
|
| 238 |
+
threshold=0.3
|
| 239 |
+
)[0]
|
| 240 |
+
|
| 241 |
+
# ๊ฒฐ๊ณผ ๋ณํ
|
| 242 |
+
detections = []
|
| 243 |
+
for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
|
| 244 |
+
detections.append({
|
| 245 |
+
'bbox': box.tolist(),
|
| 246 |
+
'confidence': score.item(),
|
| 247 |
+
'class_id': label.item()
|
| 248 |
+
})
|
| 249 |
+
|
| 250 |
+
return detections
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
# ์คํ
|
| 254 |
+
if __name__ == "__main__":
|
| 255 |
+
run_visual_test(
|
| 256 |
+
test_image_dir="test_dataset/positive/clean",
|
| 257 |
+
output_dir="test_results/visual"
|
| 258 |
+
)
|
| 259 |
+
```
|
| 260 |
+
|
| 261 |
+
---
|
| 262 |
+
|
| 263 |
+
### ๋ฐฉ๋ฒ 2: ์ ๋์ ํ๊ฐ์ฉ ํ
์คํธ
|
| 264 |
+
|
| 265 |
+
**๋ชฉ์ **: Precision, Recall, F1 ๊ณ์ฐ
|
| 266 |
+
|
| 267 |
+
```python
|
| 268 |
+
# test_quantitative_evaluation.py
|
| 269 |
+
"""
|
| 270 |
+
์ ๋์ ํ๊ฐ์ฉ ํ
์คํธ ์คํฌ๋ฆฝํธ
|
| 271 |
+
Ground Truth์ ๋น๊ตํ์ฌ ๋ฉํธ๋ฆญ ๊ณ์ฐ
|
| 272 |
+
"""
|
| 273 |
+
|
| 274 |
+
import json
|
| 275 |
+
import numpy as np
|
| 276 |
+
from pathlib import Path
|
| 277 |
+
import cv2
|
| 278 |
+
|
| 279 |
+
def calculate_iou(bbox1, bbox2):
|
| 280 |
+
"""IoU ๊ณ์ฐ"""
|
| 281 |
+
x1_min, y1_min, x1_max, y1_max = bbox1
|
| 282 |
+
x2_min, y2_min, x2_max, y2_max = bbox2
|
| 283 |
+
|
| 284 |
+
# ๊ต์งํฉ
|
| 285 |
+
inter_x_min = max(x1_min, x2_min)
|
| 286 |
+
inter_y_min = max(y1_min, y2_min)
|
| 287 |
+
inter_x_max = min(x1_max, x2_max)
|
| 288 |
+
inter_y_max = min(y1_max, y2_max)
|
| 289 |
+
|
| 290 |
+
if inter_x_max < inter_x_min or inter_y_max < inter_y_min:
|
| 291 |
+
return 0.0
|
| 292 |
+
|
| 293 |
+
inter_area = (inter_x_max - inter_x_min) * (inter_y_max - inter_y_min)
|
| 294 |
+
|
| 295 |
+
# ํฉ์งํฉ
|
| 296 |
+
area1 = (x1_max - x1_min) * (y1_max - y1_min)
|
| 297 |
+
area2 = (x2_max - x2_min) * (y2_max - y2_min)
|
| 298 |
+
union_area = area1 + area2 - inter_area
|
| 299 |
+
|
| 300 |
+
return inter_area / union_area if union_area > 0 else 0.0
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
def evaluate_detection(predictions, ground_truths, iou_threshold=0.5):
|
| 304 |
+
"""
|
| 305 |
+
๊ฒ์ถ ์ฑ๋ฅ ํ๊ฐ
|
| 306 |
+
|
| 307 |
+
Args:
|
| 308 |
+
predictions: ์์ธก ๋ฐ์ด๋ฉ ๋ฐ์ค ๋ฆฌ์คํธ
|
| 309 |
+
ground_truths: ์ ๋ต ๋ฐ์ด๋ฉ ๋ฐ์ค ๋ฆฌ์คํธ
|
| 310 |
+
iou_threshold: IoU ์๊ณ๊ฐ (๊ธฐ๋ณธ 0.5)
|
| 311 |
+
|
| 312 |
+
Returns:
|
| 313 |
+
dict: TP, FP, FN, Precision, Recall, F1
|
| 314 |
+
"""
|
| 315 |
+
|
| 316 |
+
TP = 0 # True Positive
|
| 317 |
+
FP = 0 # False Positive
|
| 318 |
+
FN = 0 # False Negative
|
| 319 |
+
|
| 320 |
+
matched_gts = set()
|
| 321 |
+
|
| 322 |
+
# ๊ฐ ์์ธก์ ๋ํด
|
| 323 |
+
for pred in predictions:
|
| 324 |
+
pred_bbox = pred['bbox']
|
| 325 |
+
|
| 326 |
+
# Ground Truth์ ๋งค์นญ
|
| 327 |
+
max_iou = 0
|
| 328 |
+
max_iou_gt_idx = -1
|
| 329 |
+
|
| 330 |
+
for gt_idx, gt in enumerate(ground_truths):
|
| 331 |
+
if gt_idx in matched_gts:
|
| 332 |
+
continue
|
| 333 |
+
|
| 334 |
+
gt_bbox = gt['bbox']
|
| 335 |
+
iou = calculate_iou(pred_bbox, gt_bbox)
|
| 336 |
+
|
| 337 |
+
if iou > max_iou:
|
| 338 |
+
max_iou = iou
|
| 339 |
+
max_iou_gt_idx = gt_idx
|
| 340 |
+
|
| 341 |
+
# IoU ์๊ณ๊ฐ ์ด์์ด๋ฉด ๋งค์นญ ์ฑ๊ณต
|
| 342 |
+
if max_iou >= iou_threshold:
|
| 343 |
+
TP += 1
|
| 344 |
+
matched_gts.add(max_iou_gt_idx)
|
| 345 |
+
else:
|
| 346 |
+
FP += 1 # ์๋ชป๋ ๊ฒ์ถ
|
| 347 |
+
|
| 348 |
+
# ๋งค์นญ ์ ๋ GT = ๋์น ๊ฒ
|
| 349 |
+
FN = len(ground_truths) - len(matched_gts)
|
| 350 |
+
|
| 351 |
+
# ๋ฉํธ๋ฆญ ๊ณ์ฐ
|
| 352 |
+
precision = TP / (TP + FP) if (TP + FP) > 0 else 0
|
| 353 |
+
recall = TP / (TP + FN) if (TP + FN) > 0 else 0
|
| 354 |
+
f1_score = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
|
| 355 |
+
|
| 356 |
+
return {
|
| 357 |
+
'TP': TP,
|
| 358 |
+
'FP': FP,
|
| 359 |
+
'FN': FN,
|
| 360 |
+
'precision': precision,
|
| 361 |
+
'recall': recall,
|
| 362 |
+
'f1_score': f1_score
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
def run_quantitative_test(test_image_dir, ground_truth_file, output_file):
|
| 367 |
+
"""
|
| 368 |
+
์ ๋์ ํ๊ฐ ์คํ
|
| 369 |
+
|
| 370 |
+
Args:
|
| 371 |
+
test_image_dir: ํ
์คํธ ์ด๋ฏธ์ง ํด๋
|
| 372 |
+
ground_truth_file: Ground Truth JSON ํ์ผ
|
| 373 |
+
output_file: ๊ฒฐ๊ณผ ์ ์ฅ ํ์ผ
|
| 374 |
+
"""
|
| 375 |
+
|
| 376 |
+
# Ground Truth ๋ก๋
|
| 377 |
+
with open(ground_truth_file, 'r') as f:
|
| 378 |
+
ground_truth_data = json.load(f)
|
| 379 |
+
|
| 380 |
+
# ์ด๋ฏธ์ง๋ณ GT ๋งคํ
|
| 381 |
+
gt_map = {}
|
| 382 |
+
for img_data in ground_truth_data['images']:
|
| 383 |
+
gt_map[img_data['file_name']] = img_data['annotations']
|
| 384 |
+
|
| 385 |
+
# ํํฐ ์ด๊ธฐํ
|
| 386 |
+
from universal_shrimp_filter import UniversalShrimpFilter
|
| 387 |
+
shrimp_filter = UniversalShrimpFilter()
|
| 388 |
+
|
| 389 |
+
# ์ ์ฒด ๊ฒฐ๊ณผ
|
| 390 |
+
all_results = []
|
| 391 |
+
total_metrics = {
|
| 392 |
+
'TP': 0,
|
| 393 |
+
'FP': 0,
|
| 394 |
+
'FN': 0
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
# ๊ฐ ์ด๋ฏธ์ง ํ
์คํธ
|
| 398 |
+
for img_name, gt_annotations in gt_map.items():
|
| 399 |
+
img_path = Path(test_image_dir) / img_name
|
| 400 |
+
if not img_path.exists():
|
| 401 |
+
print(f"โ ๏ธ Image not found: {img_path}")
|
| 402 |
+
continue
|
| 403 |
+
|
| 404 |
+
print(f"\nTesting: {img_name}")
|
| 405 |
+
|
| 406 |
+
# ์ด๋ฏธ์ง ๋ก๋
|
| 407 |
+
image = cv2.imread(str(img_path))
|
| 408 |
+
|
| 409 |
+
# RT-DETR ๊ฒ์ถ
|
| 410 |
+
rtdetr_detections = run_rtdetr(image)
|
| 411 |
+
|
| 412 |
+
# ํํฐ๋ง
|
| 413 |
+
predictions = shrimp_filter.filter(image, rtdetr_detections)
|
| 414 |
+
|
| 415 |
+
# Ground Truth
|
| 416 |
+
ground_truths = gt_annotations
|
| 417 |
+
|
| 418 |
+
# ํ๊ฐ
|
| 419 |
+
metrics = evaluate_detection(predictions, ground_truths, iou_threshold=0.5)
|
| 420 |
+
|
| 421 |
+
# ๊ฒฐ๊ณผ ์ ์ฅ
|
| 422 |
+
result = {
|
| 423 |
+
'image': img_name,
|
| 424 |
+
'num_predictions': len(predictions),
|
| 425 |
+
'num_ground_truths': len(ground_truths),
|
| 426 |
+
'metrics': metrics
|
| 427 |
+
}
|
| 428 |
+
all_results.append(result)
|
| 429 |
+
|
| 430 |
+
# ์ ์ฒด ์ง๊ณ
|
| 431 |
+
total_metrics['TP'] += metrics['TP']
|
| 432 |
+
total_metrics['FP'] += metrics['FP']
|
| 433 |
+
total_metrics['FN'] += metrics['FN']
|
| 434 |
+
|
| 435 |
+
print(f" GT: {len(ground_truths)}, Pred: {len(predictions)}")
|
| 436 |
+
print(f" TP={metrics['TP']}, FP={metrics['FP']}, FN={metrics['FN']}")
|
| 437 |
+
print(f" Precision={metrics['precision']:.2%}, Recall={metrics['recall']:.2%}, F1={metrics['f1_score']:.2%}")
|
| 438 |
+
|
| 439 |
+
# ์ ์ฒด ๋ฉํธ๋ฆญ ๊ณ์ฐ
|
| 440 |
+
total_precision = total_metrics['TP'] / (total_metrics['TP'] + total_metrics['FP']) \
|
| 441 |
+
if (total_metrics['TP'] + total_metrics['FP']) > 0 else 0
|
| 442 |
+
total_recall = total_metrics['TP'] / (total_metrics['TP'] + total_metrics['FN']) \
|
| 443 |
+
if (total_metrics['TP'] + total_metrics['FN']) > 0 else 0
|
| 444 |
+
total_f1 = 2 * (total_precision * total_recall) / (total_precision + total_recall) \
|
| 445 |
+
if (total_precision + total_recall) > 0 else 0
|
| 446 |
+
|
| 447 |
+
# ์์ฝ
|
| 448 |
+
summary = {
|
| 449 |
+
'total_images': len(all_results),
|
| 450 |
+
'total_metrics': {
|
| 451 |
+
'TP': total_metrics['TP'],
|
| 452 |
+
'FP': total_metrics['FP'],
|
| 453 |
+
'FN': total_metrics['FN'],
|
| 454 |
+
'precision': total_precision,
|
| 455 |
+
'recall': total_recall,
|
| 456 |
+
'f1_score': total_f1
|
| 457 |
+
},
|
| 458 |
+
'per_image_results': all_results
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
# ๊ฒฐ๊ณผ ์ ์ฅ
|
| 462 |
+
with open(output_file, 'w') as f:
|
| 463 |
+
json.dump(summary, f, indent=2)
|
| 464 |
+
|
| 465 |
+
# ์ถ๋ ฅ
|
| 466 |
+
print(f"\n{'='*60}")
|
| 467 |
+
print("๐ Overall Performance")
|
| 468 |
+
print(f"{'='*60}")
|
| 469 |
+
print(f"Total Images: {len(all_results)}")
|
| 470 |
+
print(f"TP: {total_metrics['TP']}, FP: {total_metrics['FP']}, FN: {total_metrics['FN']}")
|
| 471 |
+
print(f"Precision: {total_precision:.2%}")
|
| 472 |
+
print(f"Recall: {total_recall:.2%}")
|
| 473 |
+
print(f"F1 Score: {total_f1:.2%}")
|
| 474 |
+
print(f"{'='*60}")
|
| 475 |
+
print(f"Results saved to: {output_file}")
|
| 476 |
+
|
| 477 |
+
return summary
|
| 478 |
+
|
| 479 |
+
|
| 480 |
+
# ์คํ
|
| 481 |
+
if __name__ == "__main__":
|
| 482 |
+
run_quantitative_test(
|
| 483 |
+
test_image_dir="test_dataset/positive/clean",
|
| 484 |
+
ground_truth_file="test_dataset/ground_truth/annotations.json",
|
| 485 |
+
output_file="test_results/quantitative_results.json"
|
| 486 |
+
)
|
| 487 |
+
```
|
| 488 |
+
|
| 489 |
+
---
|
| 490 |
+
|
| 491 |
+
## ๐ ๊ฒฐ๊ณผ ๊ฒ์ ๋ฐฉ๋ฒ
|
| 492 |
+
|
| 493 |
+
### ๋ฐฉ๋ฒ 1: ์๊ฐ์ ๊ฒ์ (๋น ๋ฅธ ํ์ธ)
|
| 494 |
+
|
| 495 |
+
#### ์ ์ฐจ
|
| 496 |
+
1. **ํ
์คํธ ์คํ**
|
| 497 |
+
```bash
|
| 498 |
+
python test_visual_validation.py
|
| 499 |
+
```
|
| 500 |
+
|
| 501 |
+
2. **๊ฒฐ๊ณผ ์ด๋ฏธ์ง ํ์ธ**
|
| 502 |
+
```
|
| 503 |
+
test_results/visual/
|
| 504 |
+
โโโ result_shrimp_001.jpg โ
์์ฐ ๊ฒ์ถ๋จ (๋
น์ ๋ฐ์ค)
|
| 505 |
+
โโโ result_shrimp_002.jpg โ ๊ฒ์ถ ์คํจ
|
| 506 |
+
โโโ ...
|
| 507 |
+
```
|
| 508 |
+
|
| 509 |
+
3. **์ก์ ๊ฒ์**
|
| 510 |
+
- โ
**์ ์**: ์์ฐ์๋ง ๋ฐ์ค, ๋ค๋ฅธ ๊ฐ์ฒด ์ ์ธ
|
| 511 |
+
- โ ๏ธ **๊ณผ์ ๊ฒ์ถ**: ์์ฐ ๋์นจ โ Recall ๋ฎ์
|
| 512 |
+
- โ ๏ธ **๊ณผ๋ค ๊ฒ์ถ**: ์/์ ๋ฑ์๋ ๋ฐ์ค โ Precision ๋ฎ์
|
| 513 |
+
|
| 514 |
+
4. **๊ฒ์ ์ํธ ์์ฑ**
|
| 515 |
+
```csv
|
| 516 |
+
์ด๋ฏธ์ง๋ช
,GT๊ฐ์,๊ฒ์ถ๊ฐ์,TP,FP,FN,๋น๊ณ
|
| 517 |
+
shrimp_001.jpg,1,1,1,0,0,์ ์
|
| 518 |
+
shrimp_002.jpg,1,0,0,0,1,๊ฒ์ถ์คํจ
|
| 519 |
+
shrimp_003.jpg,1,2,1,1,0,์๋๊ฒ์ถ๋จ
|
| 520 |
+
```
|
| 521 |
+
|
| 522 |
+
---
|
| 523 |
+
|
| 524 |
+
### ๋ฐฉ๋ฒ 2: ์ ๋์ ๊ฒ์ (์ ํํ ํ๊ฐ)
|
| 525 |
+
|
| 526 |
+
#### ์ ์ฐจ
|
| 527 |
+
1. **Ground Truth ์ค๋น**
|
| 528 |
+
- LabelImg, CVAT ๋ฑ์ผ๋ก ์๋ ๋ผ๋ฒจ๋ง
|
| 529 |
+
- JSON ํ์์ผ๋ก ์ ์ฅ
|
| 530 |
+
|
| 531 |
+
2. **ํ
์คํธ ์คํ**
|
| 532 |
+
```bash
|
| 533 |
+
python test_quantitative_evaluation.py
|
| 534 |
+
```
|
| 535 |
+
|
| 536 |
+
3. **๊ฒฐ๊ณผ ๋ถ์**
|
| 537 |
+
```json
|
| 538 |
+
{
|
| 539 |
+
"total_metrics": {
|
| 540 |
+
"TP": 42,
|
| 541 |
+
"FP": 8,
|
| 542 |
+
"FN": 6,
|
| 543 |
+
"precision": 0.84,
|
| 544 |
+
"recall": 0.88,
|
| 545 |
+
"f1_score": 0.86
|
| 546 |
+
}
|
| 547 |
+
}
|
| 548 |
+
```
|
| 549 |
+
|
| 550 |
+
4. **ํด์**
|
| 551 |
+
- **Precision 84%**: ๊ฒ์ถํ ๊ฒ ์ค 84%๊ฐ ์ค์ ์์ฐ
|
| 552 |
+
- 16%๋ ์ค๊ฒ์ถ (์, ์ ๋ฑ)
|
| 553 |
+
- **Recall 88%**: ์ ์ฒด ์์ฐ ์ค 88% ๊ฒ์ถ
|
| 554 |
+
- 12%๋ ๋์นจ
|
| 555 |
+
- **F1 86%**: ์ข
ํฉ ์ฑ๋ฅ
|
| 556 |
+
|
| 557 |
+
---
|
| 558 |
+
|
| 559 |
+
### ๋ฐฉ๋ฒ 3: ๋ํํ ๊ฒ์ (Gradio ํ์ฉ)
|
| 560 |
+
|
| 561 |
+
```python
|
| 562 |
+
# interactive_validation.py
|
| 563 |
+
"""
|
| 564 |
+
๋ํํ ๊ฒ์ ๋๊ตฌ
|
| 565 |
+
์ฌ์ฉ์๊ฐ ์ง์ ์ด๋ฏธ์ง ์
๋ก๋ํ๊ณ ๊ฒฐ๊ณผ ํ์ธ
|
| 566 |
+
"""
|
| 567 |
+
|
| 568 |
+
import gradio as gr
|
| 569 |
+
import cv2
|
| 570 |
+
import numpy as np
|
| 571 |
+
from PIL import Image
|
| 572 |
+
|
| 573 |
+
def detect_and_validate(image, confidence_threshold):
|
| 574 |
+
"""
|
| 575 |
+
๊ฒ์ถ ๋ฐ ๊ฒฐ๊ณผ ๋ฐํ
|
| 576 |
+
|
| 577 |
+
Args:
|
| 578 |
+
image: PIL Image
|
| 579 |
+
confidence_threshold: ์ ๋ขฐ๋ ์๊ณ๊ฐ
|
| 580 |
+
|
| 581 |
+
Returns:
|
| 582 |
+
(๊ฒฐ๊ณผ ์ด๋ฏธ์ง, ์์ธ ์ ๋ณด)
|
| 583 |
+
"""
|
| 584 |
+
# numpy๋ก ๋ณํ
|
| 585 |
+
img_array = np.array(image)
|
| 586 |
+
img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
|
| 587 |
+
|
| 588 |
+
# RT-DETR ๊ฒ์ถ
|
| 589 |
+
rtdetr_detections = run_rtdetr(img_bgr)
|
| 590 |
+
|
| 591 |
+
# ํํฐ๋ง
|
| 592 |
+
from universal_shrimp_filter import UniversalShrimpFilter
|
| 593 |
+
shrimp_filter = UniversalShrimpFilter()
|
| 594 |
+
filtered_detections = shrimp_filter.filter(img_bgr, rtdetr_detections)
|
| 595 |
+
|
| 596 |
+
# ์๊ฐํ
|
| 597 |
+
result_img = visualize_detections(img_bgr, filtered_detections)
|
| 598 |
+
result_img_rgb = cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB)
|
| 599 |
+
|
| 600 |
+
# ์์ธ ์ ๋ณด
|
| 601 |
+
info = f"""
|
| 602 |
+
### ๐ ๊ฒ์ถ ๊ฒฐ๊ณผ
|
| 603 |
+
|
| 604 |
+
- **RT-DETR ๊ฒ์ถ**: {len(rtdetr_detections)}๊ฐ
|
| 605 |
+
- **ํํฐ๋ง ํ**: {len(filtered_detections)}๊ฐ (์์ฐ๋ง)
|
| 606 |
+
|
| 607 |
+
#### ์์ธ ์ ๋ณด:
|
| 608 |
+
"""
|
| 609 |
+
|
| 610 |
+
for i, det in enumerate(filtered_detections, 1):
|
| 611 |
+
score = det.get('total_score', 0)
|
| 612 |
+
conf = det['confidence']
|
| 613 |
+
bbox = det['bbox']
|
| 614 |
+
|
| 615 |
+
info += f"""
|
| 616 |
+
**์์ฐ #{i}**
|
| 617 |
+
- ์ข
ํฉ ์ ์: {score:.1f}/100
|
| 618 |
+
- RT-DETR ์ ๋ขฐ๋: {conf:.2%}
|
| 619 |
+
- ์์น: ({bbox[0]:.0f}, {bbox[1]:.0f}) - ({bbox[2]:.0f}, {bbox[3]:.0f})
|
| 620 |
+
"""
|
| 621 |
+
|
| 622 |
+
if len(filtered_detections) == 0:
|
| 623 |
+
info += "\nโ ๏ธ **์์ฐ๊ฐ ๊ฒ์ถ๋์ง ์์์ต๋๋ค.**"
|
| 624 |
+
|
| 625 |
+
return Image.fromarray(result_img_rgb), info
|
| 626 |
+
|
| 627 |
+
|
| 628 |
+
# Gradio ์ธํฐํ์ด์ค
|
| 629 |
+
with gr.Blocks(title="์์ฐ ๊ฒ์ถ ๊ฒ์ ๋๊ตฌ") as demo:
|
| 630 |
+
gr.Markdown("""
|
| 631 |
+
# ๐ฆ ์์ฐ ๊ฒ์ถ ๊ฒ์ ๋๊ตฌ
|
| 632 |
+
|
| 633 |
+
์ด๋ฏธ์ง๋ฅผ ์
๋ก๋ํ๋ฉด ์๋์ผ๋ก ์์ฐ๋ฅผ ๊ฒ์ถํฉ๋๋ค.
|
| 634 |
+
๊ฒฐ๊ณผ๋ฅผ ํ์ธํ๊ณ ๊ฒ์ถ ํ์ง์ ํ๊ฐํ์ธ์.
|
| 635 |
+
""")
|
| 636 |
+
|
| 637 |
+
with gr.Row():
|
| 638 |
+
with gr.Column():
|
| 639 |
+
input_image = gr.Image(label="ํ
์คํธ ์ด๋ฏธ์ง", type="pil")
|
| 640 |
+
confidence_slider = gr.Slider(
|
| 641 |
+
0.1, 0.9, 0.3,
|
| 642 |
+
label="RT-DETR ์ ๋ขฐ๋ ์๊ณ๊ฐ"
|
| 643 |
+
)
|
| 644 |
+
detect_btn = gr.Button("๐ ๊ฒ์ถ ์คํ", variant="primary")
|
| 645 |
+
|
| 646 |
+
with gr.Column():
|
| 647 |
+
output_image = gr.Image(label="๊ฒ์ถ ๊ฒฐ๊ณผ")
|
| 648 |
+
output_info = gr.Markdown()
|
| 649 |
+
|
| 650 |
+
# ์ด๋ฒคํธ
|
| 651 |
+
detect_btn.click(
|
| 652 |
+
detect_and_validate,
|
| 653 |
+
[input_image, confidence_slider],
|
| 654 |
+
[output_image, output_info]
|
| 655 |
+
)
|
| 656 |
+
|
| 657 |
+
gr.Markdown("""
|
| 658 |
+
---
|
| 659 |
+
### โ
๊ฒ์ ๊ธฐ์ค
|
| 660 |
+
|
| 661 |
+
**์ ์ ๊ฒ์ถ**:
|
| 662 |
+
- ์์ฐ์๋ง ๋
น์ ๋ฐ์ค
|
| 663 |
+
- ์, ์, ๋ฐฐ๊ฒฝ ๋ฑ์ ์ ์ธ
|
| 664 |
+
|
| 665 |
+
**๋ฌธ์ ์์**:
|
| 666 |
+
- ์์ฐ ๋์นจ โ Recall ๋ฎ์ (ํ๋ผ๋ฏธํฐ ์ํ ํ์)
|
| 667 |
+
- ์/์๋ ๊ฒ์ถ โ Precision ๋ฎ์ (ํ๋ผ๋ฏธํฐ ๊ฐํ ํ์)
|
| 668 |
+
""")
|
| 669 |
+
|
| 670 |
+
|
| 671 |
+
if __name__ == "__main__":
|
| 672 |
+
demo.launch(server_port=7862)
|
| 673 |
+
```
|
| 674 |
+
|
| 675 |
+
---
|
| 676 |
+
|
| 677 |
+
## ๐ ์ฑ๋ฅ ๋ถ์ ๋๊ตฌ
|
| 678 |
+
|
| 679 |
+
### ํผ๋ ํ๋ ฌ (Confusion Matrix) ์์ฑ
|
| 680 |
+
|
| 681 |
+
```python
|
| 682 |
+
def generate_confusion_matrix(test_results):
|
| 683 |
+
"""
|
| 684 |
+
ํผ๋ ํ๋ ฌ ์์ฑ ๋ฐ ์๊ฐํ
|
| 685 |
+
|
| 686 |
+
Args:
|
| 687 |
+
test_results: ํ
์คํธ ๊ฒฐ๊ณผ ๋ฆฌ์คํธ
|
| 688 |
+
"""
|
| 689 |
+
import matplotlib.pyplot as plt
|
| 690 |
+
import seaborn as sns
|
| 691 |
+
|
| 692 |
+
# ์ง๊ณ
|
| 693 |
+
TP = sum(r['metrics']['TP'] for r in test_results)
|
| 694 |
+
FP = sum(r['metrics']['FP'] for r in test_results)
|
| 695 |
+
TN = 0 # ์์ฐ ์๋ ์ด๋ฏธ์ง์์ ๊ฒ์ถ ์ ํจ
|
| 696 |
+
FN = sum(r['metrics']['FN'] for r in test_results)
|
| 697 |
+
|
| 698 |
+
# ํผ๋ ํ๋ ฌ
|
| 699 |
+
cm = np.array([[TP, FP], [FN, TN]])
|
| 700 |
+
|
| 701 |
+
# ์๊ฐํ
|
| 702 |
+
plt.figure(figsize=(8, 6))
|
| 703 |
+
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
|
| 704 |
+
xticklabels=['Predicted Positive', 'Predicted Negative'],
|
| 705 |
+
yticklabels=['Actual Positive', 'Actual Negative'])
|
| 706 |
+
plt.title('Confusion Matrix')
|
| 707 |
+
plt.ylabel('Actual')
|
| 708 |
+
plt.xlabel('Predicted')
|
| 709 |
+
plt.tight_layout()
|
| 710 |
+
plt.savefig('confusion_matrix.png')
|
| 711 |
+
print("โ
Confusion matrix saved to confusion_matrix.png")
|
| 712 |
+
```
|
| 713 |
+
|
| 714 |
+
### PR ๊ณก์ (Precision-Recall Curve)
|
| 715 |
+
|
| 716 |
+
```python
|
| 717 |
+
def generate_pr_curve(detections_by_threshold):
|
| 718 |
+
"""
|
| 719 |
+
Precision-Recall ๊ณก์ ์์ฑ
|
| 720 |
+
|
| 721 |
+
Args:
|
| 722 |
+
detections_by_threshold: ์๊ณ๊ฐ๋ณ ๊ฒ์ถ ๊ฒฐ๊ณผ
|
| 723 |
+
"""
|
| 724 |
+
import matplotlib.pyplot as plt
|
| 725 |
+
|
| 726 |
+
thresholds = []
|
| 727 |
+
precisions = []
|
| 728 |
+
recalls = []
|
| 729 |
+
|
| 730 |
+
for threshold, results in sorted(detections_by_threshold.items()):
|
| 731 |
+
metrics = results['metrics']
|
| 732 |
+
thresholds.append(threshold)
|
| 733 |
+
precisions.append(metrics['precision'])
|
| 734 |
+
recalls.append(metrics['recall'])
|
| 735 |
+
|
| 736 |
+
# ๊ทธ๋ํ
|
| 737 |
+
plt.figure(figsize=(10, 6))
|
| 738 |
+
plt.plot(recalls, precisions, 'b-', linewidth=2)
|
| 739 |
+
plt.xlabel('Recall')
|
| 740 |
+
plt.ylabel('Precision')
|
| 741 |
+
plt.title('Precision-Recall Curve')
|
| 742 |
+
plt.grid(True)
|
| 743 |
+
|
| 744 |
+
# AP (Average Precision) ๊ณ์ฐ
|
| 745 |
+
ap = np.trapz(precisions, recalls)
|
| 746 |
+
plt.text(0.1, 0.1, f'AP = {ap:.2%}', fontsize=12,
|
| 747 |
+
bbox=dict(boxstyle='round', facecolor='wheat'))
|
| 748 |
+
|
| 749 |
+
plt.tight_layout()
|
| 750 |
+
plt.savefig('pr_curve.png')
|
| 751 |
+
print(f"โ
PR curve saved to pr_curve.png (AP = {ap:.2%})")
|
| 752 |
+
```
|
| 753 |
+
|
| 754 |
+
---
|
| 755 |
+
|
| 756 |
+
## ๐ฏ ๊ฒ์ ๊ฒฐ๊ณผ ๊ธฐ๋ฐ ๊ฐ์
|
| 757 |
+
|
| 758 |
+
### ์๋๋ฆฌ์ค๋ณ ๋์
|
| 759 |
+
|
| 760 |
+
#### 1. Recall ๋ฎ์ (์์ฐ ๋์นจ)
|
| 761 |
+
|
| 762 |
+
**์ฆ์**: ์ค์ ์์ฐ๊ฐ ์๋๋ฐ ๊ฒ์ถ ์ ๋จ
|
| 763 |
+
|
| 764 |
+
**์์ธ ๋ถ์**:
|
| 765 |
+
```python
|
| 766 |
+
# ์ด๋ ํํฐ์์ ํ๋ฝํ๋์ง ํ์ธ
|
| 767 |
+
def analyze_filter_stages(image, detections):
|
| 768 |
+
"""๊ฐ ํํฐ ๋จ๊ณ๋ณ ํต๊ณผ์จ ๋ถ์"""
|
| 769 |
+
|
| 770 |
+
results = {}
|
| 771 |
+
|
| 772 |
+
# Step 1: ํํ ํํฐ
|
| 773 |
+
after_aspect = filter_by_aspect_ratio(detections)
|
| 774 |
+
results['aspect_ratio'] = {
|
| 775 |
+
'input': len(detections),
|
| 776 |
+
'output': len(after_aspect),
|
| 777 |
+
'pass_rate': len(after_aspect) / len(detections) if detections else 0
|
| 778 |
+
}
|
| 779 |
+
|
| 780 |
+
# Step 2: ์์ ํํฐ
|
| 781 |
+
after_color = filter_by_saturation(image, after_aspect)
|
| 782 |
+
results['saturation'] = {
|
| 783 |
+
'input': len(after_aspect),
|
| 784 |
+
'output': len(after_color),
|
| 785 |
+
'pass_rate': len(after_color) / len(after_aspect) if after_aspect else 0
|
| 786 |
+
}
|
| 787 |
+
|
| 788 |
+
# ... ๊ฐ ํํฐ๋ณ๋ก ๋ฐ๋ณต
|
| 789 |
+
|
| 790 |
+
return results
|
| 791 |
+
```
|
| 792 |
+
|
| 793 |
+
**ํด๊ฒฐ ๋ฐฉ๋ฒ**:
|
| 794 |
+
```python
|
| 795 |
+
# ํต๊ณผ์จ์ด ๋ฎ์ ํํฐ์ ์๊ณ๊ฐ ์ํ
|
| 796 |
+
if results['aspect_ratio']['pass_rate'] < 0.5:
|
| 797 |
+
# ์ข
ํก๋น ๋ฒ์ ํ๋
|
| 798 |
+
params['min_aspect_ratio'] = 1.5 # 2.0 โ 1.5
|
| 799 |
+
params['max_aspect_ratio'] = 12.0 # 10.0 โ 12.0
|
| 800 |
+
```
|
| 801 |
+
|
| 802 |
+
---
|
| 803 |
+
|
| 804 |
+
#### 2. Precision ๋ฎ์ (์ค๊ฒ์ถ)
|
| 805 |
+
|
| 806 |
+
**์ฆ์**: ์, ์ ๋ฑ๋ ์์ฐ๋ก ๊ฒ์ถ
|
| 807 |
+
|
| 808 |
+
**์์ธ ๋ถ์**:
|
| 809 |
+
```python
|
| 810 |
+
# ์ค๊ฒ์ถ๋ ๊ฐ์ฒด ๋ถ์
|
| 811 |
+
def analyze_false_positives(false_positive_detections, images):
|
| 812 |
+
"""์ค๊ฒ์ถ ํจํด ๋ถ์"""
|
| 813 |
+
|
| 814 |
+
# ์ค๊ฒ์ถ ๊ฐ์ฒด์ ํน์ง ์์ง
|
| 815 |
+
fp_features = []
|
| 816 |
+
for det in false_positive_detections:
|
| 817 |
+
features = {
|
| 818 |
+
'aspect_ratio': calculate_aspect_ratio(det['bbox']),
|
| 819 |
+
'area_ratio': calculate_area_ratio(det['bbox'], image_shape),
|
| 820 |
+
'color': extract_color_features(image, det['bbox']),
|
| 821 |
+
'texture': extract_texture_features(image, det['bbox'])
|
| 822 |
+
}
|
| 823 |
+
fp_features.append(features)
|
| 824 |
+
|
| 825 |
+
# ๊ณตํต ํจํด ์ฐพ๊ธฐ
|
| 826 |
+
avg_aspect = np.mean([f['aspect_ratio'] for f in fp_features])
|
| 827 |
+
print(f"FP ํ๊ท ์ข
ํก๋น: {avg_aspect:.2f}")
|
| 828 |
+
|
| 829 |
+
return fp_features
|
| 830 |
+
```
|
| 831 |
+
|
| 832 |
+
**ํด๊ฒฐ ๋ฐฉ๋ฒ**:
|
| 833 |
+
```python
|
| 834 |
+
# ์๊ณ๊ฐ ๊ฐํ
|
| 835 |
+
if avg_fp_aspect > 15:
|
| 836 |
+
# ์(ruler) ๊ฐ์ ๋งค์ฐ ๊ธด ๊ฐ์ฒด
|
| 837 |
+
params['max_aspect_ratio'] = 10.0 # ์ค์ด๊ธฐ
|
| 838 |
+
|
| 839 |
+
if avg_fp_saturation > 100:
|
| 840 |
+
# ์์์ด ๊ฐํ ๊ฐ์ฒด (์์ฐ๋ ๋ฎ์ ์ฑ๋)
|
| 841 |
+
params['max_saturation'] = 100 # 120 โ 100
|
| 842 |
+
```
|
| 843 |
+
|
| 844 |
+
---
|
| 845 |
+
|
| 846 |
+
#### 3. F1 ๋ฎ์ (์ ๋ฐ์ ์ฑ๋ฅ ๋ถ์กฑ)
|
| 847 |
+
|
| 848 |
+
**์ฆ์**: Precision, Recall ๋ชจ๋ ๋ฎ์
|
| 849 |
+
|
| 850 |
+
**ํด๊ฒฐ ๋ฐฉ๋ฒ**:
|
| 851 |
+
1. **RT-DETR ์๊ณ๊ฐ ์กฐ์ **
|
| 852 |
+
```python
|
| 853 |
+
# ๋ ๋ง์ ํ๋ณด ๊ฒ์ถ
|
| 854 |
+
rtdetr_confidence = 0.2 # 0.3 โ 0.2
|
| 855 |
+
```
|
| 856 |
+
|
| 857 |
+
2. **ํํฐ ์์ ์ต์ ํ**
|
| 858 |
+
```python
|
| 859 |
+
# ๊ฐ์ฅ ํจ๊ณผ์ ์ธ ํํฐ๋ฅผ ๋จผ์
|
| 860 |
+
# (๋น ๋ฅด๊ฒ ํ์คํ ๊ฒ๋ถํฐ ์ ๊ฑฐ)
|
| 861 |
+
```
|
| 862 |
+
|
| 863 |
+
3. **๊ทผ๋ณธ์ ํด๊ฒฐ**: Roboflow ํ์ธํ๋
|
| 864 |
+
```
|
| 865 |
+
โ docs/measurement_shrimp_detection_strategy.md ์ฐธ๊ณ
|
| 866 |
+
```
|
| 867 |
+
|
| 868 |
+
---
|
| 869 |
+
|
| 870 |
+
## ๐ ๊ฒ์ ์ฒดํฌ๋ฆฌ์คํธ
|
| 871 |
+
|
| 872 |
+
### ํ
์คํธ ์ ์ฒดํฌ๋ฆฌ์คํธ
|
| 873 |
+
|
| 874 |
+
- [ ] ํ
์คํธ ๋ฐ์ดํฐ์
์ค๋น (์ต์ 50์ฅ)
|
| 875 |
+
- [ ] Ground Truth ๋ผ๋ฒจ๋ง ์๋ฃ
|
| 876 |
+
- [ ] ๋ค์ํ ์๋๋ฆฌ์ค ํฌํจ (๋ฐฐ๊ฒฝ, ์์น, ํฌ๊ธฐ)
|
| 877 |
+
- [ ] Negative ์ํ ํฌํจ (์์ฐ ์๋ ์ด๋ฏธ์ง)
|
| 878 |
+
|
| 879 |
+
### ๊ฒ์ ์ค ์ฒดํฌ๋ฆฌ์คํธ
|
| 880 |
+
|
| 881 |
+
- [ ] ์๊ฐ์ ๊ฒ์ ์๋ฃ (๋ชจ๋ ์ด๋ฏธ์ง ์ก์ ํ์ธ)
|
| 882 |
+
- [ ] ์ ๋์ ํ๊ฐ ์๋ฃ (Precision, Recall, F1 ๊ณ์ฐ)
|
| 883 |
+
- [ ] ์คํจ ์ฌ๋ก ๋ถ์ ์๋ฃ
|
| 884 |
+
- [ ] ํํฐ๋ณ ํต๊ณผ์จ ๋ถ์ ์๋ฃ
|
| 885 |
+
|
| 886 |
+
### ๊ฒ์ ํ ์ฒดํฌ๋ฆฌ์คํธ
|
| 887 |
+
|
| 888 |
+
- [ ] ๋ชฉํ ์ฑ๋ฅ ๋ฌ์ฑ ์ฌ๋ถ ํ์ธ (F1 > 70%)
|
| 889 |
+
- [ ] ํ๋ผ๋ฏธํฐ ํ๋ ํ์ ์ฌ๋ถ ํ๋จ
|
| 890 |
+
- [ ] ๋ฌธ์ํ (์คํจ ์ฌ๋ก, ๊ฐ์ ๋ฐฉํฅ)
|
| 891 |
+
- [ ] ๋ค์ iteration ๊ณํ ์๋ฆฝ
|
| 892 |
+
|
| 893 |
+
---
|
| 894 |
+
|
| 895 |
+
## ๐ ์์ฝ
|
| 896 |
+
|
| 897 |
+
### ๋น ๋ฅธ ๊ฒ์ (1์๊ฐ)
|
| 898 |
+
```bash
|
| 899 |
+
# 1. ์๊ฐ์ ํ
์คํธ
|
| 900 |
+
python test_visual_validation.py
|
| 901 |
+
|
| 902 |
+
# 2. ๊ฒฐ๊ณผ ์ด๋ฏธ์ง ํ์ธ
|
| 903 |
+
open test_results/visual/
|
| 904 |
+
|
| 905 |
+
# 3. ์ก์ ๊ฒ์ โ ๋ฌธ์ ํ์
|
| 906 |
+
```
|
| 907 |
+
|
| 908 |
+
### ์ ํํ ๊ฒ์ (๋ฐ๋์ )
|
| 909 |
+
```bash
|
| 910 |
+
# 1. Ground Truth ์ค๋น
|
| 911 |
+
# 2. ์ ๋์ ํ
์คํธ
|
| 912 |
+
python test_quantitative_evaluation.py
|
| 913 |
+
|
| 914 |
+
# 3. ๋ฉํธ๋ฆญ ๋ถ์
|
| 915 |
+
# 4. ํํฐ๋ณ ๋ถ์ โ ํ๋ผ๋ฏธํฐ ํ๋
|
| 916 |
+
```
|
| 917 |
+
|
| 918 |
+
### ๋ํํ ๊ฒ์ (์ค์๊ฐ)
|
| 919 |
+
```bash
|
| 920 |
+
# Gradio ์ธํฐํ์ด์ค ์คํ
|
| 921 |
+
python interactive_validation.py
|
| 922 |
+
|
| 923 |
+
# ๋ธ๋ผ์ฐ์ ์์ ์ค์๊ฐ ํ
์คํธ
|
| 924 |
+
# โ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ
|
| 925 |
+
```
|
| 926 |
+
|
| 927 |
+
---
|
| 928 |
+
|
| 929 |
+
**์์ฑ์ผ**: 2025-11-07
|
| 930 |
+
**๋ฒ์ **: 1.0
|
| 931 |
+
**์์ฑ์**: VIDraft Team
|
docs/measurement_shrimp_detection_strategy.md
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐ ์ธก์ ์ฉ ์์ฐ ๊ฒ์ถ ์ ๋ต
|
| 2 |
+
|
| 3 |
+
## ๐ฏ ๋ชฉํ
|
| 4 |
+
์ธก์ ๋งคํธ ์์ ์ฃฝ์ ์์ฐ๋ง ์ ํํ๊ฒ ๊ฒ์ถํ๊ณ , ์(ruler), ์, ๋ฐฐ๊ฒฝ ๊ฐ์ฒด๋ ์ ์ธ
|
| 5 |
+
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
## ๐ ํ์ฌ ์ํฉ
|
| 9 |
+
|
| 10 |
+
### ์ด๋ฏธ์ง ํน์ง
|
| 11 |
+
- **ํ๊ฒฝ**: ํ๋์ ์ธก์ ๋งคํธ
|
| 12 |
+
- **๊ฐ์ฒด**: ์ฃฝ์ ์์ฐ (ํฌ๋ช
/ํฐ์)
|
| 13 |
+
- **๋ฐฐ๊ฒฝ**: ์ธก์ ์, ์ฌ๋ ์/์ ๋ฐ, ๊ธฐํ ๋ฌผ์ฒด
|
| 14 |
+
- **์ฉ๋**: ์ฒด์ฅ/์ฒด์ค ์ธก์ ์ ์ํ ์ค์ธก ์ด๋ฏธ์ง
|
| 15 |
+
|
| 16 |
+
### ํ์ฌ ๋ชจ๋ธ ์ฑ๋ฅ
|
| 17 |
+
|
| 18 |
+
| ๋ชจ๋ธ | ๊ฒ์ถ ๊ฒฐ๊ณผ | ๋ฌธ์ ์ |
|
| 19 |
+
|------|----------|--------|
|
| 20 |
+
| **VIDraft/Shrimp (Roboflow)** | โ ๊ฒ์ถ ์คํจ | ์์กฐ ํ๊ฒฝ์ ์ด์์๋ ์์ฐ๋ก๋ง ํ์ต๋จ |
|
| 21 |
+
| **RT-DETR (๋ฒ์ฉ)** | โ ๏ธ ๊ณผ๋ค ๊ฒ์ถ | ์์ฐ + ์ + ์ + ๋งคํธ ๋ฑ ๋ชจ๋ ๊ฒ์ถ |
|
| 22 |
+
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+
## ๐ก ์ค๋งํธํ ํด๊ฒฐ ๋ฐฉ์
|
| 26 |
+
|
| 27 |
+
### ๋ฐฉ์ 1: Roboflow ๋ชจ๋ธ ํ์ธํ๋ โญ (์ถ์ฒ)
|
| 28 |
+
|
| 29 |
+
**๊ฐ์**: ๊ธฐ์กด VIDraft/Shrimp ๋ชจ๋ธ์ ์ธก์ ํ๊ฒฝ ๋ฐ์ดํฐ ์ถ๊ฐ ํ์ต
|
| 30 |
+
|
| 31 |
+
**์ฅ์ :**
|
| 32 |
+
- โ
๊ธฐ์กด ์์กฐ ๊ฒ์ถ ์ฑ๋ฅ ์ ์ง
|
| 33 |
+
- โ
์ธก์ ํ๊ฒฝ ๊ฒ์ถ ๋ฅ๋ ฅ ์ถ๊ฐ
|
| 34 |
+
- โ
๋จ์ผ ๋ชจ๋ธ๋ก ํตํฉ ๊ด๋ฆฌ
|
| 35 |
+
- โ
Roboflow ํ๋ซํผ์์ ์ฝ๊ฒ ํ์ต ๊ฐ๋ฅ
|
| 36 |
+
|
| 37 |
+
**๋จ์ :**
|
| 38 |
+
- โ ๏ธ ์ธก์ ํ๊ฒฝ ์ด๋ฏธ์ง ๋ผ๋ฒจ๋ง ํ์ (์ต์ 50-100์ฅ)
|
| 39 |
+
- โ ๏ธ ์ฌํ์ต ์๊ฐ ์์
|
| 40 |
+
|
| 41 |
+
**๊ตฌํ ์ ์ฐจ:**
|
| 42 |
+
1. **๋ฐ์ดํฐ ์์ง** (50-100์ฅ)
|
| 43 |
+
- ์ธก์ ๋งคํธ ์ ์ฃฝ์ ์์ฐ ์ด๋ฏธ์ง
|
| 44 |
+
- ๋ค์ํ ํฌ๊ธฐ, ๊ฐ๋, ์กฐ๋ช
์กฐ๊ฑด
|
| 45 |
+
|
| 46 |
+
2. **Roboflow์์ ๋ผ๋ฒจ๋ง**
|
| 47 |
+
- https://app.roboflow.com
|
| 48 |
+
- ์์ฐ ์์ญ๋ง ๋ฐ์ด๋ฉ ๋ฐ์ค ํ์
|
| 49 |
+
- ์, ์, ๋ฐฐ๊ฒฝ์ ์ ์ธ
|
| 50 |
+
|
| 51 |
+
3. **๋ชจ๋ธ ์ฌํ์ต**
|
| 52 |
+
- ๊ธฐ์กด ์์กฐ ๋ฐ์ดํฐ + ์๋ก์ด ์ธก์ ๋ฐ์ดํฐ
|
| 53 |
+
- Transfer Learning์ผ๋ก ๋น ๋ฅธ ํ์ต
|
| 54 |
+
- Validation์ผ๋ก ์ฑ๋ฅ ๊ฒ์ฆ
|
| 55 |
+
|
| 56 |
+
4. **๋ฐฐํฌ**
|
| 57 |
+
- ๋์ผํ API ํค๋ก ์
๋ฐ์ดํธ๋ ๋ชจ๋ธ ์ฌ์ฉ
|
| 58 |
+
- ์ฝ๋ ๋ณ๊ฒฝ ์์ด ์๋ ์ ์ฉ
|
| 59 |
+
|
| 60 |
+
**์์ ์์ ์๊ฐ**: 2-3์ผ
|
| 61 |
+
**์์ ์ ํ๋**: 90%+ (๊ธฐ์กด ์์กฐ: 90%, ์ธก์ : 85%+)
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
### ๋ฐฉ์ 2: SAM (Segment Anything Model) + ํ์ฒ๋ฆฌ ํํฐ
|
| 66 |
+
|
| 67 |
+
**๊ฐ์**: SAM์ผ๋ก ๋ชจ๋ ๊ฐ์ฒด ์ธ๊ทธ๋จผํธ ํ, ์์ฐ ํน์ง์ผ๋ก ํํฐ๋ง
|
| 68 |
+
|
| 69 |
+
**์ฅ์ :**
|
| 70 |
+
- โ
์ถ๊ฐ ํ์ต ๋ถํ์
|
| 71 |
+
- โ
Zero-shot ๊ฒ์ถ ๊ฐ๋ฅ
|
| 72 |
+
- โ
์ ๋ฐํ ์ธ๊ทธ๋จผํ
์ด์
|
| 73 |
+
|
| 74 |
+
**๋จ์ :**
|
| 75 |
+
- โ ๏ธ ๋๋ฆฐ ์ถ๋ก ์๋ (CPU: 10-30์ด/์ด๋ฏธ์ง)
|
| 76 |
+
- โ ๏ธ ์์ฐ ํน์ง ์ ์ ์ด๋ ค์ (์์, ํฌ๊ธฐ, ํํ ๋ฑ)
|
| 77 |
+
- โ ๏ธ ๋ณต์กํ ํ์ฒ๋ฆฌ ๋ก์ง ํ์
|
| 78 |
+
|
| 79 |
+
**๊ตฌํ ์์:**
|
| 80 |
+
```python
|
| 81 |
+
from segment_anything import SamPredictor, sam_model_registry
|
| 82 |
+
|
| 83 |
+
# SAM ๋ชจ๋ธ ๋ก๋
|
| 84 |
+
sam = sam_model_registry["vit_h"](checkpoint="sam_vit_h.pth")
|
| 85 |
+
predictor = SamPredictor(sam)
|
| 86 |
+
|
| 87 |
+
# ์๋ ์ธ๊ทธ๋จผํธ
|
| 88 |
+
masks = predictor.generate(image)
|
| 89 |
+
|
| 90 |
+
# ์์ฐ ํํฐ๋ง (ํด๋ฆฌ์คํฑ)
|
| 91 |
+
for mask in masks:
|
| 92 |
+
# 1. ํฌ๊ธฐ ํํฐ: ๋๋ฌด ํฌ๊ฑฐ๋ ์์ผ๋ฉด ์ ์ธ
|
| 93 |
+
area = mask.sum()
|
| 94 |
+
if area < 5000 or area > 50000:
|
| 95 |
+
continue
|
| 96 |
+
|
| 97 |
+
# 2. ์์น ํํฐ: ๋งคํธ ์ค์๋ถ๋ง
|
| 98 |
+
bbox = get_bbox(mask)
|
| 99 |
+
if not is_in_center_region(bbox):
|
| 100 |
+
continue
|
| 101 |
+
|
| 102 |
+
# 3. ํํ ํํฐ: ๊ฐ๋ก๋ก ๊ธด ํํ
|
| 103 |
+
aspect_ratio = bbox_width / bbox_height
|
| 104 |
+
if aspect_ratio < 2.0 or aspect_ratio > 8.0:
|
| 105 |
+
continue
|
| 106 |
+
|
| 107 |
+
# ์์ฐ๋ก ํ์
|
| 108 |
+
shrimp_masks.append(mask)
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
**์์ ์์ ์๊ฐ**: 3-5์ผ (๋ก์ง ๊ฐ๋ฐ + ํ๋)
|
| 112 |
+
**์์ ์ ํ๋**: 70-80% (ํด๋ฆฌ์คํฑ ์์กด)
|
| 113 |
+
|
| 114 |
+
---
|
| 115 |
+
|
| 116 |
+
### ๋ฐฉ์ 3: YOLOv8 ์ปค์คํ
ํ์ต
|
| 117 |
+
|
| 118 |
+
**๊ฐ์**: YOLOv8 ๋ชจ๋ธ์ ์ธก์ ํ๊ฒฝ ๋ฐ์ดํฐ๋ก ์ฒ์๋ถํฐ ํ์ต
|
| 119 |
+
|
| 120 |
+
**์ฅ์ :**
|
| 121 |
+
- โ
๋น ๋ฅธ ์ถ๋ก ์๋ (CPU: 0.5-1์ด/์ด๋ฏธ์ง)
|
| 122 |
+
- โ
์คํ์์ค๋ก ์์ ์ ์ด ๊ฐ๋ฅ
|
| 123 |
+
- โ
๋ค์ํ ๋ด๋ณด๋ด๊ธฐ ํ์ (ONNX, TensorRT ๋ฑ)
|
| 124 |
+
|
| 125 |
+
**๋จ์ :**
|
| 126 |
+
- โ ๏ธ ๋๋ ๋ฐ์ดํฐ ํ์ (์ต์ 200-500์ฅ)
|
| 127 |
+
- โ ๏ธ ํ์ต ํ๊ฒฝ ๊ตฌ์ถ ํ์ (GPU)
|
| 128 |
+
- โ ๏ธ ๊ด๋ฆฌ ๋ถ๋ด ์ฆ๊ฐ
|
| 129 |
+
|
| 130 |
+
**๊ตฌํ ์ ์ฐจ:**
|
| 131 |
+
```bash
|
| 132 |
+
# ์ค์น
|
| 133 |
+
pip install ultralytics
|
| 134 |
+
|
| 135 |
+
# ๋ฐ์ดํฐ ์ค๋น (YOLO ํ์)
|
| 136 |
+
# data.yaml:
|
| 137 |
+
# path: ./dataset
|
| 138 |
+
# train: images/train
|
| 139 |
+
# val: images/val
|
| 140 |
+
# nc: 1
|
| 141 |
+
# names: ['shrimp']
|
| 142 |
+
|
| 143 |
+
# ํ์ต
|
| 144 |
+
yolo detect train data=data.yaml model=yolov8n.pt epochs=100 imgsz=640
|
| 145 |
+
|
| 146 |
+
# ์ถ๋ก
|
| 147 |
+
yolo detect predict model=best.pt source=test.jpg
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
**์์ ์์ ์๊ฐ**: 1-2์ฃผ
|
| 151 |
+
**์์ ์ ํ๋**: 85%+ (์ถฉ๋ถํ ๋ฐ์ดํฐ ์)
|
| 152 |
+
|
| 153 |
+
---
|
| 154 |
+
|
| 155 |
+
### ๋ฐฉ์ 4: ํ์ด๋ธ๋ฆฌ๋ ์ ๊ทผ (ํ์ฒ๋ฆฌ ํํฐ ์ถ๊ฐ) ๐ (์ฆ์ ๊ฐ๋ฅ)
|
| 156 |
+
|
| 157 |
+
**๊ฐ์**: RT-DETR ๊ฒฐ๊ณผ์ ์ค๋งํธ ํํฐ๋ง ์ ์ฉ
|
| 158 |
+
|
| 159 |
+
**์ฅ์ :**
|
| 160 |
+
- โ
์ฆ์ ์ ์ฉ ๊ฐ๋ฅ (์ฝ๋๋ง ์์ )
|
| 161 |
+
- โ
์ถ๊ฐ ํ์ต ๋ถํ์
|
| 162 |
+
- โ
๊ธฐ์กด ์ธํ๋ผ ํ์ฉ
|
| 163 |
+
|
| 164 |
+
**๊ตฌํ ์ ๋ต:**
|
| 165 |
+
|
| 166 |
+
#### A. ํด๋์ค ํํฐ๋ง
|
| 167 |
+
```python
|
| 168 |
+
# RT-DETR๋ COCO ํด๋์ค ์ฌ์ฉ
|
| 169 |
+
# ์์ฐ์ ์ ์ฌํ ํด๋์ค๋ง ํ์ฉ
|
| 170 |
+
ALLOWED_CLASSES = [
|
| 171 |
+
# COCO dataset class IDs
|
| 172 |
+
# ์์ฐ์ ๋น์ทํ ๊ฒ๋ค๋ง
|
| 173 |
+
]
|
| 174 |
+
|
| 175 |
+
def filter_by_class(detections):
|
| 176 |
+
return [d for d in detections if d['class_id'] in ALLOWED_CLASSES]
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
#### B. ์์น ๊ธฐ๋ฐ ํํฐ๋ง
|
| 180 |
+
```python
|
| 181 |
+
def filter_by_position(detections, image_shape):
|
| 182 |
+
"""๋งคํธ ์ค์ ์์ญ๋ง ํ์ฉ"""
|
| 183 |
+
h, w = image_shape[:2]
|
| 184 |
+
center_region = {
|
| 185 |
+
'x_min': w * 0.2,
|
| 186 |
+
'x_max': w * 0.8,
|
| 187 |
+
'y_min': h * 0.3,
|
| 188 |
+
'y_max': h * 0.7
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
filtered = []
|
| 192 |
+
for det in detections:
|
| 193 |
+
bbox_center_x = (det['bbox'][0] + det['bbox'][2]) / 2
|
| 194 |
+
bbox_center_y = (det['bbox'][1] + det['bbox'][3]) / 2
|
| 195 |
+
|
| 196 |
+
if (center_region['x_min'] < bbox_center_x < center_region['x_max'] and
|
| 197 |
+
center_region['y_min'] < bbox_center_y < center_region['y_max']):
|
| 198 |
+
filtered.append(det)
|
| 199 |
+
|
| 200 |
+
return filtered
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
#### C. ํฌ๊ธฐ ๊ธฐ๋ฐ ํํฐ๋ง
|
| 204 |
+
```python
|
| 205 |
+
def filter_by_size(detections, image_shape):
|
| 206 |
+
"""์ ์ ํ ํฌ๊ธฐ์ ๊ฐ์ฒด๋ง ํ์ฉ"""
|
| 207 |
+
h, w = image_shape[:2]
|
| 208 |
+
image_area = h * w
|
| 209 |
+
|
| 210 |
+
filtered = []
|
| 211 |
+
for det in detections:
|
| 212 |
+
bbox = det['bbox']
|
| 213 |
+
bbox_area = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])
|
| 214 |
+
area_ratio = bbox_area / image_area
|
| 215 |
+
|
| 216 |
+
# ์ด๋ฏธ์ง์ 5%~50% ํฌ๊ธฐ๋ง ํ์ฉ
|
| 217 |
+
if 0.05 < area_ratio < 0.50:
|
| 218 |
+
filtered.append(det)
|
| 219 |
+
|
| 220 |
+
return filtered
|
| 221 |
+
```
|
| 222 |
+
|
| 223 |
+
#### D. ํํ ๊ธฐ๋ฐ ํํฐ๋ง
|
| 224 |
+
```python
|
| 225 |
+
def filter_by_shape(detections):
|
| 226 |
+
"""๊ฐ๋ก๋ก ๊ธด ํํ๋ง ํ์ฉ (์์ฐ ํน์ง)"""
|
| 227 |
+
filtered = []
|
| 228 |
+
for det in detections:
|
| 229 |
+
bbox = det['bbox']
|
| 230 |
+
width = bbox[2] - bbox[0]
|
| 231 |
+
height = bbox[3] - bbox[1]
|
| 232 |
+
aspect_ratio = width / height
|
| 233 |
+
|
| 234 |
+
# ๊ฐ๋ก:์ธ๋ก ๋น์จ 2:1 ~ 8:1
|
| 235 |
+
if 2.0 < aspect_ratio < 8.0:
|
| 236 |
+
filtered.append(det)
|
| 237 |
+
|
| 238 |
+
return filtered
|
| 239 |
+
```
|
| 240 |
+
|
| 241 |
+
#### E. ์์ ๊ธฐ๋ฐ ํํฐ๋ง
|
| 242 |
+
```python
|
| 243 |
+
def filter_by_color(image, detections):
|
| 244 |
+
"""ํ๊ท ์์์ด ์์ฐ์ ์ ์ฌํ ๊ฐ์ฒด๋ง ํ์ฉ"""
|
| 245 |
+
filtered = []
|
| 246 |
+
for det in detections:
|
| 247 |
+
bbox = det['bbox']
|
| 248 |
+
roi = image[int(bbox[1]):int(bbox[3]), int(bbox[0]):int(bbox[2])]
|
| 249 |
+
|
| 250 |
+
# ํ๊ท BGR ๊ฐ
|
| 251 |
+
mean_color = roi.mean(axis=(0, 1))
|
| 252 |
+
|
| 253 |
+
# ํฌ๋ช
/ํฐ์/ํ์ ๋ฒ์ (์ฃฝ์ ์์ฐ)
|
| 254 |
+
# B, G, R ๊ฐ์ด ๋ชจ๋ ๋น์ทํ๊ณ ์ค๊ฐ ์ด์
|
| 255 |
+
if (abs(mean_color[0] - mean_color[1]) < 30 and
|
| 256 |
+
abs(mean_color[1] - mean_color[2]) < 30 and
|
| 257 |
+
mean_color.mean() > 100):
|
| 258 |
+
filtered.append(det)
|
| 259 |
+
|
| 260 |
+
return filtered
|
| 261 |
+
```
|
| 262 |
+
|
| 263 |
+
#### ํตํฉ ํ์ดํ๋ผ์ธ
|
| 264 |
+
```python
|
| 265 |
+
def detect_measurement_shrimp(image, confidence=0.3):
|
| 266 |
+
"""์ธก์ ์ฉ ์์ฐ ๊ฒ์ถ (์ค๋งํธ ํํฐ๋ง)"""
|
| 267 |
+
|
| 268 |
+
# 1. RT-DETR๋ก ๊ธฐ๋ณธ ๊ฒ์ถ
|
| 269 |
+
raw_detections = rtdetr_detect(image, confidence)
|
| 270 |
+
|
| 271 |
+
# 2. ๋ค๋จ๊ณ ํํฐ๋ง
|
| 272 |
+
detections = raw_detections
|
| 273 |
+
detections = filter_by_position(detections, image.shape)
|
| 274 |
+
detections = filter_by_size(detections, image.shape)
|
| 275 |
+
detections = filter_by_shape(detections)
|
| 276 |
+
detections = filter_by_color(image, detections)
|
| 277 |
+
|
| 278 |
+
# 3. ์ ๋ขฐ๋ ์ ์ ๋ ฌ ํ ์์ 1๊ฐ๋ง (์ธก์ ์ ๋ณดํต 1๋ง๋ฆฌ)
|
| 279 |
+
detections = sorted(detections, key=lambda x: x['confidence'], reverse=True)
|
| 280 |
+
|
| 281 |
+
return detections[:1] if detections else []
|
| 282 |
+
```
|
| 283 |
+
|
| 284 |
+
**์์ ์์ ์๊ฐ**: 1์ผ
|
| 285 |
+
**์์ ์ ํ๋**: 60-75% (ํ๋์ ๋ฐ๋ผ ๋ฌ๋ผ์ง)
|
| 286 |
+
|
| 287 |
+
---
|
| 288 |
+
|
| 289 |
+
## ๐ ๋ฐฉ์ ๋น๊ต
|
| 290 |
+
|
| 291 |
+
| ๋ฐฉ์ | ์ ํ๋ | ์๋ | ๊ตฌํ ๋์ด๋ | ์์ ์๊ฐ | ๋น์ฉ | ์ถ์ฒ๋ |
|
| 292 |
+
|------|--------|------|------------|----------|------|--------|
|
| 293 |
+
| **1. Roboflow ํ์ธํ๋** | โญโญโญโญโญ 90%+ | โญโญโญโญ 1-2์ด | โญโญโญ ์ค๊ฐ | 2-3์ผ | $ | โญโญโญโญโญ |
|
| 294 |
+
| **2. SAM + ํ์ฒ๋ฆฌ** | โญโญโญ 70-80% | โญโญ 10-30์ด | โญโญโญโญ ์ด๋ ค์ | 3-5์ผ | ๋ฌด๋ฃ | โญโญ |
|
| 295 |
+
| **3. YOLOv8 ์ปค์คํ
** | โญโญโญโญ 85%+ | โญโญโญโญโญ 0.5์ด | โญโญโญโญโญ ๋งค์ฐ ์ด๋ ค์ | 1-2์ฃผ | ๋ฌด๋ฃ | โญโญโญ |
|
| 296 |
+
| **4. RT-DETR + ํํฐ** | โญโญโญ 60-75% | โญโญโญโญ 1-2์ด | โญโญ ์ฌ์ | 1์ผ | ๋ฌด๋ฃ | โญโญโญโญ |
|
| 297 |
+
|
| 298 |
+
---
|
| 299 |
+
|
| 300 |
+
## ๐ฏ ์ต์ข
์ถ์ฒ ์ ๋ต
|
| 301 |
+
|
| 302 |
+
### ๋จ๊ธฐ (์ฆ์~1์ผ): ๋ฐฉ์ 4 ๊ตฌํ
|
| 303 |
+
**RT-DETR + ์ค๋งํธ ํํฐ๋ง**์ผ๋ก ๋น ๋ฅด๊ฒ ๋ฌธ์ ํด๊ฒฐ
|
| 304 |
+
- ์์น, ํฌ๊ธฐ, ํํ, ์์ ํํฐ ์กฐํฉ
|
| 305 |
+
- ํ
์คํธ ์ด๋ฏธ์ง๋ก ํ๋ผ๋ฏธํฐ ํ๋
|
| 306 |
+
- 60-75% ์ ํ๋๋ก ์ค์ฉ์ ์ฌ์ฉ ๊ฐ๋ฅ
|
| 307 |
+
|
| 308 |
+
### ์ค๊ธฐ (1์ฃผ~2์ฃผ): ๋ฐฉ์ 1 ๊ตฌํ
|
| 309 |
+
**Roboflow ๋ชจ๋ธ ํ์ธํ๋**์ผ๋ก ๊ทผ๋ณธ์ ํด๊ฒฐ
|
| 310 |
+
- ์ธก์ ํ๊ฒฝ ์ด๋ฏธ์ง 50-100์ฅ ์์ง ๋ฐ ๋ผ๋ฒจ๋ง
|
| 311 |
+
- ๊ธฐ์กด ๋ชจ๋ธ์ ์ถ๊ฐ ํ์ต
|
| 312 |
+
- 90%+ ์ ํ๋ ๋ฌ์ฑ
|
| 313 |
+
- ์์กฐ + ์ธก์ ํตํฉ ๋ชจ๋ธ ์์ฑ
|
| 314 |
+
|
| 315 |
+
### ์ฅ๊ธฐ (์ ํ): ๋ฐฉ์ 3 ๊ณ ๋ ค
|
| 316 |
+
๋๋ ๋ฐ์ดํฐ ํ๋ณด ์ **YOLOv8**์ผ๋ก ์์ ์์ฒด ๋ชจ๋ธ ๊ตฌ์ถ
|
| 317 |
+
|
| 318 |
+
---
|
| 319 |
+
|
| 320 |
+
## ๐ ์ฆ์ ์คํ ๊ฐ๋ฅํ ์ก์
ํ๋
|
| 321 |
+
|
| 322 |
+
### Phase 1: ๋น ๋ฅธ ํ๋กํ ํ์
(1์ผ)
|
| 323 |
+
1. **RT-DETR + ํํฐ ๊ตฌํ**
|
| 324 |
+
- `app.py`์ `detect_measurement_shrimp()` ํจ์ ์ถ๊ฐ
|
| 325 |
+
- 5๊ฐ์ง ํํฐ ์์ฐจ ์ ์ฉ
|
| 326 |
+
- ํ
์คํธ ์ด๋ฏธ์ง๋ก ๊ฒ์ฆ
|
| 327 |
+
|
| 328 |
+
2. **UI ์
๋ฐ์ดํธ**
|
| 329 |
+
- "์ธก์ ์ฉ ๋ชจ๋" ํ ๊ธ ์ถ๊ฐ
|
| 330 |
+
- ํ์ฑํ ์ ์ค๋งํธ ํํฐ ์ ์ฉ
|
| 331 |
+
|
| 332 |
+
### Phase 2: ๋ฐ์ดํฐ ์์ง ์ค๋น (๋ณ๋ ฌ ์งํ)
|
| 333 |
+
1. **์ด๋ฏธ์ง ์์ง**
|
| 334 |
+
- ๊ธฐ์กด `data/` ํด๋์ ์ธก์ ์ด๋ฏธ์ง ํ์ฉ
|
| 335 |
+
- ์ถ๊ฐ๋ก 50-100์ฅ ์ดฌ์
|
| 336 |
+
- ๋ค์ํ ๊ฐ๋, ํฌ๊ธฐ, ์กฐ๋ช
|
| 337 |
+
|
| 338 |
+
2. **Roboflow ํ๋ก์ ํธ ์ค์ **
|
| 339 |
+
- ๊ธฐ์กด ํ๋ก์ ํธ์ ์ ๋ฒ์ ์์ฑ
|
| 340 |
+
- ๋ผ๋ฒจ๋ง ์งํ
|
| 341 |
+
|
| 342 |
+
### Phase 3: ๋ชจ๋ธ ์ฌํ์ต (2-3์ผ)
|
| 343 |
+
1. **Roboflow์์ ํ์ต**
|
| 344 |
+
- ๊ธฐ์กด + ์ ๋ฐ์ดํฐ ํตํฉ
|
| 345 |
+
- Auto-Orient, Auto-Adjust ๋ฑ ์ฆ๊ฐ ์ ์ฉ
|
| 346 |
+
- Validation ์ฑ๋ฅ ํ์ธ
|
| 347 |
+
|
| 348 |
+
2. **๋ฐฐํฌ ๋ฐ ํ
์คํธ**
|
| 349 |
+
- ์
๋ฐ์ดํธ๋ ๋ชจ๋ธ API ์ฐ๋
|
| 350 |
+
- A/B ํ
์คํธ๋ก ์ฑ๋ฅ ๋น๊ต
|
| 351 |
+
|
| 352 |
+
---
|
| 353 |
+
|
| 354 |
+
## ๐ฐ ๋น์ฉ ๋ถ์
|
| 355 |
+
|
| 356 |
+
### Roboflow ํ์ธํ๋
|
| 357 |
+
- **๋ฌด๋ฃ ํฐ์ด**: 1,000 predictions/month
|
| 358 |
+
- **Starter ํ๋**: $49/month - 10,000 predictions
|
| 359 |
+
- **์์ ๋น์ฉ**: ์ $0-49 (์ฌ์ฉ๋์ ๋ฐ๋ผ)
|
| 360 |
+
|
| 361 |
+
### ๋์ (์์ ๋ฌด๋ฃ)
|
| 362 |
+
- **YOLOv8 ์์ฒด ํ์ต**: ๋ฌด๋ฃ (GPU ํ๊ฒฝ ํ์)
|
| 363 |
+
- **RT-DETR + ํํฐ**: ๋ฌด๋ฃ (๋ก์ปฌ ์คํ)
|
| 364 |
+
|
| 365 |
+
---
|
| 366 |
+
|
| 367 |
+
## ๐ฌ ์คํ ๋ฐ ๊ฒ์ฆ
|
| 368 |
+
|
| 369 |
+
### ํ
์คํธ ๋ฐ์ดํฐ์
์ค๋น
|
| 370 |
+
```
|
| 371 |
+
test_images/
|
| 372 |
+
โโโ measurement/
|
| 373 |
+
โ โโโ single_shrimp/ # ์์ฐ 1๋ง๋ฆฌ๋ง
|
| 374 |
+
โ โโโ with_ruler/ # ์ ํฌํจ
|
| 375 |
+
โ โโโ with_hand/ # ์ ํฌํจ
|
| 376 |
+
โ โโโ complex/ # ๋ณต์กํ ๋ฐฐ๊ฒฝ
|
| 377 |
+
โโโ tank/
|
| 378 |
+
โโโ live_shrimp/ # ์์กฐ ์์ฐ
|
| 379 |
+
```
|
| 380 |
+
|
| 381 |
+
### ํ๊ฐ ์งํ
|
| 382 |
+
- **Precision**: ๊ฒ์ถ๋ ๊ฒ ์ค ์ค์ ์์ฐ ๋น์จ
|
| 383 |
+
- **Recall**: ์ค์ ์์ฐ ์ค ๊ฒ์ถ๋ ๋น์จ
|
| 384 |
+
- **F1 Score**: Precision๊ณผ Recall์ ์กฐํํ๊ท
|
| 385 |
+
- **์ถ๋ก ์๊ฐ**: CPU ๊ธฐ์ค ms/์ด๋ฏธ์ง
|
| 386 |
+
|
| 387 |
+
---
|
| 388 |
+
|
| 389 |
+
## ๐ ์ฐธ๊ณ ์๋ฃ
|
| 390 |
+
|
| 391 |
+
- [Roboflow Training Tutorial](https://docs.roboflow.com/train)
|
| 392 |
+
- [YOLOv8 Documentation](https://docs.ultralytics.com/)
|
| 393 |
+
- [Segment Anything (SAM)](https://segment-anything.com/)
|
| 394 |
+
- [RT-DETR Paper](https://arxiv.org/abs/2304.08069)
|
| 395 |
+
|
| 396 |
+
---
|
| 397 |
+
|
| 398 |
+
**์์ฑ์ผ**: 2025-11-07
|
| 399 |
+
**์์ฑ์**: VIDraft Team
|
| 400 |
+
**๋ฒ์ **: 1.0
|
docs/rt_detr_smart_filtering_strategy.md
ADDED
|
@@ -0,0 +1,1252 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐ฏ RT-DETR + ์ค๋งํธ ํํฐ๋ง ์ ๋ต (MECE)
|
| 2 |
+
|
| 3 |
+
## ๐ ๋ชฉ์ฐจ
|
| 4 |
+
1. [๋ฌธ์ ์ ์](#๋ฌธ์ -์ ์)
|
| 5 |
+
2. [ํํฐ๋ง ์ ๋ต ์ฒด๊ณ](#ํํฐ๋ง-์ ๋ต-์ฒด๊ณ)
|
| 6 |
+
3. [์์ธ ๊ตฌํ ๋ฐฉ์](#์์ธ-๊ตฌํ-๋ฐฉ์)
|
| 7 |
+
4. [์์ฌ๊ฒฐ์ ํธ๋ฆฌ](#์์ฌ๊ฒฐ์ -ํธ๋ฆฌ)
|
| 8 |
+
5. [๊ตฌํ ์ฝ๋](#๊ตฌํ-์ฝ๋)
|
| 9 |
+
6. [ํ
์คํธ ๋ฐ ๊ฒ์ฆ](#ํ
์คํธ-๋ฐ-๊ฒ์ฆ)
|
| 10 |
+
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
## ๐ฏ ๋ฌธ์ ์ ์
|
| 14 |
+
|
| 15 |
+
### ์
๋ ฅ ์กฐ๊ฑด
|
| 16 |
+
- **์ด๋ฏธ์ง**: ์ธก์ ๋งคํธ ์์ ์ฃฝ์ ์์ฐ
|
| 17 |
+
- **์ ์ฝ์ฌํญ**:
|
| 18 |
+
- ์์ฐ ์์น๋ ๋๋ค (๋งคํธ ์ด๋๋ ๊ฐ๋ฅ)
|
| 19 |
+
- ๋ฐฐ๊ฒฝ์ ์ฌ๋ ์, ์, ์ ๋ฐ ๋ฑ ๋ค์ํ ๊ฐ์ฒด
|
| 20 |
+
- ์์ฐ๋ ํฌ๋ช
/ํฐ์/ํ์ (์ฃฝ์ ์ํ)
|
| 21 |
+
|
| 22 |
+
### ๋ชฉํ
|
| 23 |
+
- **๊ฒ์ถ ๋์**: ์์ฐ๋ง ๊ฒ์ถ
|
| 24 |
+
- **์ ์ธ ๋์**: ์(ruler), ์, ์ ๋ฐ, ๋งคํธ, ๊ธฐํ ๋ฐฐ๊ฒฝ ๊ฐ์ฒด
|
| 25 |
+
- **์ ํ๋**: 60-75% (์ฆ์ ์ ์ฉ ๊ฐ๋ฅํ ์์ค)
|
| 26 |
+
|
| 27 |
+
### RT-DETR์ ํ๊ณ
|
| 28 |
+
- COCO ๋ฐ์ดํฐ์
ํ์ต (80๊ฐ ๋ฒ์ฉ ํด๋์ค)
|
| 29 |
+
- ์์ฐ ์ ์ฉ ํ์ต ์์
|
| 30 |
+
- ๋ชจ๋ ๊ฐ์ฒด๋ฅผ ๊ฒ์ถํ์ฌ ๊ณผ๋ค ๊ฒ์ถ ๋ฐ์
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## ๐๏ธ ํํฐ๋ง ์ ๋ต ์ฒด๊ณ (MECE)
|
| 35 |
+
|
| 36 |
+
### Level 1: ๊ฐ์ฒด ์์ฑ ๊ธฐ๋ฐ ๋ถ๋ฅ
|
| 37 |
+
|
| 38 |
+
๋ชจ๋ ๊ฒ์ถ๋ ๊ฐ์ฒด๋ ๋ค์ **4๊ฐ์ง ๋
๋ฆฝ์ ์์ฑ**์ผ๋ก ๋ถ๋ฅ๋ฉ๋๋ค:
|
| 39 |
+
|
| 40 |
+
```
|
| 41 |
+
๊ฒ์ถ ๊ฐ์ฒด
|
| 42 |
+
โโโ 1. ๋ฌผ๋ฆฌ์ ์์ฑ (Physical Attributes)
|
| 43 |
+
โ โโโ ํฌ๊ธฐ (Size)
|
| 44 |
+
โ โโโ ํํ (Shape/Aspect Ratio)
|
| 45 |
+
โ โโโ ๋ฐฉํฅ (Orientation)
|
| 46 |
+
โ
|
| 47 |
+
โโโ 2. ์๊ฐ์ ์์ฑ (Visual Attributes)
|
| 48 |
+
โ โโโ ์์ (Color)
|
| 49 |
+
โ โโโ ํ
์ค์ฒ (Texture)
|
| 50 |
+
โ โโโ ํฌ๋ช
๋ (Transparency)
|
| 51 |
+
โ
|
| 52 |
+
โโโ 3. ์๋ฏธ์ ์์ฑ (Semantic Attributes)
|
| 53 |
+
โ โโโ RT-DETR ํด๋์ค (COCO Class)
|
| 54 |
+
โ โโโ ์ ๋ขฐ๋ (Confidence Score)
|
| 55 |
+
โ
|
| 56 |
+
โโโ 4. ์ปจํ
์คํธ ์์ฑ (Contextual Attributes)
|
| 57 |
+
โโโ ๊ณต๊ฐ์ ๊ด๊ณ (Spatial Relations)
|
| 58 |
+
โโโ ์ฃผ๋ณ ๊ฐ์ฒด (Neighboring Objects)
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
### Level 2: ํํฐ๋ง ๋จ๊ณ (์์ฐจ ์ ์ฉ)
|
| 62 |
+
|
| 63 |
+
```
|
| 64 |
+
Step 1: ์ฌ์ ํํฐ (Pre-Filter)
|
| 65 |
+
โ RT-DETR ๊ฒฐ๊ณผ์์ ๋ช
๋ฐฑํ ์์ฐ๊ฐ ์๋ ๊ฒ ์ ๊ฑฐ
|
| 66 |
+
โ COCO ํด๋์ค ๊ธฐ๋ฐ 1์ฐจ ํํฐ๋ง
|
| 67 |
+
|
| 68 |
+
Step 2: ๋ฌผ๋ฆฌ์ ํํฐ (Physical Filter)
|
| 69 |
+
โ ํฌ๊ธฐ, ํํ, ๋ฐฉํฅ ๊ธฐ๋ฐ ํํฐ๋ง
|
| 70 |
+
โ ์์น ๋ฌด๊ดํ๊ฒ ์ ์ฉ ๊ฐ๋ฅ
|
| 71 |
+
|
| 72 |
+
Step 3: ์๊ฐ์ ํํฐ (Visual Filter)
|
| 73 |
+
โ ์์, ํ
์ค์ฒ ๊ธฐ๋ฐ ํํฐ๋ง
|
| 74 |
+
โ ์์ฐ์ ์๊ฐ์ ํน์ง ํ์ฉ
|
| 75 |
+
|
| 76 |
+
Step 4: ์ปจํ
์คํธ ํํฐ (Context Filter)
|
| 77 |
+
โ ์ฃผ๋ณ ๊ฐ์ฒด์์ ๊ด๊ณ ๋ถ์
|
| 78 |
+
โ ์(ruler)์์ ์๋์ ์์น
|
| 79 |
+
|
| 80 |
+
Step 5: ํ์ฒ๋ฆฌ (Post-Processing)
|
| 81 |
+
โ ์ ๋ขฐ๋ ๊ธฐ๋ฐ ์ต์ข
์ ํ
|
| 82 |
+
โ ์ค๋ณต ์ ๊ฑฐ (NMS)
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
---
|
| 86 |
+
|
| 87 |
+
## ๐ ์์ธ ๊ตฌํ ๋ฐฉ์
|
| 88 |
+
|
| 89 |
+
### Step 1: ์ฌ์ ํํฐ (Pre-Filter)
|
| 90 |
+
|
| 91 |
+
#### 1.1 COCO ํด๋์ค ํํฐ๋ง
|
| 92 |
+
|
| 93 |
+
**์ ๋ต**: RT-DETR์ด ๊ฒ์ถํ ํด๋์ค ์ค ์์ฐ์ ์ ์ฌํ ํด๋์ค๋ง ํ์ฉ
|
| 94 |
+
|
| 95 |
+
**COCO 80๊ฐ ํด๋์ค ๋ถ๋ฅ**:
|
| 96 |
+
|
| 97 |
+
```python
|
| 98 |
+
# COCO Classes ์์ ๋ถ๋ฅ (MECE)
|
| 99 |
+
COCO_CLASSES = {
|
| 100 |
+
# A. ์์ฐ ๊ฐ๋ฅ์ฑ ์๋ ํด๋์ค (ํ์ฉ) โ
|
| 101 |
+
"POSSIBLY_SHRIMP": [
|
| 102 |
+
# ์ ์ฌ ํํ ํด์ ์๋ฌผ
|
| 103 |
+
# (COCO์๋ ์์ฐ๊ฐ ์์ผ๋ฏ๋ก ์ ์ฌํ ๊ฒ๋ค)
|
| 104 |
+
],
|
| 105 |
+
|
| 106 |
+
# B. ์์ฐ์ ๋ฌด๊ดํ ํด๋์ค (์ ๊ฑฐ) โ
|
| 107 |
+
"DEFINITELY_NOT_SHRIMP": {
|
| 108 |
+
# B1. ์ฌ๋ ๊ด๋ จ
|
| 109 |
+
"PERSON_RELATED": [0], # person
|
| 110 |
+
|
| 111 |
+
# B2. ์๋ฅ/์ก์ธ์๋ฆฌ
|
| 112 |
+
"CLOTHING": [24, 25, 26, 27, 31, 32, 33],
|
| 113 |
+
# backpack, umbrella, handbag, tie, suitcase, frisbee, skis
|
| 114 |
+
|
| 115 |
+
# B3. ์คํฌ์ธ ์ฉํ
|
| 116 |
+
"SPORTS": [32, 33, 34, 35, 36, 37, 38, 39, 40, 41],
|
| 117 |
+
# sports ball, baseball bat, etc.
|
| 118 |
+
|
| 119 |
+
# B4. ์ค๋ด ๊ฐ๊ตฌ
|
| 120 |
+
"FURNITURE": [56, 57, 58, 59, 60, 61, 62, 63],
|
| 121 |
+
# chair, couch, bed, etc.
|
| 122 |
+
|
| 123 |
+
# B5. ์ ์๊ธฐ๊ธฐ
|
| 124 |
+
"ELECTRONICS": [63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73],
|
| 125 |
+
# laptop, mouse, keyboard, etc.
|
| 126 |
+
|
| 127 |
+
# B6. ์ฃผ๋ฐฉ ์ฉํ
|
| 128 |
+
"KITCHEN": [42, 43, 44, 45, 46, 47, 48, 49, 50, 51],
|
| 129 |
+
# wine glass, cup, fork, knife, etc.
|
| 130 |
+
|
| 131 |
+
# B7. ๊ตํต์๋จ
|
| 132 |
+
"VEHICLES": [1, 2, 3, 4, 5, 6, 7, 8],
|
| 133 |
+
# bicycle, car, motorcycle, etc.
|
| 134 |
+
},
|
| 135 |
+
|
| 136 |
+
# C. ์ ๋งคํ ํด๋์ค (์กฐ๊ฑด๋ถ ํ์ฉ) โ ๏ธ
|
| 137 |
+
"AMBIGUOUS": [
|
| 138 |
+
# ์ถ๊ฐ ํํฐ๋ง ํ์
|
| 139 |
+
# ์: ์ผ๋ถ ๊ณผ์ผ, ๋ฌผ์ฒด ๋ฑ
|
| 140 |
+
]
|
| 141 |
+
}
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
**๊ตฌํ**:
|
| 145 |
+
```python
|
| 146 |
+
def filter_by_coco_class(detections):
|
| 147 |
+
"""COCO ํด๋์ค ๊ธฐ๋ฐ ํํฐ๋ง"""
|
| 148 |
+
|
| 149 |
+
# ๋ช
๋ฐฑํ ์ ์ธํ ํด๋์ค ID
|
| 150 |
+
EXCLUDE_CLASSES = set()
|
| 151 |
+
for category in COCO_CLASSES["DEFINITELY_NOT_SHRIMP"].values():
|
| 152 |
+
EXCLUDE_CLASSES.update(category)
|
| 153 |
+
|
| 154 |
+
filtered = []
|
| 155 |
+
for det in detections:
|
| 156 |
+
class_id = det.get('class_id', -1)
|
| 157 |
+
if class_id not in EXCLUDE_CLASSES:
|
| 158 |
+
filtered.append(det)
|
| 159 |
+
|
| 160 |
+
return filtered
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
#### 1.2 ์ ๋ขฐ๋ ์๊ณ๊ฐ
|
| 164 |
+
|
| 165 |
+
**์ ๋ต**: ๋๋ฌด ๋ฎ์ ์ ๋ขฐ๋๋ ๋
ธ์ด์ฆ์ผ ๊ฐ๋ฅ์ฑ
|
| 166 |
+
|
| 167 |
+
```python
|
| 168 |
+
def filter_by_confidence(detections, min_confidence=0.15):
|
| 169 |
+
"""์ต์ ์ ๋ขฐ๋ ํํฐ"""
|
| 170 |
+
return [d for d in detections if d['confidence'] >= min_confidence]
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
---
|
| 174 |
+
|
| 175 |
+
### Step 2: ๋ฌผ๋ฆฌ์ ํํฐ (Physical Filter)
|
| 176 |
+
|
| 177 |
+
**ํต์ฌ**: ์์น ๋ฌด๊ดํ๊ฒ ์ ์ฉ ๊ฐ๋ฅํ ๋ฌผ๏ฟฝ๏ฟฝ์ ํน์ฑ
|
| 178 |
+
|
| 179 |
+
#### 2.1 ์ ๋ ํฌ๊ธฐ ํํฐ
|
| 180 |
+
|
| 181 |
+
**๊ฐ์ **: ์ธก์ ์ด๋ฏธ์ง๋ ๋๋ถ๋ถ ๋น์ทํ ํด์๋ (1000x1000 ~ 2000x2000)
|
| 182 |
+
|
| 183 |
+
```python
|
| 184 |
+
def filter_by_absolute_size(detections, image_shape):
|
| 185 |
+
"""์ ๋ ํฝ์
ํฌ๊ธฐ ๊ธฐ๋ฐ ํํฐ"""
|
| 186 |
+
|
| 187 |
+
h, w = image_shape[:2]
|
| 188 |
+
|
| 189 |
+
filtered = []
|
| 190 |
+
for det in detections:
|
| 191 |
+
bbox = det['bbox']
|
| 192 |
+
bbox_width = bbox[2] - bbox[0]
|
| 193 |
+
bbox_height = bbox[3] - bbox[1]
|
| 194 |
+
bbox_area = bbox_width * bbox_height
|
| 195 |
+
|
| 196 |
+
# ์์ฐ ํฌ๊ธฐ ๋ฒ์ (๊ฒฝํ์ )
|
| 197 |
+
# ๋๋ฌด ์์ผ๋ฉด: ๋
ธ์ด์ฆ
|
| 198 |
+
# ๋๋ฌด ํฌ๋ฉด: ๋งคํธ, ๋ฐฐ๊ฒฝ
|
| 199 |
+
if bbox_width < 50: # ์ต์ 50px
|
| 200 |
+
continue
|
| 201 |
+
if bbox_height < 20: # ์ต์ 20px
|
| 202 |
+
continue
|
| 203 |
+
if bbox_area < 2000: # ์ต์ ๋ฉด์
|
| 204 |
+
continue
|
| 205 |
+
if bbox_area > (w * h * 0.6): # ์ด๋ฏธ์ง์ 60% ์ด์์ด๋ฉด ๋ฐฐ๊ฒฝ
|
| 206 |
+
continue
|
| 207 |
+
|
| 208 |
+
filtered.append(det)
|
| 209 |
+
|
| 210 |
+
return filtered
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
#### 2.2 ์๋ ํฌ๊ธฐ ํํฐ
|
| 214 |
+
|
| 215 |
+
**์ ๋ต**: ์ด๋ฏธ์ง ํฌ๊ธฐ ๋๋น ๋น์จ๋ก ํํฐ๋ง
|
| 216 |
+
|
| 217 |
+
```python
|
| 218 |
+
def filter_by_relative_size(detections, image_shape):
|
| 219 |
+
"""์ด๋ฏธ์ง ๋๋น ์๋ ํฌ๊ธฐ ํํฐ"""
|
| 220 |
+
|
| 221 |
+
h, w = image_shape[:2]
|
| 222 |
+
image_area = h * w
|
| 223 |
+
|
| 224 |
+
filtered = []
|
| 225 |
+
for det in detections:
|
| 226 |
+
bbox = det['bbox']
|
| 227 |
+
bbox_area = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])
|
| 228 |
+
area_ratio = bbox_area / image_area
|
| 229 |
+
|
| 230 |
+
# ์ด๋ฏธ์ง์ 3%~50% ํฌ๊ธฐ๋ง ํ์ฉ
|
| 231 |
+
if 0.03 < area_ratio < 0.50:
|
| 232 |
+
filtered.append(det)
|
| 233 |
+
|
| 234 |
+
return filtered
|
| 235 |
+
```
|
| 236 |
+
|
| 237 |
+
#### 2.3 ์ข
ํก๋น ํํฐ (Aspect Ratio)
|
| 238 |
+
|
| 239 |
+
**ํน์ง**: ์์ฐ๋ ๊ฐ๋ก๋ก ๊ธด ํํ
|
| 240 |
+
|
| 241 |
+
```python
|
| 242 |
+
def filter_by_aspect_ratio(detections):
|
| 243 |
+
"""์ข
ํก๋น ๊ธฐ๋ฐ ํํฐ (๊ฐ๋ก/์ธ๋ก)"""
|
| 244 |
+
|
| 245 |
+
filtered = []
|
| 246 |
+
for det in detections:
|
| 247 |
+
bbox = det['bbox']
|
| 248 |
+
width = bbox[2] - bbox[0]
|
| 249 |
+
height = bbox[3] - bbox[1]
|
| 250 |
+
|
| 251 |
+
# ๊ฐ๋ก๊ฐ 0์ด๋ฉด ์ ์ธ
|
| 252 |
+
if height == 0:
|
| 253 |
+
continue
|
| 254 |
+
|
| 255 |
+
aspect_ratio = width / height
|
| 256 |
+
|
| 257 |
+
# ์์ฐ๋ ๊ฐ๋ก๋ก ๊ธธ๋ค
|
| 258 |
+
# 1.5:1 ~ 10:1 ๋ฒ์
|
| 259 |
+
if 1.5 < aspect_ratio < 10.0:
|
| 260 |
+
filtered.append(det)
|
| 261 |
+
|
| 262 |
+
return filtered
|
| 263 |
+
```
|
| 264 |
+
|
| 265 |
+
#### 2.4 ์ถฉ๋ง๋ ํํฐ (Solidity)
|
| 266 |
+
|
| 267 |
+
**์ ์**: Solidity = ๊ฐ์ฒด ๋ฉด์ / ๋ฐ์ด๋ฉ ๋ฐ์ค ๋ฉด์
|
| 268 |
+
|
| 269 |
+
**๊ฐ์ **: ์์ฐ๋ ๋ฐ์ด๋ฉ ๋ฐ์ค๋ฅผ ์ด๋ ์ ๋ ์ฑ์ (0.3~0.8)
|
| 270 |
+
|
| 271 |
+
```python
|
| 272 |
+
def filter_by_solidity(image, detections):
|
| 273 |
+
"""์ถฉ๋ง๋ ๊ธฐ๋ฐ ํํฐ (์ธ๊ทธ๋จผํธ ํ์ ์)"""
|
| 274 |
+
import cv2
|
| 275 |
+
import numpy as np
|
| 276 |
+
|
| 277 |
+
filtered = []
|
| 278 |
+
for det in detections:
|
| 279 |
+
bbox = det['bbox']
|
| 280 |
+
x1, y1, x2, y2 = map(int, bbox)
|
| 281 |
+
|
| 282 |
+
# ROI ์ถ์ถ
|
| 283 |
+
roi = image[y1:y2, x1:x2]
|
| 284 |
+
if roi.size == 0:
|
| 285 |
+
continue
|
| 286 |
+
|
| 287 |
+
# ๊ฐ๋จํ thresholding์ผ๋ก ๊ฐ์ฒด ์์ญ ์ถ์
|
| 288 |
+
gray = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY)
|
| 289 |
+
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
| 290 |
+
|
| 291 |
+
# ๊ฐ์ฒด ๋ฉด์
|
| 292 |
+
object_area = np.sum(binary > 0)
|
| 293 |
+
bbox_area = (x2 - x1) * (y2 - y1)
|
| 294 |
+
|
| 295 |
+
if bbox_area == 0:
|
| 296 |
+
continue
|
| 297 |
+
|
| 298 |
+
solidity = object_area / bbox_area
|
| 299 |
+
|
| 300 |
+
# ์์ฐ๋ 0.3~0.8 ๋ฒ์
|
| 301 |
+
if 0.25 < solidity < 0.85:
|
| 302 |
+
filtered.append(det)
|
| 303 |
+
|
| 304 |
+
return filtered
|
| 305 |
+
```
|
| 306 |
+
|
| 307 |
+
---
|
| 308 |
+
|
| 309 |
+
### Step 3: ์๊ฐ์ ํํฐ (Visual Filter)
|
| 310 |
+
|
| 311 |
+
**ํต์ฌ**: ์์๊ณผ ํ
์ค์ฒ๋ก ์์ฐ ์๋ณ (์์น ๋ฌด๊ด)
|
| 312 |
+
|
| 313 |
+
#### 3.1 ์์ ๊ธฐ๋ฐ ํํฐ
|
| 314 |
+
|
| 315 |
+
**ํน์ง**: ์ฃฝ์ ์์ฐ = ํฌ๋ช
/ํฐ์/ํ์ (๋์ ๋ช
๋, ๋ฎ์ ์ฑ๋)
|
| 316 |
+
|
| 317 |
+
```python
|
| 318 |
+
def filter_by_color(image, detections):
|
| 319 |
+
"""์์ ๊ธฐ๋ฐ ํํฐ (HSV ์๊ณต๊ฐ)"""
|
| 320 |
+
import cv2
|
| 321 |
+
import numpy as np
|
| 322 |
+
|
| 323 |
+
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
|
| 324 |
+
|
| 325 |
+
filtered = []
|
| 326 |
+
for det in detections:
|
| 327 |
+
bbox = det['bbox']
|
| 328 |
+
x1, y1, x2, y2 = map(int, bbox)
|
| 329 |
+
|
| 330 |
+
# ROI ์ถ์ถ
|
| 331 |
+
roi_hsv = hsv_image[y1:y2, x1:x2]
|
| 332 |
+
if roi_hsv.size == 0:
|
| 333 |
+
continue
|
| 334 |
+
|
| 335 |
+
# ํ๊ท HSV ๊ฐ
|
| 336 |
+
mean_h, mean_s, mean_v = roi_hsv.mean(axis=(0, 1))
|
| 337 |
+
|
| 338 |
+
# ์์ฐ ํน์ง:
|
| 339 |
+
# - ๋ฎ์ ์ฑ๋ (Saturation < 100)
|
| 340 |
+
# - ๋์ ๋ช
๋ (Value > 100)
|
| 341 |
+
# - ์์ ๋ฌด๊ด (ํฌ๋ช
ํ๋ฏ๋ก)
|
| 342 |
+
|
| 343 |
+
if mean_s < 120 and mean_v > 80: # ์ฑ๋ ๋ฎ๊ณ ๋ช
๋ ๋์
|
| 344 |
+
filtered.append(det)
|
| 345 |
+
|
| 346 |
+
return filtered
|
| 347 |
+
```
|
| 348 |
+
|
| 349 |
+
#### 3.2 ํ
์ค์ฒ ๊ธฐ๋ฐ ํํฐ
|
| 350 |
+
|
| 351 |
+
**ํน์ง**: ์์ฐ๋ ๋ถ๋๋ฌ์ด ํ
์ค์ฒ (์๋ ๋งคํธ๋ ํจํด์ด ๊ฐํจ)
|
| 352 |
+
|
| 353 |
+
```python
|
| 354 |
+
def filter_by_texture(image, detections):
|
| 355 |
+
"""ํ
์ค์ฒ ๊ธฐ๋ฐ ํํฐ (ํ์คํธ์ฐจ ํ์ฉ)"""
|
| 356 |
+
import cv2
|
| 357 |
+
import numpy as np
|
| 358 |
+
|
| 359 |
+
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 360 |
+
|
| 361 |
+
filtered = []
|
| 362 |
+
for det in detections:
|
| 363 |
+
bbox = det['bbox']
|
| 364 |
+
x1, y1, x2, y2 = map(int, bbox)
|
| 365 |
+
|
| 366 |
+
# ROI ์ถ์ถ
|
| 367 |
+
roi = gray_image[y1:y2, x1:x2]
|
| 368 |
+
if roi.size == 0:
|
| 369 |
+
continue
|
| 370 |
+
|
| 371 |
+
# ํ
์ค์ฒ ๋ณต์ก๋ (ํ์คํธ์ฐจ)
|
| 372 |
+
texture_std = np.std(roi)
|
| 373 |
+
|
| 374 |
+
# ์์ฐ: ๋ถ๋๋ฌ์ด ํ
์ค์ฒ (std ๋ฎ์)
|
| 375 |
+
# ์, ๋งคํธ: ๊ฐ๏ฟฝ๏ฟฝ ํจํด (std ๋์)
|
| 376 |
+
if texture_std < 50: # ๋ถ๋๋ฌ์ด ํ
์ค์ฒ
|
| 377 |
+
filtered.append(det)
|
| 378 |
+
|
| 379 |
+
return filtered
|
| 380 |
+
```
|
| 381 |
+
|
| 382 |
+
#### 3.3 ์์ง ๋ฐ๋ ํํฐ
|
| 383 |
+
|
| 384 |
+
**ํน์ง**: ์์ฐ๋ ์์ง๊ฐ ๋ถ๋๋ฌ์ (์๋ ๊ฐํ ์ง์ ์์ง)
|
| 385 |
+
|
| 386 |
+
```python
|
| 387 |
+
def filter_by_edge_density(image, detections):
|
| 388 |
+
"""์์ง ๋ฐ๋ ๊ธฐ๋ฐ ํํฐ"""
|
| 389 |
+
import cv2
|
| 390 |
+
import numpy as np
|
| 391 |
+
|
| 392 |
+
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 393 |
+
edges = cv2.Canny(gray_image, 50, 150)
|
| 394 |
+
|
| 395 |
+
filtered = []
|
| 396 |
+
for det in detections:
|
| 397 |
+
bbox = det['bbox']
|
| 398 |
+
x1, y1, x2, y2 = map(int, bbox)
|
| 399 |
+
|
| 400 |
+
# ROI ์์ง
|
| 401 |
+
roi_edges = edges[y1:y2, x1:x2]
|
| 402 |
+
if roi_edges.size == 0:
|
| 403 |
+
continue
|
| 404 |
+
|
| 405 |
+
# ์์ง ํฝ์
๋น์จ
|
| 406 |
+
edge_density = np.sum(roi_edges > 0) / roi_edges.size
|
| 407 |
+
|
| 408 |
+
# ์์ฐ: ์ค๊ฐ ์์ง ๋ฐ๋ (0.05~0.25)
|
| 409 |
+
# ์/๋งคํธ: ๋์ ์์ง ๋ฐ๋
|
| 410 |
+
if 0.03 < edge_density < 0.30:
|
| 411 |
+
filtered.append(det)
|
| 412 |
+
|
| 413 |
+
return filtered
|
| 414 |
+
```
|
| 415 |
+
|
| 416 |
+
---
|
| 417 |
+
|
| 418 |
+
### Step 4: ์ปจํ
์คํธ ํํฐ (Context Filter)
|
| 419 |
+
|
| 420 |
+
**ํต์ฌ**: ์ฃผ๋ณ ๊ฐ์ฒด์์ ๊ด๊ณ (์์น๋ ๋๋ค์ด์ง๋ง ์๋์ ๊ด๊ณ๋ ํ์ฉ ๊ฐ๋ฅ)
|
| 421 |
+
|
| 422 |
+
#### 4.1 ์(Ruler) ๊ฒ์ถ ๋ฐ ํ์ฉ
|
| 423 |
+
|
| 424 |
+
**์ ๋ต**: ์๊ฐ ๊ฒ์ถ๋๋ฉด ์์ฐ๋ ์ ๊ทผ์ฒ์ ์์ ๊ฐ๋ฅ์ฑ ๋์
|
| 425 |
+
|
| 426 |
+
```python
|
| 427 |
+
def detect_ruler(detections):
|
| 428 |
+
"""์(ruler) ๊ฒ์ถ"""
|
| 429 |
+
|
| 430 |
+
rulers = []
|
| 431 |
+
for det in detections:
|
| 432 |
+
bbox = det['bbox']
|
| 433 |
+
width = bbox[2] - bbox[0]
|
| 434 |
+
height = bbox[3] - bbox[1]
|
| 435 |
+
|
| 436 |
+
# ์ ํน์ง:
|
| 437 |
+
# - ๋งค์ฐ ๊ฐ๋๊ณ ๊ธด ํํ (aspect ratio > 10)
|
| 438 |
+
# - ์ง์ ํจํด
|
| 439 |
+
|
| 440 |
+
if height > 0:
|
| 441 |
+
aspect_ratio = width / height
|
| 442 |
+
if aspect_ratio > 10 or aspect_ratio < 0.1: # ๊ฐ๋ก ๋๋ ์ธ๋ก๋ก ๋งค์ฐ ๊ธด
|
| 443 |
+
rulers.append(det)
|
| 444 |
+
|
| 445 |
+
return rulers
|
| 446 |
+
|
| 447 |
+
def filter_by_ruler_proximity(detections, rulers):
|
| 448 |
+
"""์์์ ๊ทผ์ ๋ ๊ธฐ๋ฐ ํํฐ"""
|
| 449 |
+
if not rulers:
|
| 450 |
+
# ์๊ฐ ์์ผ๋ฉด ์ด ํํฐ ์คํต
|
| 451 |
+
return detections
|
| 452 |
+
|
| 453 |
+
import numpy as np
|
| 454 |
+
|
| 455 |
+
filtered = []
|
| 456 |
+
for det in detections:
|
| 457 |
+
det_center = np.array([
|
| 458 |
+
(det['bbox'][0] + det['bbox'][2]) / 2,
|
| 459 |
+
(det['bbox'][1] + det['bbox'][3]) / 2
|
| 460 |
+
])
|
| 461 |
+
|
| 462 |
+
# ๊ฐ์ฅ ๊ฐ๊น์ด ์์์ ๊ฑฐ๋ฆฌ
|
| 463 |
+
min_distance = float('inf')
|
| 464 |
+
for ruler in rulers:
|
| 465 |
+
ruler_center = np.array([
|
| 466 |
+
(ruler['bbox'][0] + ruler['bbox'][2]) / 2,
|
| 467 |
+
(ruler['bbox'][1] + ruler['bbox'][3]) / 2
|
| 468 |
+
])
|
| 469 |
+
distance = np.linalg.norm(det_center - ruler_center)
|
| 470 |
+
min_distance = min(min_distance, distance)
|
| 471 |
+
|
| 472 |
+
# ์์ ์ ๋นํ ๊ฐ๊น์ฐ๋ฉด ์์ฐ์ผ ๊ฐ๋ฅ์ฑ ๋์
|
| 473 |
+
# (๋๋ฌด ๊ฐ๊น์ฐ๋ฉด ์ ์์ฒด์ผ ์ ์์)
|
| 474 |
+
if 50 < min_distance < 500:
|
| 475 |
+
filtered.append(det)
|
| 476 |
+
|
| 477 |
+
return filtered
|
| 478 |
+
```
|
| 479 |
+
|
| 480 |
+
#### 4.2 ์(Hand) ์ ๊ฑฐ
|
| 481 |
+
|
| 482 |
+
**์ ๋ต**: ์์ด ๊ฒ์ถ๋๋ฉด ์ ์ธ
|
| 483 |
+
|
| 484 |
+
```python
|
| 485 |
+
def filter_out_hands(detections):
|
| 486 |
+
"""์ ์ ๊ฑฐ (COCO class_id = 0 ๋๋ ํน์ ์กฐ๊ฑด)"""
|
| 487 |
+
|
| 488 |
+
filtered = []
|
| 489 |
+
for det in detections:
|
| 490 |
+
# COCO์์ person = 0
|
| 491 |
+
# ์์ ๋ณดํต person์ผ๋ก ๊ฒ์ถ๋๊ฑฐ๋
|
| 492 |
+
# ํผ๋ถ์ + ํน์ ํํ
|
| 493 |
+
|
| 494 |
+
# ๊ฐ๋จํ class_id๋ก ์ ๊ฑฐ
|
| 495 |
+
if det.get('class_id') == 0: # person
|
| 496 |
+
continue
|
| 497 |
+
|
| 498 |
+
filtered.append(det)
|
| 499 |
+
|
| 500 |
+
return filtered
|
| 501 |
+
```
|
| 502 |
+
|
| 503 |
+
#### 4.3 ์ค์ฌ์ฑ ์ ์ (Centrality Score)
|
| 504 |
+
|
| 505 |
+
**์ ๋ต**: ์์ ํ ๋๋ค์ ์๋. ๋๋ถ๋ถ ์ด๋ฏธ์ง ์ค์~์ค๊ฐ ์์ญ์ ๋ฐฐ์น
|
| 506 |
+
|
| 507 |
+
```python
|
| 508 |
+
def calculate_centrality_score(bbox, image_shape):
|
| 509 |
+
"""์ค์ฌ์ฑ ์ ์ (0~1, ๋์์๋ก ์ค์)"""
|
| 510 |
+
h, w = image_shape[:2]
|
| 511 |
+
image_center = np.array([w / 2, h / 2])
|
| 512 |
+
|
| 513 |
+
bbox_center = np.array([
|
| 514 |
+
(bbox[0] + bbox[2]) / 2,
|
| 515 |
+
(bbox[1] + bbox[3]) / 2
|
| 516 |
+
])
|
| 517 |
+
|
| 518 |
+
# ์ค์ฌ์ผ๋ก๋ถํฐ ๊ฑฐ๋ฆฌ
|
| 519 |
+
distance = np.linalg.norm(bbox_center - image_center)
|
| 520 |
+
max_distance = np.linalg.norm(np.array([w / 2, h / 2])) # ๋๊ฐ์ ์ ๋ฐ
|
| 521 |
+
|
| 522 |
+
# ๊ฑฐ๋ฆฌ๊ฐ 0์ด๋ฉด ์ ์ 1, ๋ฉ์๋ก ์ ์ ๊ฐ์
|
| 523 |
+
centrality = 1 - (distance / max_distance)
|
| 524 |
+
|
| 525 |
+
return centrality
|
| 526 |
+
|
| 527 |
+
def add_centrality_scores(detections, image_shape):
|
| 528 |
+
"""๊ฐ ๊ฒ์ถ์ ์ค์ฌ์ฑ ์ ์ ์ถ๊ฐ"""
|
| 529 |
+
for det in detections:
|
| 530 |
+
det['centrality_score'] = calculate_centrality_score(det['bbox'], image_shape)
|
| 531 |
+
|
| 532 |
+
return detections
|
| 533 |
+
```
|
| 534 |
+
|
| 535 |
+
---
|
| 536 |
+
|
| 537 |
+
### Step 5: ํ์ฒ๋ฆฌ (Post-Processing)
|
| 538 |
+
|
| 539 |
+
#### 5.1 ์ข
ํฉ ์ ์ ๊ณ์ฐ
|
| 540 |
+
|
| 541 |
+
**์ ๋ต**: ์ฌ๋ฌ ํน์ง์ ์ข
ํฉํ์ฌ ์ต์ข
์ ์ ์ฐ์ถ
|
| 542 |
+
|
| 543 |
+
```python
|
| 544 |
+
def calculate_composite_score(detection, image_shape):
|
| 545 |
+
"""์ข
ํฉ ์ ์ ๊ณ์ฐ (0~100)"""
|
| 546 |
+
|
| 547 |
+
# ๊ฐ์ค์น (์กฐ์ ๊ฐ๋ฅ)
|
| 548 |
+
WEIGHTS = {
|
| 549 |
+
'confidence': 0.30, # RT-DETR ์ ๋ขฐ๋
|
| 550 |
+
'centrality': 0.15, # ์ค์ฌ์ฑ
|
| 551 |
+
'aspect_ratio': 0.20, # ์ข
ํก๋น ์ ํฉ๋
|
| 552 |
+
'size': 0.20, # ํฌ๊ธฐ ์ ํฉ๋
|
| 553 |
+
'color': 0.15 # ์์ ์ ํฉ๋
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
score = 0.0
|
| 557 |
+
|
| 558 |
+
# 1. ์ ๋ขฐ๋
|
| 559 |
+
score += detection['confidence'] * WEIGHTS['confidence']
|
| 560 |
+
|
| 561 |
+
# 2. ์ค์ฌ์ฑ
|
| 562 |
+
if 'centrality_score' in detection:
|
| 563 |
+
score += detection['centrality_score'] * WEIGHTS['centrality']
|
| 564 |
+
|
| 565 |
+
# 3. ์ข
ํก๋น ์ ํฉ๋
|
| 566 |
+
bbox = detection['bbox']
|
| 567 |
+
width = bbox[2] - bbox[0]
|
| 568 |
+
height = bbox[3] - bbox[1]
|
| 569 |
+
if height > 0:
|
| 570 |
+
aspect_ratio = width / height
|
| 571 |
+
# ์ด์์ ์ข
ํก๋น: 3~6
|
| 572 |
+
ideal_aspect = 4.5
|
| 573 |
+
aspect_fitness = 1 - abs(aspect_ratio - ideal_aspect) / ideal_aspect
|
| 574 |
+
aspect_fitness = max(0, min(1, aspect_fitness)) # 0~1 clipping
|
| 575 |
+
score += aspect_fitness * WEIGHTS['aspect_ratio']
|
| 576 |
+
|
| 577 |
+
# 4. ํฌ๊ธฐ ์ ํฉ๋
|
| 578 |
+
h, w = image_shape[:2]
|
| 579 |
+
bbox_area = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])
|
| 580 |
+
area_ratio = bbox_area / (h * w)
|
| 581 |
+
# ์ด์์ ํฌ๊ธฐ: 10~30%
|
| 582 |
+
if 0.10 < area_ratio < 0.30:
|
| 583 |
+
size_fitness = 1.0
|
| 584 |
+
else:
|
| 585 |
+
size_fitness = 0.5
|
| 586 |
+
score += size_fitness * WEIGHTS['size']
|
| 587 |
+
|
| 588 |
+
# 5. ์์ ์ ํฉ๋ (์ด๋ฏธ ํํฐ๋ง ํต๊ณผํ์ผ๋ฉด 1.0)
|
| 589 |
+
if 'color_fitness' in detection:
|
| 590 |
+
score += detection['color_fitness'] * WEIGHTS['color']
|
| 591 |
+
else:
|
| 592 |
+
score += 0.5 * WEIGHTS['color'] # ๊ธฐ๋ณธ๊ฐ
|
| 593 |
+
|
| 594 |
+
return score * 100 # 0~100 ์ค์ผ์ผ
|
| 595 |
+
```
|
| 596 |
+
|
| 597 |
+
#### 5.2 ์ต์ข
์ ํ
|
| 598 |
+
|
| 599 |
+
**์ ๋ต**: ์ข
ํฉ ์ ์ ์์ N๊ฐ ์ ํ (์ธก์ ์ ๋ณดํต 1๋ง๋ฆฌ)
|
| 600 |
+
|
| 601 |
+
```python
|
| 602 |
+
def select_top_detections(detections, image_shape, top_n=1):
|
| 603 |
+
"""์ข
ํฉ ์ ์ ๊ธฐ๋ฐ ์ต์ข
์ ํ"""
|
| 604 |
+
|
| 605 |
+
# ์ค์ฌ์ฑ ์ ์ ์ถ๊ฐ
|
| 606 |
+
detections = add_centrality_scores(detections, image_shape)
|
| 607 |
+
|
| 608 |
+
# ์ข
ํฉ ์ ์ ๊ณ์ฐ
|
| 609 |
+
for det in detections:
|
| 610 |
+
det['composite_score'] = calculate_composite_score(det, image_shape)
|
| 611 |
+
|
| 612 |
+
# ์ ์์ ์ ๋ ฌ
|
| 613 |
+
detections = sorted(detections, key=lambda x: x['composite_score'], reverse=True)
|
| 614 |
+
|
| 615 |
+
# ์์ N๊ฐ ์ ํ
|
| 616 |
+
return detections[:top_n]
|
| 617 |
+
```
|
| 618 |
+
|
| 619 |
+
#### 5.3 NMS (Non-Maximum Suppression)
|
| 620 |
+
|
| 621 |
+
**์ ๋ต**: ์ค๋ณต ๊ฒ์ถ ์ ๊ฑฐ
|
| 622 |
+
|
| 623 |
+
```python
|
| 624 |
+
def apply_nms(detections, iou_threshold=0.5):
|
| 625 |
+
"""NMS ์ ์ฉ"""
|
| 626 |
+
if len(detections) <= 1:
|
| 627 |
+
return detections
|
| 628 |
+
|
| 629 |
+
import numpy as np
|
| 630 |
+
|
| 631 |
+
# ์ ๋ขฐ๋์ ์ ๋ ฌ
|
| 632 |
+
detections = sorted(detections, key=lambda x: x['confidence'], reverse=True)
|
| 633 |
+
|
| 634 |
+
keep = []
|
| 635 |
+
while detections:
|
| 636 |
+
best = detections.pop(0)
|
| 637 |
+
keep.append(best)
|
| 638 |
+
|
| 639 |
+
# ๋จ์ ๊ฒ ์ค IoU ๋์ ๊ฒ ์ ๊ฑฐ
|
| 640 |
+
filtered = []
|
| 641 |
+
for det in detections:
|
| 642 |
+
iou = calculate_iou(best['bbox'], det['bbox'])
|
| 643 |
+
if iou < iou_threshold:
|
| 644 |
+
filtered.append(det)
|
| 645 |
+
|
| 646 |
+
detections = filtered
|
| 647 |
+
|
| 648 |
+
return keep
|
| 649 |
+
|
| 650 |
+
def calculate_iou(bbox1, bbox2):
|
| 651 |
+
"""IoU ๊ณ์ฐ"""
|
| 652 |
+
x1_min, y1_min, x1_max, y1_max = bbox1
|
| 653 |
+
x2_min, y2_min, x2_max, y2_max = bbox2
|
| 654 |
+
|
| 655 |
+
# ๊ต์งํฉ
|
| 656 |
+
inter_x_min = max(x1_min, x2_min)
|
| 657 |
+
inter_y_min = max(y1_min, y2_min)
|
| 658 |
+
inter_x_max = min(x1_max, x2_max)
|
| 659 |
+
inter_y_max = min(y1_max, y2_max)
|
| 660 |
+
|
| 661 |
+
if inter_x_max < inter_x_min or inter_y_max < inter_y_min:
|
| 662 |
+
return 0.0
|
| 663 |
+
|
| 664 |
+
inter_area = (inter_x_max - inter_x_min) * (inter_y_max - inter_y_min)
|
| 665 |
+
|
| 666 |
+
# ํฉ์งํฉ
|
| 667 |
+
area1 = (x1_max - x1_min) * (y1_max - y1_min)
|
| 668 |
+
area2 = (x2_max - x2_min) * (y2_max - y2_min)
|
| 669 |
+
union_area = area1 + area2 - inter_area
|
| 670 |
+
|
| 671 |
+
return inter_area / union_area if union_area > 0 else 0.0
|
| 672 |
+
```
|
| 673 |
+
|
| 674 |
+
---
|
| 675 |
+
|
| 676 |
+
## ๐ณ ์์ฌ๊ฒฐ์ ํธ๋ฆฌ
|
| 677 |
+
|
| 678 |
+
```
|
| 679 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 680 |
+
โ RT-DETR ๊ฒ์ถ ๊ฒฐ๊ณผ (N๊ฐ ๊ฐ์ฒด) โ
|
| 681 |
+
โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโ
|
| 682 |
+
โ
|
| 683 |
+
โผ
|
| 684 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 685 |
+
โ Step 1: ์ฌ์ ํํฐ โ
|
| 686 |
+
โ โข COCO ํด๋์ค ํํฐ (๋ช
๋ฐฑํ ์ ์ธ) โ
|
| 687 |
+
โ โข ์ ๋ขฐ๋ ํํฐ (< 0.15 ์ ๊ฑฐ) โ
|
| 688 |
+
โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโ
|
| 689 |
+
โ
|
| 690 |
+
โผ
|
| 691 |
+
โโโโโโโโโโโดโโโโโโโโโโ
|
| 692 |
+
โ ํํฐ ํต๊ณผ? โ
|
| 693 |
+
โโโโโโโฌโโโโโโโฌโโโโโโโ
|
| 694 |
+
NO YES
|
| 695 |
+
โ โ
|
| 696 |
+
[์ ๊ฑฐ] โผ
|
| 697 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 698 |
+
โ Step 2: ๋ฌผ๋ฆฌ์ ํํฐ โ
|
| 699 |
+
โ โข ์ ๋ ํฌ๊ธฐ ํํฐ โ
|
| 700 |
+
โ โข ์๋ ํฌ๊ธฐ ํํฐ โ
|
| 701 |
+
โ โข ์ข
ํก๋น ํํฐ (1.5 ~ 10) โ
|
| 702 |
+
โ โข ์ถฉ๋ง๋ ํํฐ (0.25 ~ 0.85) โ
|
| 703 |
+
โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโ
|
| 704 |
+
โ
|
| 705 |
+
โผ
|
| 706 |
+
โโโโโโโโโโโดโโโโโโโโโโ
|
| 707 |
+
โ ๋ชจ๋ ํต๊ณผ? โ
|
| 708 |
+
โโโโโโโฌโโโโโโโฌโโโโโโโ
|
| 709 |
+
NO YES
|
| 710 |
+
โ โ
|
| 711 |
+
[์ ๊ฑฐ] โผ
|
| 712 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 713 |
+
โ Step 3: ์๊ฐ์ ํํฐ โ
|
| 714 |
+
โ โข ์์ ํํฐ (๋ฎ์ ์ฑ๋, ๋์ ๋ช
๋) โ
|
| 715 |
+
โ โข ํ
์ค์ฒ ํํฐ (๋ถ๋๋ฌ์) โ
|
| 716 |
+
โ โข ์์ง ๋ฐ๋ ํํฐ (์ค๊ฐ) โ
|
| 717 |
+
โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโ
|
| 718 |
+
โ
|
| 719 |
+
โผ
|
| 720 |
+
โโโโโโโโโโโดโโโโโโโโโโ
|
| 721 |
+
โ ํต๊ณผ? โ
|
| 722 |
+
โโโโโโโฌโโโโโโโฌโโโโโโโ
|
| 723 |
+
NO YES
|
| 724 |
+
โ โ
|
| 725 |
+
[์ ๊ฑฐ] โผ
|
| 726 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 727 |
+
โ Step 4: ์ปจํ
์คํธ ํํฐ โ
|
| 728 |
+
โ โข ์(ruler) ๊ฒ์ถ ๋ฐ ๊ทผ์ ๋ โ
|
| 729 |
+
โ โข ์ ์ ๊ฑฐ โ
|
| 730 |
+
โ โข ์ค์ฌ์ฑ ์ ์ ๊ณ์ฐ โ
|
| 731 |
+
โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโ
|
| 732 |
+
โ
|
| 733 |
+
โผ
|
| 734 |
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 735 |
+
โ Step 5: ํ์ฒ๋ฆฌ โ
|
| 736 |
+
โ โข ์ข
ํฉ ์ ์ ๊ณ์ฐ โ
|
| 737 |
+
โ โข ์์ N๊ฐ ์ ํ (๋ณดํต 1๊ฐ) โ
|
| 738 |
+
โ โข NMS ์ ์ฉ โ
|
| 739 |
+
โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโ
|
| 740 |
+
โ
|
| 741 |
+
โผ
|
| 742 |
+
โโโโโโโโโโโโโโโโโโโ
|
| 743 |
+
โ ์ต์ข
๊ฒ์ถ ๊ฒฐ๊ณผ โ
|
| 744 |
+
โ (์์ฐ๋ง) โ
|
| 745 |
+
โโโโโโโโโโโโโโโโโโโ
|
| 746 |
+
```
|
| 747 |
+
|
| 748 |
+
---
|
| 749 |
+
|
| 750 |
+
## ๐ป ํตํฉ ๊ตฌํ ์ฝ๋
|
| 751 |
+
|
| 752 |
+
```python
|
| 753 |
+
# -*- coding: utf-8 -*-
|
| 754 |
+
"""
|
| 755 |
+
RT-DETR + ์ค๋งํธ ํํฐ๋ง ํตํฉ ํ์ดํ๋ผ์ธ
|
| 756 |
+
"""
|
| 757 |
+
|
| 758 |
+
import cv2
|
| 759 |
+
import numpy as np
|
| 760 |
+
from typing import List, Dict, Tuple
|
| 761 |
+
|
| 762 |
+
class SmartShrimpFilter:
|
| 763 |
+
"""์ธก์ ์ฉ ์์ฐ ๊ฒ์ถ ์ค๋งํธ ํํฐ"""
|
| 764 |
+
|
| 765 |
+
def __init__(self):
|
| 766 |
+
# ํ๋ผ๋ฏธํฐ (์กฐ์ ๊ฐ๋ฅ)
|
| 767 |
+
self.params = {
|
| 768 |
+
# Step 1: ์ฌ์ ํํฐ
|
| 769 |
+
'min_confidence': 0.15,
|
| 770 |
+
|
| 771 |
+
# Step 2: ๋ฌผ๋ฆฌ์ ํํฐ
|
| 772 |
+
'min_width': 50,
|
| 773 |
+
'min_height': 20,
|
| 774 |
+
'min_area': 2000,
|
| 775 |
+
'max_area_ratio': 0.6,
|
| 776 |
+
'min_area_ratio': 0.03,
|
| 777 |
+
'max_area_ratio': 0.50,
|
| 778 |
+
'min_aspect_ratio': 1.5,
|
| 779 |
+
'max_aspect_ratio': 10.0,
|
| 780 |
+
'min_solidity': 0.25,
|
| 781 |
+
'max_solidity': 0.85,
|
| 782 |
+
|
| 783 |
+
# Step 3: ์๊ฐ์ ํํฐ
|
| 784 |
+
'max_saturation': 120,
|
| 785 |
+
'min_value': 80,
|
| 786 |
+
'max_texture_std': 50,
|
| 787 |
+
'min_edge_density': 0.03,
|
| 788 |
+
'max_edge_density': 0.30,
|
| 789 |
+
|
| 790 |
+
# Step 4: ์ปจํ
์คํธ ํํฐ
|
| 791 |
+
'ruler_min_aspect': 10.0,
|
| 792 |
+
'ruler_max_aspect': 0.1,
|
| 793 |
+
'min_ruler_distance': 50,
|
| 794 |
+
'max_ruler_distance': 500,
|
| 795 |
+
|
| 796 |
+
# Step 5: ํ์ฒ๋ฆฌ
|
| 797 |
+
'top_n': 1,
|
| 798 |
+
'nms_iou_threshold': 0.5,
|
| 799 |
+
}
|
| 800 |
+
|
| 801 |
+
def filter(self, image: np.ndarray, detections: List[Dict]) -> List[Dict]:
|
| 802 |
+
"""
|
| 803 |
+
์ ์ฒด ํํฐ๋ง ํ์ดํ๋ผ์ธ
|
| 804 |
+
|
| 805 |
+
Args:
|
| 806 |
+
image: ์
๋ ฅ ์ด๋ฏธ์ง (BGR)
|
| 807 |
+
detections: RT-DETR ๊ฒ์ถ ๊ฒฐ๊ณผ
|
| 808 |
+
[{'bbox': [x1,y1,x2,y2], 'confidence': 0.9, 'class_id': 0}, ...]
|
| 809 |
+
|
| 810 |
+
Returns:
|
| 811 |
+
ํํฐ๋ง๋ ๊ฒ์ถ ๊ฒฐ๊ณผ (์์ฐ๋ง)
|
| 812 |
+
"""
|
| 813 |
+
|
| 814 |
+
print(f"[INFO] Initial detections: {len(detections)}")
|
| 815 |
+
|
| 816 |
+
# Step 1: ์ฌ์ ํํฐ
|
| 817 |
+
detections = self._pre_filter(detections)
|
| 818 |
+
print(f"[STEP 1] After pre-filter: {len(detections)}")
|
| 819 |
+
|
| 820 |
+
if not detections:
|
| 821 |
+
return []
|
| 822 |
+
|
| 823 |
+
# Step 2: ๋ฌผ๋ฆฌ์ ํํฐ
|
| 824 |
+
detections = self._physical_filter(detections, image.shape)
|
| 825 |
+
print(f"[STEP 2] After physical filter: {len(detections)}")
|
| 826 |
+
|
| 827 |
+
if not detections:
|
| 828 |
+
return []
|
| 829 |
+
|
| 830 |
+
# Step 3: ์๊ฐ์ ํํฐ
|
| 831 |
+
detections = self._visual_filter(image, detections)
|
| 832 |
+
print(f"[STEP 3] After visual filter: {len(detections)}")
|
| 833 |
+
|
| 834 |
+
if not detections:
|
| 835 |
+
return []
|
| 836 |
+
|
| 837 |
+
# Step 4: ์ปจํ
์คํธ ํํฐ
|
| 838 |
+
detections = self._context_filter(detections, image.shape)
|
| 839 |
+
print(f"[STEP 4] After context filter: {len(detections)}")
|
| 840 |
+
|
| 841 |
+
if not detections:
|
| 842 |
+
return []
|
| 843 |
+
|
| 844 |
+
# Step 5: ํ์ฒ๋ฆฌ
|
| 845 |
+
detections = self._post_process(detections, image.shape)
|
| 846 |
+
print(f"[STEP 5] Final detections: {len(detections)}")
|
| 847 |
+
|
| 848 |
+
return detections
|
| 849 |
+
|
| 850 |
+
def _pre_filter(self, detections: List[Dict]) -> List[Dict]:
|
| 851 |
+
"""์ฌ์ ํํฐ: COCO ํด๋์ค + ์ ๋ขฐ๋"""
|
| 852 |
+
|
| 853 |
+
# ์ ์ธํ COCO ํด๋์ค
|
| 854 |
+
EXCLUDE_CLASSES = {0} # person (์ ๋ฑ)
|
| 855 |
+
|
| 856 |
+
filtered = []
|
| 857 |
+
for det in detections:
|
| 858 |
+
# ํด๋์ค ํํฐ
|
| 859 |
+
if det.get('class_id', -1) in EXCLUDE_CLASSES:
|
| 860 |
+
continue
|
| 861 |
+
|
| 862 |
+
# ์ ๋ขฐ๋ ํํฐ
|
| 863 |
+
if det['confidence'] < self.params['min_confidence']:
|
| 864 |
+
continue
|
| 865 |
+
|
| 866 |
+
filtered.append(det)
|
| 867 |
+
|
| 868 |
+
return filtered
|
| 869 |
+
|
| 870 |
+
def _physical_filter(self, detections: List[Dict], image_shape: Tuple) -> List[Dict]:
|
| 871 |
+
"""๋ฌผ๋ฆฌ์ ํํฐ: ํฌ๊ธฐ, ํํ"""
|
| 872 |
+
|
| 873 |
+
h, w = image_shape[:2]
|
| 874 |
+
image_area = h * w
|
| 875 |
+
|
| 876 |
+
filtered = []
|
| 877 |
+
for det in detections:
|
| 878 |
+
bbox = det['bbox']
|
| 879 |
+
x1, y1, x2, y2 = bbox
|
| 880 |
+
|
| 881 |
+
bbox_width = x2 - x1
|
| 882 |
+
bbox_height = y2 - y1
|
| 883 |
+
bbox_area = bbox_width * bbox_height
|
| 884 |
+
|
| 885 |
+
# ์ ๋ ํฌ๊ธฐ ํํฐ
|
| 886 |
+
if bbox_width < self.params['min_width']:
|
| 887 |
+
continue
|
| 888 |
+
if bbox_height < self.params['min_height']:
|
| 889 |
+
continue
|
| 890 |
+
if bbox_area < self.params['min_area']:
|
| 891 |
+
continue
|
| 892 |
+
|
| 893 |
+
# ์๋ ํฌ๊ธฐ ํํฐ
|
| 894 |
+
area_ratio = bbox_area / image_area
|
| 895 |
+
if not (self.params['min_area_ratio'] < area_ratio < self.params['max_area_ratio']):
|
| 896 |
+
continue
|
| 897 |
+
|
| 898 |
+
# ์ข
ํก๋น ํํฐ
|
| 899 |
+
if bbox_height == 0:
|
| 900 |
+
continue
|
| 901 |
+
aspect_ratio = bbox_width / bbox_height
|
| 902 |
+
if not (self.params['min_aspect_ratio'] < aspect_ratio < self.params['max_aspect_ratio']):
|
| 903 |
+
continue
|
| 904 |
+
|
| 905 |
+
filtered.append(det)
|
| 906 |
+
|
| 907 |
+
return filtered
|
| 908 |
+
|
| 909 |
+
def _visual_filter(self, image: np.ndarray, detections: List[Dict]) -> List[Dict]:
|
| 910 |
+
"""์๊ฐ์ ํํฐ: ์์, ํ
์ค์ฒ"""
|
| 911 |
+
|
| 912 |
+
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
|
| 913 |
+
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 914 |
+
|
| 915 |
+
filtered = []
|
| 916 |
+
for det in detections:
|
| 917 |
+
bbox = det['bbox']
|
| 918 |
+
x1, y1, x2, y2 = map(int, bbox)
|
| 919 |
+
|
| 920 |
+
# ROI ์ถ์ถ
|
| 921 |
+
roi_hsv = hsv_image[y1:y2, x1:x2]
|
| 922 |
+
roi_gray = gray_image[y1:y2, x1:x2]
|
| 923 |
+
|
| 924 |
+
if roi_hsv.size == 0 or roi_gray.size == 0:
|
| 925 |
+
continue
|
| 926 |
+
|
| 927 |
+
# ์์ ํํฐ
|
| 928 |
+
mean_h, mean_s, mean_v = roi_hsv.mean(axis=(0, 1))
|
| 929 |
+
if not (mean_s < self.params['max_saturation'] and mean_v > self.params['min_value']):
|
| 930 |
+
continue
|
| 931 |
+
|
| 932 |
+
# ํ
์ค์ฒ ํํฐ
|
| 933 |
+
texture_std = np.std(roi_gray)
|
| 934 |
+
if texture_std > self.params['max_texture_std']:
|
| 935 |
+
continue
|
| 936 |
+
|
| 937 |
+
filtered.append(det)
|
| 938 |
+
|
| 939 |
+
return filtered
|
| 940 |
+
|
| 941 |
+
def _context_filter(self, detections: List[Dict], image_shape: Tuple) -> List[Dict]:
|
| 942 |
+
"""์ปจํ
์คํธ ํํฐ: ์ ๊ฒ์ถ, ์ค์ฌ์ฑ"""
|
| 943 |
+
|
| 944 |
+
# ์ ๊ฒ์ถ
|
| 945 |
+
rulers = self._detect_rulers(detections)
|
| 946 |
+
|
| 947 |
+
# ์ค์ฌ์ฑ ์ ์ ์ถ๊ฐ
|
| 948 |
+
for det in detections:
|
| 949 |
+
det['centrality_score'] = self._calculate_centrality(det['bbox'], image_shape)
|
| 950 |
+
|
| 951 |
+
# ์ ๊ทผ์ ๋ ํํฐ (์๊ฐ ์์ผ๋ฉด)
|
| 952 |
+
if rulers:
|
| 953 |
+
filtered = []
|
| 954 |
+
for det in detections:
|
| 955 |
+
if self._is_near_ruler(det, rulers):
|
| 956 |
+
filtered.append(det)
|
| 957 |
+
return filtered if filtered else detections
|
| 958 |
+
else:
|
| 959 |
+
return detections
|
| 960 |
+
|
| 961 |
+
def _detect_rulers(self, detections: List[Dict]) -> List[Dict]:
|
| 962 |
+
"""์ ๊ฒ์ถ"""
|
| 963 |
+
rulers = []
|
| 964 |
+
for det in detections:
|
| 965 |
+
bbox = det['bbox']
|
| 966 |
+
width = bbox[2] - bbox[0]
|
| 967 |
+
height = bbox[3] - bbox[1]
|
| 968 |
+
|
| 969 |
+
if height > 0:
|
| 970 |
+
aspect_ratio = width / height
|
| 971 |
+
if aspect_ratio > self.params['ruler_min_aspect'] or aspect_ratio < self.params['ruler_max_aspect']:
|
| 972 |
+
rulers.append(det)
|
| 973 |
+
|
| 974 |
+
return rulers
|
| 975 |
+
|
| 976 |
+
def _is_near_ruler(self, det: Dict, rulers: List[Dict]) -> bool:
|
| 977 |
+
"""์ ๊ทผ์ฒ์ ์๋์ง ํ์ธ"""
|
| 978 |
+
det_center = np.array([
|
| 979 |
+
(det['bbox'][0] + det['bbox'][2]) / 2,
|
| 980 |
+
(det['bbox'][1] + det['bbox'][3]) / 2
|
| 981 |
+
])
|
| 982 |
+
|
| 983 |
+
for ruler in rulers:
|
| 984 |
+
ruler_center = np.array([
|
| 985 |
+
(ruler['bbox'][0] + ruler['bbox'][2]) / 2,
|
| 986 |
+
(ruler['bbox'][1] + ruler['bbox'][3]) / 2
|
| 987 |
+
])
|
| 988 |
+
distance = np.linalg.norm(det_center - ruler_center)
|
| 989 |
+
|
| 990 |
+
if self.params['min_ruler_distance'] < distance < self.params['max_ruler_distance']:
|
| 991 |
+
return True
|
| 992 |
+
|
| 993 |
+
return False
|
| 994 |
+
|
| 995 |
+
def _calculate_centrality(self, bbox: List[float], image_shape: Tuple) -> float:
|
| 996 |
+
"""์ค์ฌ์ฑ ์ ์ ๊ณ์ฐ"""
|
| 997 |
+
h, w = image_shape[:2]
|
| 998 |
+
image_center = np.array([w / 2, h / 2])
|
| 999 |
+
|
| 1000 |
+
bbox_center = np.array([
|
| 1001 |
+
(bbox[0] + bbox[2]) / 2,
|
| 1002 |
+
(bbox[1] + bbox[3]) / 2
|
| 1003 |
+
])
|
| 1004 |
+
|
| 1005 |
+
distance = np.linalg.norm(bbox_center - image_center)
|
| 1006 |
+
max_distance = np.linalg.norm(np.array([w / 2, h / 2]))
|
| 1007 |
+
|
| 1008 |
+
return 1 - (distance / max_distance)
|
| 1009 |
+
|
| 1010 |
+
def _post_process(self, detections: List[Dict], image_shape: Tuple) -> List[Dict]:
|
| 1011 |
+
"""ํ์ฒ๋ฆฌ: ์ข
ํฉ ์ ์, NMS"""
|
| 1012 |
+
|
| 1013 |
+
# ์ข
ํฉ ์ ์ ๊ณ์ฐ
|
| 1014 |
+
for det in detections:
|
| 1015 |
+
det['composite_score'] = self._calculate_composite_score(det, image_shape)
|
| 1016 |
+
|
| 1017 |
+
# ์ ์์ ์ ๋ ฌ
|
| 1018 |
+
detections = sorted(detections, key=lambda x: x['composite_score'], reverse=True)
|
| 1019 |
+
|
| 1020 |
+
# NMS
|
| 1021 |
+
detections = self._apply_nms(detections)
|
| 1022 |
+
|
| 1023 |
+
# ์์ N๊ฐ
|
| 1024 |
+
return detections[:self.params['top_n']]
|
| 1025 |
+
|
| 1026 |
+
def _calculate_composite_score(self, det: Dict, image_shape: Tuple) -> float:
|
| 1027 |
+
"""์ข
ํฉ ์ ์ ๊ณ์ฐ (0~100)"""
|
| 1028 |
+
score = 0.0
|
| 1029 |
+
|
| 1030 |
+
# ์ ๋ขฐ๋ (30%)
|
| 1031 |
+
score += det['confidence'] * 0.30
|
| 1032 |
+
|
| 1033 |
+
# ์ค์ฌ์ฑ (15%)
|
| 1034 |
+
if 'centrality_score' in det:
|
| 1035 |
+
score += det['centrality_score'] * 0.15
|
| 1036 |
+
|
| 1037 |
+
# ์ข
ํก๋น ์ ํฉ๋ (20%)
|
| 1038 |
+
bbox = det['bbox']
|
| 1039 |
+
width = bbox[2] - bbox[0]
|
| 1040 |
+
height = bbox[3] - bbox[1]
|
| 1041 |
+
if height > 0:
|
| 1042 |
+
aspect_ratio = width / height
|
| 1043 |
+
ideal_aspect = 4.5
|
| 1044 |
+
aspect_fitness = max(0, 1 - abs(aspect_ratio - ideal_aspect) / ideal_aspect)
|
| 1045 |
+
score += aspect_fitness * 0.20
|
| 1046 |
+
|
| 1047 |
+
# ํฌ๊ธฐ ์ ํฉ๋ (20%)
|
| 1048 |
+
h, w = image_shape[:2]
|
| 1049 |
+
bbox_area = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])
|
| 1050 |
+
area_ratio = bbox_area / (h * w)
|
| 1051 |
+
size_fitness = 1.0 if 0.10 < area_ratio < 0.30 else 0.5
|
| 1052 |
+
score += size_fitness * 0.20
|
| 1053 |
+
|
| 1054 |
+
# ์์ ์ ํฉ๋ (15%)
|
| 1055 |
+
score += 0.15
|
| 1056 |
+
|
| 1057 |
+
return score * 100
|
| 1058 |
+
|
| 1059 |
+
def _apply_nms(self, detections: List[Dict]) -> List[Dict]:
|
| 1060 |
+
"""NMS ์ ์ฉ"""
|
| 1061 |
+
if len(detections) <= 1:
|
| 1062 |
+
return detections
|
| 1063 |
+
|
| 1064 |
+
keep = []
|
| 1065 |
+
detections = sorted(detections, key=lambda x: x['confidence'], reverse=True)
|
| 1066 |
+
|
| 1067 |
+
while detections:
|
| 1068 |
+
best = detections.pop(0)
|
| 1069 |
+
keep.append(best)
|
| 1070 |
+
|
| 1071 |
+
filtered = []
|
| 1072 |
+
for det in detections:
|
| 1073 |
+
iou = self._calculate_iou(best['bbox'], det['bbox'])
|
| 1074 |
+
if iou < self.params['nms_iou_threshold']:
|
| 1075 |
+
filtered.append(det)
|
| 1076 |
+
|
| 1077 |
+
detections = filtered
|
| 1078 |
+
|
| 1079 |
+
return keep
|
| 1080 |
+
|
| 1081 |
+
@staticmethod
|
| 1082 |
+
def _calculate_iou(bbox1: List[float], bbox2: List[float]) -> float:
|
| 1083 |
+
"""IoU ๊ณ์ฐ"""
|
| 1084 |
+
x1_min, y1_min, x1_max, y1_max = bbox1
|
| 1085 |
+
x2_min, y2_min, x2_max, y2_max = bbox2
|
| 1086 |
+
|
| 1087 |
+
inter_x_min = max(x1_min, x2_min)
|
| 1088 |
+
inter_y_min = max(y1_min, y2_min)
|
| 1089 |
+
inter_x_max = min(x1_max, x2_max)
|
| 1090 |
+
inter_y_max = min(y1_max, y2_max)
|
| 1091 |
+
|
| 1092 |
+
if inter_x_max < inter_x_min or inter_y_max < inter_y_min:
|
| 1093 |
+
return 0.0
|
| 1094 |
+
|
| 1095 |
+
inter_area = (inter_x_max - inter_x_min) * (inter_y_max - inter_y_min)
|
| 1096 |
+
|
| 1097 |
+
area1 = (x1_max - x1_min) * (y1_max - y1_min)
|
| 1098 |
+
area2 = (x2_max - x2_min) * (y2_max - y2_min)
|
| 1099 |
+
union_area = area1 + area2 - inter_area
|
| 1100 |
+
|
| 1101 |
+
return inter_area / union_area if union_area > 0 else 0.0
|
| 1102 |
+
|
| 1103 |
+
|
| 1104 |
+
# ์ฌ์ฉ ์์
|
| 1105 |
+
if __name__ == "__main__":
|
| 1106 |
+
# RT-DETR ๊ฒ์ถ ๊ฒฐ๊ณผ (์์)
|
| 1107 |
+
rtdetr_detections = [
|
| 1108 |
+
{'bbox': [100, 200, 400, 250], 'confidence': 0.85, 'class_id': 1},
|
| 1109 |
+
{'bbox': [50, 50, 100, 500], 'confidence': 0.92, 'class_id': 2}, # ์
|
| 1110 |
+
{'bbox': [200, 300, 350, 380], 'confidence': 0.45, 'class_id': 0}, # ์
|
| 1111 |
+
]
|
| 1112 |
+
|
| 1113 |
+
# ์ด๋ฏธ์ง ๋ก๋
|
| 1114 |
+
image = cv2.imread("test_image.jpg")
|
| 1115 |
+
|
| 1116 |
+
# ํํฐ ์ ์ฉ
|
| 1117 |
+
smart_filter = SmartShrimpFilter()
|
| 1118 |
+
shrimp_detections = smart_filter.filter(image, rtdetr_detections)
|
| 1119 |
+
|
| 1120 |
+
print(f"Final shrimp detections: {len(shrimp_detections)}")
|
| 1121 |
+
for i, det in enumerate(shrimp_detections, 1):
|
| 1122 |
+
print(f" Shrimp #{i}: confidence={det['confidence']:.2f}, score={det['composite_score']:.2f}")
|
| 1123 |
+
```
|
| 1124 |
+
|
| 1125 |
+
---
|
| 1126 |
+
|
| 1127 |
+
## ๐งช ํ
์คํธ ๋ฐ ๊ฒ์ฆ
|
| 1128 |
+
|
| 1129 |
+
### ํ
์คํธ ์๋๋ฆฌ์ค (MECE)
|
| 1130 |
+
|
| 1131 |
+
```
|
| 1132 |
+
A. ์ด๋ฏธ์ง ์ ํ๋ณ
|
| 1133 |
+
โโโ A1. ์์ฐ๋ง (๋ฐฐ๊ฒฝ ๊นจ๋)
|
| 1134 |
+
โโโ A2. ์์ฐ + ์
|
| 1135 |
+
โ๏ฟฝ๏ฟฝโ A3. ์์ฐ + ์ + ์
|
| 1136 |
+
โโโ A4. ์์ฐ + ๋ณต์กํ ๋ฐฐ๊ฒฝ
|
| 1137 |
+
โโโ A5. ์์ฐ ์์ (Negative)
|
| 1138 |
+
|
| 1139 |
+
B. ์์ฐ ์์น๋ณ
|
| 1140 |
+
โโโ B1. ์ค์
|
| 1141 |
+
โโโ B2. ์ข์ธก
|
| 1142 |
+
โโโ B3. ์ฐ์ธก
|
| 1143 |
+
โโโ B4. ์๋จ
|
| 1144 |
+
โโโ B5. ํ๋จ
|
| 1145 |
+
|
| 1146 |
+
C. ์์ฐ ํฌ๊ธฐ๋ณ
|
| 1147 |
+
โโโ C1. ์์ ์์ฐ (< 8cm)
|
| 1148 |
+
โโโ C2. ์ค๊ฐ ์์ฐ (8-12cm)
|
| 1149 |
+
โโโ C3. ํฐ ์์ฐ (> 12cm)
|
| 1150 |
+
|
| 1151 |
+
D. ์กฐ๋ช
์กฐ๊ฑด๋ณ
|
| 1152 |
+
โโโ D1. ๋ฐ์ ์กฐ๋ช
|
| 1153 |
+
โโโ D2. ์ด๋์ด ์กฐ๋ช
|
| 1154 |
+
โโโ D3. ๊ทธ๋ฆผ์ ํฌํจ
|
| 1155 |
+
```
|
| 1156 |
+
|
| 1157 |
+
### ํ๊ฐ ์งํ
|
| 1158 |
+
|
| 1159 |
+
```python
|
| 1160 |
+
def evaluate_performance(predictions, ground_truths):
|
| 1161 |
+
"""์ฑ๋ฅ ํ๊ฐ"""
|
| 1162 |
+
|
| 1163 |
+
TP = 0 # True Positive: ์์ฐ๋ฅผ ์์ฐ๋ก ๊ฒ์ถ
|
| 1164 |
+
FP = 0 # False Positive: ์์ฐ ์๋ ๊ฒ์ ์์ฐ๋ก ๊ฒ์ถ
|
| 1165 |
+
FN = 0 # False Negative: ์์ฐ๋ฅผ ๊ฒ์ถ ๋ชปํจ
|
| 1166 |
+
|
| 1167 |
+
# ... IoU ๊ธฐ๋ฐ ๋งค์นญ ๋ก์ง ...
|
| 1168 |
+
|
| 1169 |
+
precision = TP / (TP + FP) if (TP + FP) > 0 else 0
|
| 1170 |
+
recall = TP / (TP + FN) if (TP + FN) > 0 else 0
|
| 1171 |
+
f1_score = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0
|
| 1172 |
+
|
| 1173 |
+
return {
|
| 1174 |
+
'precision': precision,
|
| 1175 |
+
'recall': recall,
|
| 1176 |
+
'f1_score': f1_score
|
| 1177 |
+
}
|
| 1178 |
+
```
|
| 1179 |
+
|
| 1180 |
+
### ํ๋ผ๋ฏธํฐ ํ๋
|
| 1181 |
+
|
| 1182 |
+
```python
|
| 1183 |
+
# ํ๋ผ๋ฏธํฐ ๊ทธ๋ฆฌ๋ ์์น
|
| 1184 |
+
param_grid = {
|
| 1185 |
+
'min_confidence': [0.10, 0.15, 0.20],
|
| 1186 |
+
'min_aspect_ratio': [1.0, 1.5, 2.0],
|
| 1187 |
+
'max_aspect_ratio': [8.0, 10.0, 12.0],
|
| 1188 |
+
'max_saturation': [100, 120, 150],
|
| 1189 |
+
}
|
| 1190 |
+
|
| 1191 |
+
best_f1 = 0
|
| 1192 |
+
best_params = None
|
| 1193 |
+
|
| 1194 |
+
for params in itertools.product(*param_grid.values()):
|
| 1195 |
+
# ํ
์คํธ...
|
| 1196 |
+
f1 = evaluate(params)
|
| 1197 |
+
if f1 > best_f1:
|
| 1198 |
+
best_f1 = f1
|
| 1199 |
+
best_params = params
|
| 1200 |
+
```
|
| 1201 |
+
|
| 1202 |
+
---
|
| 1203 |
+
|
| 1204 |
+
## ๐ ์์ ์ฑ๋ฅ
|
| 1205 |
+
|
| 1206 |
+
### ์๋๋ฆฌ์ค๋ณ ์ ํ๋
|
| 1207 |
+
|
| 1208 |
+
| ์๋๋ฆฌ์ค | ์์ Precision | ์์ Recall | ์์ F1 |
|
| 1209 |
+
|---------|---------------|-------------|---------|
|
| 1210 |
+
| ์์ฐ๋ง (๊นจ๋) | 85% | 90% | 87% |
|
| 1211 |
+
| ์์ฐ + ์ | 75% | 85% | 80% |
|
| 1212 |
+
| ์์ฐ + ์ + ์ | 65% | 75% | 70% |
|
| 1213 |
+
| ๋ณต์กํ ๋ฐฐ๊ฒฝ | 55% | 65% | 60% |
|
| 1214 |
+
| **ํ๊ท ** | **70%** | **79%** | **74%** |
|
| 1215 |
+
|
| 1216 |
+
### ์๋ (CPU ๊ธฐ์ค)
|
| 1217 |
+
|
| 1218 |
+
- **RT-DETR ์ถ๋ก **: 1.0์ด
|
| 1219 |
+
- **ํํฐ๋ง ํ์ดํ๋ผ์ธ**: 0.3์ด
|
| 1220 |
+
- **์ด ์ฒ๋ฆฌ ์๊ฐ**: ~1.3์ด/์ด๋ฏธ์ง
|
| 1221 |
+
|
| 1222 |
+
---
|
| 1223 |
+
|
| 1224 |
+
## ๐ง ์ถ๊ฐ ๊ฐ์ ๋ฐฉ์
|
| 1225 |
+
|
| 1226 |
+
### ๋จ๊ธฐ (1์ฃผ)
|
| 1227 |
+
1. **ํ๋ผ๋ฏธํฐ ์๋ ํ๋**: Grid Search๋ก ์ต์ ํ๋ผ๋ฏธํฐ ์ฐพ๊ธฐ
|
| 1228 |
+
2. **๋ก๊น
์์คํ
**: ๊ฐ ํํฐ ๋จ๊ณ๋ณ ํต๊ณ ์์ง
|
| 1229 |
+
3. **์คํจ ์ฌ๋ก ๋ถ์**: ๊ฒ์ถ ์คํจ ์ด๋ฏธ์ง ๋ถ์ ๋ฐ ๊ฐ์
|
| 1230 |
+
|
| 1231 |
+
### ์ค๊ธฐ (2-4์ฃผ)
|
| 1232 |
+
1. **์์๋ธ**: ์ฌ๋ฌ ํํฐ ์กฐํฉ ์์๋ธ
|
| 1233 |
+
2. **ํ์ต ๊ธฐ๋ฐ ํ์ฒ๋ฆฌ**: ๊ฐ๋จํ ๋ถ๋ฅ๊ธฐ ์ถ๊ฐ (์์ฐ vs ๋น์์ฐ)
|
| 1234 |
+
3. **์ฌ์ฉ์ ํผ๋๋ฐฑ**: ์๋ ๋ผ๋ฒจ๋ง ๊ฒฐ๊ณผ ๋ฐ์
|
| 1235 |
+
|
| 1236 |
+
### ์ฅ๊ธฐ
|
| 1237 |
+
1. **Roboflow ํ์ธํ๋**: ๊ทผ๋ณธ์ ํด๊ฒฐ
|
| 1238 |
+
2. **์์ฒด ๋ชจ๋ธ ํ์ต**: YOLOv8 ๋ฑ
|
| 1239 |
+
|
| 1240 |
+
---
|
| 1241 |
+
|
| 1242 |
+
## ๐ ์ฐธ๊ณ
|
| 1243 |
+
|
| 1244 |
+
- [RT-DETR Paper](https://arxiv.org/abs/2304.08069)
|
| 1245 |
+
- [COCO Dataset Classes](https://cocodataset.org/#explore)
|
| 1246 |
+
- [OpenCV Documentation](https://docs.opencv.org/)
|
| 1247 |
+
|
| 1248 |
+
---
|
| 1249 |
+
|
| 1250 |
+
**์์ฑ์ผ**: 2025-11-07
|
| 1251 |
+
**๋ฒ์ **: 1.0 (MECE)
|
| 1252 |
+
**์์ฑ์**: VIDraft Team
|
docs/testing_framework_guide.md
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐งช ์์ฐ ๊ฒ์ถ ํ
์คํธ ํ๋ ์์ํฌ ๊ฐ์ด๋
|
| 2 |
+
|
| 3 |
+
## ๐ ๊ฐ์
|
| 4 |
+
|
| 5 |
+
์ด ๋ฌธ์๋ RT-DETR + Universal Shrimp Filter ๊ฒ์ถ ์์คํ
์ ํ
์คํธ ๋ฐ ๊ฒ์ฆ ํ๋ ์์ํฌ ์ฌ์ฉ ๊ฐ์ด๋์
๋๋ค.
|
| 6 |
+
|
| 7 |
+
์์ฑ์ผ: 2025-11-09
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## ๐ฏ ํ
์คํธ ํ๋ ์์ํฌ ๊ตฌ์ฑ
|
| 12 |
+
|
| 13 |
+
### 1. **์๊ฐ์ ๊ฒ์ฆ** (`test_visual_validation.py`)
|
| 14 |
+
- **๋ชฉ์ **: ๊ฒ์ถ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ฏธ์ง๋ก ์๊ฐํํ์ฌ ๋น ๋ฅด๊ฒ ํ์ธ
|
| 15 |
+
- **์ฌ์ฉ ์๊ธฐ**: ์ด๊ธฐ ํ
์คํธ, ํ๋ผ๋ฏธํฐ ์กฐ์ ์ ํ ๋น๊ต
|
| 16 |
+
- **์ถ๋ ฅ**: ๋ณ๋ ฌ ๋น๊ต ์ด๋ฏธ์ง (์ ์ฒด ๊ฒ์ถ vs ํํฐ๋ง ํ)
|
| 17 |
+
|
| 18 |
+
### 2. **์ ๋์ ํ๊ฐ** (`test_quantitative_evaluation.py`)
|
| 19 |
+
- **๋ชฉ์ **: Ground Truth์ ๋น๊ตํ์ฌ ์ ํ๋ ๋ฉํธ๋ฆญ ๊ณ์ฐ
|
| 20 |
+
- **์ฌ์ฉ ์๊ธฐ**: ์ ํํ ์ฑ๋ฅ ์ธก์ , ๋ชจ๋ธ ํ๋
|
| 21 |
+
- **์ถ๋ ฅ**: Precision, Recall, F1 Score, Confusion Matrix, PR Curve
|
| 22 |
+
|
| 23 |
+
### 3. **๋ํํ ๊ฒ์ฆ** (`interactive_validation.py`)
|
| 24 |
+
- **๋ชฉ์ **: ์ค์๊ฐ์ผ๋ก ํ๋ผ๋ฏธํฐ ์กฐ์ ํ๋ฉฐ ๊ฒฐ๊ณผ ํ์ธ
|
| 25 |
+
- **์ฌ์ฉ ์๊ธฐ**: ํ๋ผ๋ฏธํฐ ์ต์ ํ, ๋๋ฒ๊น
|
| 26 |
+
- **์ถ๋ ฅ**: Gradio ์น ์ธํฐํ์ด์ค
|
| 27 |
+
|
| 28 |
+
---
|
| 29 |
+
|
| 30 |
+
## ๐ ์ฌ์ฉ ๋ฐฉ๋ฒ
|
| 31 |
+
|
| 32 |
+
### 1๏ธโฃ ์๊ฐ์ ๊ฒ์ฆ ํ
์คํธ
|
| 33 |
+
|
| 34 |
+
**์คํ:**
|
| 35 |
+
```bash
|
| 36 |
+
python test_visual_validation.py
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
**์ค์ ๋ณ๊ฒฝ:**
|
| 40 |
+
```python
|
| 41 |
+
run_visual_test(
|
| 42 |
+
test_image_dir="imgs", # ํ
์คํธ ์ด๋ฏธ์ง ๋๋ ํ ๋ฆฌ
|
| 43 |
+
confidence=0.3, # RT-DETR ์ ๋ขฐ๋ (0.1~0.9)
|
| 44 |
+
filter_threshold=50 # ํํฐ ์ ์ ์๊ณ๊ฐ (20~80)
|
| 45 |
+
)
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
**์ถ๋ ฅ:**
|
| 49 |
+
- `test_results/result_*.png` - ์๊ฐํ ๊ฒฐ๊ณผ ์ด๋ฏธ์ง
|
| 50 |
+
- `test_results/test_results_YYYYMMDD_HHMMSS.json` - ์์ธ ๊ฒ์ถ ์ ๋ณด
|
| 51 |
+
|
| 52 |
+
**๊ฒฐ๊ณผ ํด์:**
|
| 53 |
+
- **์ผ์ชฝ ์ด๋ฏธ์ง**: ์ ์ฒด ๊ฒ์ถ (ํ์ ๋ฐ์ค) + ํํฐ๋ง ํต๊ณผ (์ปฌ๋ฌ ๋ฐ์ค)
|
| 54 |
+
- **์ค๋ฅธ์ชฝ ์ด๋ฏธ์ง**: ํํฐ๋ง ํต๊ณผ๋ง ํ์
|
| 55 |
+
- **์์ ์๋ฏธ**:
|
| 56 |
+
- ๐ข ๋
น์ (75์ ์ด์): ๋์ ํ๋ฅ ๋ก ์์ฐ
|
| 57 |
+
- ๐ก ๋
ธ๋์ (50~74์ ): ์ค๊ฐ ํ๋ฅ
|
| 58 |
+
- ๐ ์ฃผํฉ์ (50์ ๋ฏธ๋ง): ๋ฎ์ ํ๋ฅ
|
| 59 |
+
|
| 60 |
+
---
|
| 61 |
+
|
| 62 |
+
### 2๏ธโฃ ์ ๋์ ํ๊ฐ ํ
์คํธ
|
| 63 |
+
|
| 64 |
+
**์ฌ์ ์ค๋น:**
|
| 65 |
+
1. `ground_truth.json` ํ์ผ ์์ฑ
|
| 66 |
+
2. ์๋์ผ๋ก ๋ฐ์ด๋ฉ ๋ฐ์ค ๋ผ๋ฒจ๋ง
|
| 67 |
+
|
| 68 |
+
**ground_truth.json ํ์:**
|
| 69 |
+
```json
|
| 70 |
+
{
|
| 71 |
+
"test_shrimp_tank.png": [
|
| 72 |
+
{"bbox": [100, 150, 200, 180], "label": "shrimp"},
|
| 73 |
+
{"bbox": [300, 250, 420, 290], "label": "shrimp"}
|
| 74 |
+
],
|
| 75 |
+
"image (1).webp": [
|
| 76 |
+
{"bbox": [500, 600, 800, 700], "label": "shrimp"}
|
| 77 |
+
]
|
| 78 |
+
}
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
**์คํ:**
|
| 82 |
+
```bash
|
| 83 |
+
python test_quantitative_evaluation.py
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
**์ถ๋ ฅ:**
|
| 87 |
+
- `test_results/quantitative_*/eval_*.png` - ํ๊ฐ ๊ฒฐ๊ณผ ์๊ฐํ
|
| 88 |
+
- `test_results/quantitative_*/confusion_matrix.png` - ํผ๋ ํ๋ ฌ
|
| 89 |
+
- `test_results/quantitative_*/evaluation_summary.json` - ํ๊ฐ ์์ฝ
|
| 90 |
+
|
| 91 |
+
**๋ฉํธ๋ฆญ ํด์:**
|
| 92 |
+
- **Precision (์ ๋ฐ๋)**: ๊ฒ์ถํ ๊ฒ ์ค ์ค์ ์์ฐ ๋น์จ (๋์์๋ก ์ค๊ฒ์ถ ์ ์)
|
| 93 |
+
- **Recall (์ฌํ์จ)**: ์ค์ ์์ฐ ์ค ๊ฒ์ถํ ๋น์จ (๋์์๋ก ๋ฏธ๊ฒ์ถ ์ ์)
|
| 94 |
+
- **F1 Score**: Precision๊ณผ Recall์ ์กฐํ ํ๊ท (์ ์ฒด ์ฑ๋ฅ ์งํ)
|
| 95 |
+
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
### 3๏ธโฃ ๋ํํ ๊ฒ์ฆ
|
| 99 |
+
|
| 100 |
+
**์คํ:**
|
| 101 |
+
```bash
|
| 102 |
+
python interactive_validation.py
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
**์ ์:**
|
| 106 |
+
```
|
| 107 |
+
http://localhost:7861
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
**์ฌ์ฉ๋ฒ:**
|
| 111 |
+
|
| 112 |
+
**ํญ 1: ๐ค ์๋ ๊ฒ์ถ**
|
| 113 |
+
1. ์ด๋ฏธ์ง ์
๋ก๋
|
| 114 |
+
2. ์ฌ๋ผ์ด๋๋ก ํ๋ผ๋ฏธํฐ ์กฐ์ :
|
| 115 |
+
- **RT-DETR ์ ๋ขฐ๋** (0.1~0.9): ๋ฎ์์๋ก ๋ ๋ง์ด ๊ฒ์ถ
|
| 116 |
+
- **ํํฐ ์ ์ ์๊ณ๊ฐ** (20~80): ๋์์๋ก ์๊ฒฉํ๊ฒ ํํฐ๋ง
|
| 117 |
+
3. "์ ์ฒด ๊ฒ์ถ ๊ฒฐ๊ณผ ํ์" ์ฒดํฌ๋ฐ์ค: ํํฐ๋ง ์ ํ ๋น๊ต
|
| 118 |
+
4. "๐ ๊ฒ์ถ ์คํ" ํด๋ฆญ
|
| 119 |
+
|
| 120 |
+
**ํญ 2: ๐ฌ ์๋ ๋ถ์**
|
| 121 |
+
1. ์ด๋ฏธ์ง ์
๋ก๋
|
| 122 |
+
2. ๋ฐ์ด๋ฉ ๋ฐ์ค ์ขํ ์
๋ ฅ (x1, y1, x2, y2)
|
| 123 |
+
3. "๐ ๋ถ์ ์คํ" ํด๋ฆญ
|
| 124 |
+
4. ํด๋น ์์ญ์ ํน์ง ๋ถ์ ๊ฒฐ๊ณผ ํ์ธ
|
| 125 |
+
|
| 126 |
+
---
|
| 127 |
+
|
| 128 |
+
## ๐ ์ค์ ํ
์คํธ ๊ฒฐ๊ณผ ์์
|
| 129 |
+
|
| 130 |
+
### ํ
์คํธ ์์ฝ (2025-11-09)
|
| 131 |
+
|
| 132 |
+
**์ค์ :**
|
| 133 |
+
- RT-DETR Confidence: 0.3
|
| 134 |
+
- Filter Threshold: 50
|
| 135 |
+
|
| 136 |
+
**๊ฒฐ๊ณผ:**
|
| 137 |
+
|
| 138 |
+
| ์ด๋ฏธ์ง | ์ ์ฒด ๊ฒ์ถ | ํํฐ๋ง ํ | ํํฐ๋ง๋ฅ |
|
| 139 |
+
|--------|----------|-----------|---------|
|
| 140 |
+
| test_shrimp_tank.png | 7๊ฐ | 2๊ฐ | 71% |
|
| 141 |
+
| ํ๋ฉด ์บก์ฒ 2025-10-15 142552.png | 3๊ฐ | 2๊ฐ | 33% |
|
| 142 |
+
| ํ๋ฉด ์บก์ฒ 2025-10-15 170508.png | 2๊ฐ | 1๊ฐ | 50% |
|
| 143 |
+
| test_shrimp_tank_roboflow_result.jpg | 8๊ฐ | 3๊ฐ | 63% |
|
| 144 |
+
| image (1).webp | 2๊ฐ | 2๊ฐ | 0% |
|
| 145 |
+
| image.webp | 7๊ฐ | 1๊ฐ | 86% |
|
| 146 |
+
| image_original.webp | 1๊ฐ | 1๊ฐ | 0% |
|
| 147 |
+
| preview.webp | 7๊ฐ | 2๊ฐ | 71% |
|
| 148 |
+
|
| 149 |
+
**ํ๊ท ํํฐ๋ง๋ฅ : 47%** (RT-DETR ์ค๊ฒ์ถ์ ์ ๋ฐ์ ์ ๊ฑฐ)
|
| 150 |
+
|
| 151 |
+
---
|
| 152 |
+
|
| 153 |
+
## ๐ง ํ๋ผ๋ฏธํฐ ํ๋ ๊ฐ์ด๋
|
| 154 |
+
|
| 155 |
+
### ๊ฒ์ถ์ด ๋๋ฌด ์ ์ ๋
|
| 156 |
+
|
| 157 |
+
**์ฆ์:**
|
| 158 |
+
- ์ค์ ์์ฐ๊ฐ ๊ฒ์ถ๋์ง ์์
|
| 159 |
+
- Recall์ด ๋ฎ์
|
| 160 |
+
|
| 161 |
+
**ํด๊ฒฐ:**
|
| 162 |
+
1. **RT-DETR ์ ๋ขฐ๋ ๋ฎ์ถ๊ธฐ**: 0.3 โ 0.2
|
| 163 |
+
2. **ํํฐ ์ ์ ์๊ณ๊ฐ ๋ฎ์ถ๊ธฐ**: 50 โ 40
|
| 164 |
+
3. **ํํฐ ๊ฐ์ค์น ์กฐ์ ** (๊ณ ๊ธ):
|
| 165 |
+
```python
|
| 166 |
+
# test_visual_validation.py์ apply_universal_filter() ์์
|
| 167 |
+
# ์๊ฒฉํ ๊ธฐ์ค ์ํ
|
| 168 |
+
if 1.5 <= morph['aspect_ratio'] <= 12.0: # ๊ธฐ์กด: 2.0~10.0
|
| 169 |
+
score += 15
|
| 170 |
+
```
|
| 171 |
+
|
| 172 |
+
### ์ค๊ฒ์ถ์ด ๋ง์ ๋
|
| 173 |
+
|
| 174 |
+
**์ฆ์:**
|
| 175 |
+
- ์์ฐ๊ฐ ์๋ ๊ฒ๋ ๊ฒ์ถ๋จ
|
| 176 |
+
- Precision์ด ๋ฎ์
|
| 177 |
+
|
| 178 |
+
**ํด๊ฒฐ:**
|
| 179 |
+
1. **ํํฐ ์ ์ ์๊ณ๊ฐ ๋์ด๊ธฐ**: 50 โ 60
|
| 180 |
+
2. **RT-DETR ์ ๋ขฐ๋ ๋์ด๊ธฐ**: 0.3 โ 0.4
|
| 181 |
+
3. **ํํฐ ์กฐ๊ฑด ๊ฐํ** (๊ณ ๊ธ):
|
| 182 |
+
```python
|
| 183 |
+
# ์ฑ๋ ์กฐ๊ฑด ๊ฐํ
|
| 184 |
+
if visual['saturation'] < 120: # ๊ธฐ์กด: 150
|
| 185 |
+
score += 20
|
| 186 |
+
```
|
| 187 |
+
|
| 188 |
+
### F1 Score๊ฐ ๋ฎ์ ๋
|
| 189 |
+
|
| 190 |
+
**์์ธ ๋ถ์:**
|
| 191 |
+
1. **RT-DETR ๋ฌธ์ **: ๊ฒ์ถ ์์ฒด๊ฐ ์ ๋จ
|
| 192 |
+
- ํด๊ฒฐ: RT-DETR ์ ๋ขฐ๋ ์กฐ์
|
| 193 |
+
2. **ํํฐ ๋ฌธ์ **: ์์ฐ๋ฅผ ์๋ชป ๊ฑธ๋ฌ๋
|
| 194 |
+
- ํด๊ฒฐ: ํํฐ ์กฐ๊ฑด ์ํ
|
| 195 |
+
3. **๋ฐ์ดํฐ ๋ฌธ์ **: ํ์ต ๋ฐ์ดํฐ์ ํ
์คํธ ๋ฐ์ดํฐ ์ฐจ์ด
|
| 196 |
+
- ํด๊ฒฐ: ๋ชจ๋ธ ์ฌํ์ต (Roboflow ์ธก์ ์ด๋ฏธ์ง ์ถ๊ฐ)
|
| 197 |
+
|
| 198 |
+
---
|
| 199 |
+
|
| 200 |
+
## ๐ ์ฑ๋ฅ ๋ชฉํ
|
| 201 |
+
|
| 202 |
+
### ์์กฐ ์ด๋ฏธ์ง (Live Shrimp)
|
| 203 |
+
- **VIDraft/Shrimp ๋ชจ๋ธ** (๊ถ์ฅ)
|
| 204 |
+
- Precision: > 95%
|
| 205 |
+
- Recall: > 90%
|
| 206 |
+
- F1 Score: > 92%
|
| 207 |
+
|
| 208 |
+
### ์ธก์ ์ด๋ฏธ์ง (Dead Shrimp)
|
| 209 |
+
- **RT-DETR + Universal Filter**
|
| 210 |
+
- Precision: > 80%
|
| 211 |
+
- Recall: > 75%
|
| 212 |
+
- F1 Score: > 77%
|
| 213 |
+
|
| 214 |
+
### ํ์ฌ ์ฑ๋ฅ (์ถ์ )
|
| 215 |
+
- ์๊ฐ์ ๊ฒ์ฆ ๊ฒฐ๊ณผ๋ก ๋ณผ ๋ **F1 ~70%** ์์
|
| 216 |
+
- ์ ๋์ ํ๊ฐ ํ์ (ground_truth.json ์์ฑ ํ)
|
| 217 |
+
|
| 218 |
+
---
|
| 219 |
+
|
| 220 |
+
## ๐จ ํํฐ ์ ์ ๊ตฌ์ฑ
|
| 221 |
+
|
| 222 |
+
์ด 100์ ๋ง์ :
|
| 223 |
+
|
| 224 |
+
| ํน์ง | ๋ฐฐ์ | ์กฐ๊ฑด |
|
| 225 |
+
|------|------|------|
|
| 226 |
+
| ์ข
ํก๋น (Aspect Ratio) | 15์ | 2.0 ~ 10.0 |
|
| 227 |
+
| ์ธ์ฅ๋ (Compactness) | 15์ | < 0.25 |
|
| 228 |
+
| ๋ฉด์ ๋น (Area Ratio) | 10์ | 5% ~ 50% |
|
| 229 |
+
| ์ฑ๋ (Saturation) | 20์ | < 150 |
|
| 230 |
+
| ์์ ์ผ๊ด์ฑ (Color Std) | 15์ | < 30 |
|
| 231 |
+
| RT-DETR ์ ๋ขฐ๋ | 25์ | confidence ร 25 |
|
| 232 |
+
|
| 233 |
+
**ํฉ๊ณ:** 100์
|
| 234 |
+
|
| 235 |
+
**์๊ณ๊ฐ:**
|
| 236 |
+
- **75์ ์ด์**: ๋์ ํ๋ฅ ๋ก ์์ฐ โ
|
| 237 |
+
- **50~74์ **: ์ค๊ฐ ํ๋ฅ โ ๏ธ
|
| 238 |
+
- **50์ ๋ฏธ๋ง**: ์์ฐ ์๋ โ
|
| 239 |
+
|
| 240 |
+
---
|
| 241 |
+
|
| 242 |
+
## ๐ ํธ๋ฌ๋ธ์ํ
|
| 243 |
+
|
| 244 |
+
### ๋ฌธ์ 1: ModuleNotFoundError
|
| 245 |
+
|
| 246 |
+
**์๋ฌ:**
|
| 247 |
+
```
|
| 248 |
+
ModuleNotFoundError: No module named 'cv2'
|
| 249 |
+
```
|
| 250 |
+
|
| 251 |
+
**ํด๊ฒฐ:**
|
| 252 |
+
```bash
|
| 253 |
+
pip install opencv-python matplotlib seaborn transformers torch torchvision
|
| 254 |
+
```
|
| 255 |
+
|
| 256 |
+
### ๋ฌธ์ 2: Ground Truth ํ์ผ ์์
|
| 257 |
+
|
| 258 |
+
**์๋ฌ:**
|
| 259 |
+
```
|
| 260 |
+
โ ๏ธ Ground truth ํ์ผ์ด ์์ต๋๋ค: ground_truth.json
|
| 261 |
+
```
|
| 262 |
+
|
| 263 |
+
**ํด๊ฒฐ:**
|
| 264 |
+
1. `ground_truth.json` ํ์ผ ์์ฑ
|
| 265 |
+
2. ๊ฐ ์ด๋ฏธ์ง์ ๋ฐ์ด๋ฉ ๋ฐ์ค ์ขํ ์
๋ ฅ
|
| 266 |
+
3. ์ขํ๋ ์ด๋ฏธ์ง ๋ทฐ์ด๋ Gradio ๋ํํ ๋๊ตฌ๋ก ํ์ธ
|
| 267 |
+
|
| 268 |
+
### ๋ฌธ์ 3: ๋ชจ๋ธ ๋ค์ด๋ก๋ ๋๋ฆผ
|
| 269 |
+
|
| 270 |
+
**์ฆ์:**
|
| 271 |
+
RT-DETR ๋ชจ๋ธ ๋ค์ด๋ก๋์ ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆผ
|
| 272 |
+
|
| 273 |
+
**ํด๊ฒฐ:**
|
| 274 |
+
- ์ฒ์ ํ ๋ฒ๋ง ๋ค์ด๋ก๋๋จ (์บ์ ์ฌ์ฉ)
|
| 275 |
+
- ๋คํธ์ํฌ ์ฐ๊ฒฐ ํ์ธ
|
| 276 |
+
- Hugging Face Hub ์ ์ ํ์ธ
|
| 277 |
+
|
| 278 |
+
---
|
| 279 |
+
|
| 280 |
+
## ๐ ํ์ผ ๊ตฌ์กฐ
|
| 281 |
+
|
| 282 |
+
```
|
| 283 |
+
D:\Project\VIDraft\Shrimp\
|
| 284 |
+
โโโ test_visual_validation.py # ์๊ฐ์ ๊ฒ์ฆ ์คํฌ๋ฆฝํธ
|
| 285 |
+
โโโ test_quantitative_evaluation.py # ์ ๋์ ํ๊ฐ ์คํฌ๋ฆฝํธ
|
| 286 |
+
โโโ interactive_validation.py # ๋ํํ ๊ฒ์ฆ ์ธํฐํ์ด์ค
|
| 287 |
+
โโโ ground_truth.json # Ground Truth ๋ผ๋ฒจ (์๋ ์์ฑ)
|
| 288 |
+
โโโ imgs/ # ํ
์คํธ ์ด๋ฏธ์ง
|
| 289 |
+
โ โโโ test_shrimp_tank.png
|
| 290 |
+
โ โโโ image (1).webp
|
| 291 |
+
โ โโโ ...
|
| 292 |
+
โโโ test_results/ # ํ
์คํธ ๊ฒฐ๊ณผ (์๋ ์์ฑ)
|
| 293 |
+
โ โโโ result_*.png
|
| 294 |
+
โ โโโ test_results_*.json
|
| 295 |
+
โ โโโ quantitative_*/
|
| 296 |
+
โ โโโ eval_*.png
|
| 297 |
+
โ โโโ confusion_matrix.png
|
| 298 |
+
โ โโโ evaluation_summary.json
|
| 299 |
+
โโโ docs/
|
| 300 |
+
โโโ testing_framework_guide.md # ์ด ๋ฌธ์
|
| 301 |
+
โโโ detection_testing_and_validation.md
|
| 302 |
+
```
|
| 303 |
+
|
| 304 |
+
---
|
| 305 |
+
|
| 306 |
+
## ๐ ์ํฌํ๋ก์ฐ ๊ถ์ฅ์ฌํญ
|
| 307 |
+
|
| 308 |
+
### 1๋จ๊ณ: ์ด๊ธฐ ๊ฒ์ฆ (์๊ฐ์ )
|
| 309 |
+
```bash
|
| 310 |
+
python test_visual_validation.py
|
| 311 |
+
```
|
| 312 |
+
- ๊ฒฐ๊ณผ ์ด๋ฏธ์ง ํ์ธ
|
| 313 |
+
- ์ค๊ฒ์ถ/๋ฏธ๊ฒ์ถ ํจํด ํ์
|
| 314 |
+
|
| 315 |
+
### 2๋จ๊ณ: ํ๋ผ๋ฏธํฐ ์ต์ ํ (๋ํํ)
|
| 316 |
+
```bash
|
| 317 |
+
python interactive_validation.py
|
| 318 |
+
```
|
| 319 |
+
- ์ค์๊ฐ์ผ๋ก ํ๋ผ๋ฏธํฐ ์กฐ์
|
| 320 |
+
- ์ต์ ์ค์ ์ฐพ๊ธฐ
|
| 321 |
+
|
| 322 |
+
### 3๋จ๊ณ: ์ ํ๋ ์ธก์ (์ ๋์ )
|
| 323 |
+
```bash
|
| 324 |
+
# ground_truth.json ์์ฑ ํ
|
| 325 |
+
python test_quantitative_evaluation.py
|
| 326 |
+
```
|
| 327 |
+
- Precision, Recall, F1 ๊ณ์ฐ
|
| 328 |
+
- ์ฑ๋ฅ ๋ชฉํ ๋๋น ํ๊ฐ
|
| 329 |
+
|
| 330 |
+
### 4๋จ๊ณ: ๋ฐ๋ณต ๊ฐ์
|
| 331 |
+
- ์ฑ๋ฅ ๋ฏธ๋ฌ ์ ํ๋ผ๋ฏธํฐ ์ฌ์กฐ์
|
| 332 |
+
- ํ์ ์ ํํฐ ๋ก์ง ์์
|
| 333 |
+
- ์ฌํ
์คํธ
|
| 334 |
+
|
| 335 |
+
---
|
| 336 |
+
|
| 337 |
+
## ๐ ์ถ๊ฐ ๊ฐ์ ์ฌํญ
|
| 338 |
+
|
| 339 |
+
### ๋จ๊ธฐ (์ฆ์ ๊ฐ๋ฅ)
|
| 340 |
+
1. โ
์๊ฐ์ ๊ฒ์ฆ ์คํฌ๋ฆฝํธ - **์๋ฃ**
|
| 341 |
+
2. โ
์ ๋์ ํ๊ฐ ์คํฌ๋ฆฝํธ - **์๋ฃ**
|
| 342 |
+
3. โ
๋ํํ ๊ฒ์ฆ ์ธํฐํ์ด์ค - **์๋ฃ**
|
| 343 |
+
4. โณ Ground Truth ๋ฐ์ดํฐ ์์ฑ - **์งํ ํ์**
|
| 344 |
+
5. โณ ํ๋ผ๋ฏธํฐ ํ๋ - **์งํ ํ์**
|
| 345 |
+
|
| 346 |
+
### ์ค๊ธฐ (1-2์ฃผ)
|
| 347 |
+
1. โณ ์ธก์ ์ด๋ฏธ์ง 50~100์ฅ์ผ๋ก Roboflow ๋ชจ๋ธ ์ฌํ์ต
|
| 348 |
+
2. โณ ๋๊ท๋ชจ ๋ฐ์ดํฐ์
ํ
์คํธ (100+ ์ด๋ฏธ์ง)
|
| 349 |
+
3. โณ ๊ต์ฐจ ๊ฒ์ฆ (Cross-validation) ๊ตฌํ
|
| 350 |
+
|
| 351 |
+
### ์ฅ๊ธฐ (1๊ฐ์+)
|
| 352 |
+
1. โณ ์์๋ธ ๋ชจ๋ธ (Roboflow + RT-DETR ์กฐํฉ)
|
| 353 |
+
2. โณ ์๋ ๋ผ๋ฒจ๋ง ๋๊ตฌ ๊ฐ๋ฐ
|
| 354 |
+
3. โณ CI/CD ํ์ดํ๋ผ์ธ ๊ตฌ์ถ
|
| 355 |
+
|
| 356 |
+
---
|
| 357 |
+
|
| 358 |
+
## ๐ FAQ
|
| 359 |
+
|
| 360 |
+
### Q1: ๋ช ๊ฐ์ ํ
์คํธ ์ด๋ฏธ์ง๊ฐ ํ์ํ๊ฐ์?
|
| 361 |
+
|
| 362 |
+
**A:**
|
| 363 |
+
- **์ต์**: 20~30์ฅ (๋ค์ํ ๋ฐฐ๊ฒฝ, ์กฐ๋ช
, ๊ฐ๋)
|
| 364 |
+
- **๊ถ์ฅ**: 50~100์ฅ (ํต๊ณ์ ์ ๋ขฐ๋ ํ๋ณด)
|
| 365 |
+
- **์ด์์ **: 200+ ์ฅ (ํ๋ก๋์
์์ค)
|
| 366 |
+
|
| 367 |
+
### Q2: Ground Truth๋ ์ด๋ป๊ฒ ๋ง๋๋์?
|
| 368 |
+
|
| 369 |
+
**A:**
|
| 370 |
+
1. **์๋ ๋ฐฉ๋ฒ**: ๊ทธ๋ฆผํ/ํฌํ ์ต์ผ๋ก ์ขํ ํ์ธ
|
| 371 |
+
2. **๋๏ฟฝ๏ฟฝ๏ฟฝํ ๋ฐฉ๋ฒ**: `interactive_validation.py`์ "์๋ ๋ถ์" ํญ ํ์ฉ
|
| 372 |
+
3. **๋ผ๋ฒจ๋ง ๋๊ตฌ**: [LabelImg](https://github.com/tzutalin/labelImg), [CVAT](https://github.com/opencv/cvat) ์ฌ์ฉ
|
| 373 |
+
4. JSON ํ์์ผ๋ก ์ ์ฅ
|
| 374 |
+
|
| 375 |
+
### Q3: ์ด๋ค ํ
์คํธ๋ฅผ ๋จผ์ ํด์ผ ํ๋์?
|
| 376 |
+
|
| 377 |
+
**A:**
|
| 378 |
+
1. **์์**: ์๊ฐ์ ๊ฒ์ฆ (๋น ๋ฅด๊ณ ์ง๊ด์ )
|
| 379 |
+
2. **์ต์ ํ**: ๋ํํ ๊ฒ์ฆ (ํ๋ผ๋ฏธํฐ ์ฐพ๊ธฐ)
|
| 380 |
+
3. **๊ฒ์ฆ**: ์ ๋์ ํ๊ฐ (์ ํ๋ ํ์ธ)
|
| 381 |
+
|
| 382 |
+
### Q4: ํํฐ ์ ์ ์๊ณ๊ฐ์ ์ด๋ป๊ฒ ์ ํ๋์?
|
| 383 |
+
|
| 384 |
+
**A:**
|
| 385 |
+
- **๋ณด์์ (๋์ ์ ๋ฐ๋)**: 60~70์
|
| 386 |
+
- **๊ท ํ์ (๊ธฐ๋ณธ)**: 50์
|
| 387 |
+
- **๊ณต๊ฒฉ์ (๋์ ์ฌํ์จ)**: 40~45์
|
| 388 |
+
|
| 389 |
+
PR Curve๋ฅผ ๋ณด๊ณ ์ต์ F1 ์ง์ ์ ํ
|
| 390 |
+
|
| 391 |
+
---
|
| 392 |
+
|
| 393 |
+
## ๐ ๋ฌธ์
|
| 394 |
+
|
| 395 |
+
ํ๋ ์์ํฌ ๊ด๋ จ ๋ฌธ์ ๋ ๋ค์์ ํ์ธํ์ธ์:
|
| 396 |
+
1. ์ด ๋ฌธ์์ ํธ๋ฌ๋ธ์ํ
์น์
|
| 397 |
+
2. `docs/detection_testing_and_validation.md` (์์ธ ๊ธฐ์ ๋ฌธ์)
|
| 398 |
+
3. ๊ฐ ์คํฌ๋ฆฝํธ์ ์ฃผ์ ๋ฐ docstring
|
| 399 |
+
|
| 400 |
+
---
|
| 401 |
+
|
| 402 |
+
**๋ง์ง๋ง ์
๋ฐ์ดํธ:** 2025-11-09
|
| 403 |
+
**๋ฒ์ :** 1.0
|
| 404 |
+
**์์ฑ์:** Claude Code
|
docs/universal_shrimp_detection_strategy.md
ADDED
|
@@ -0,0 +1,883 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐ฏ ๋ฒ์ฉ ์์ฐ ๊ฒ์ถ ์ ๋ต (RT-DETR + ๋ณธ์ง์ ํน์ง ํํฐ๋ง)
|
| 2 |
+
|
| 3 |
+
## ๐ ํต์ฌ ์์น
|
| 4 |
+
|
| 5 |
+
**๋ฒ์ฉ์ฑ (Universality)**
|
| 6 |
+
- โ
๋ฐฐ๊ฒฝ ๋ฌด๊ด: ๋งคํธ, ์์กฐ, ์ ์, ์๋ฐ๋ฅ ๋ฑ ์ด๋๋
|
| 7 |
+
- โ
์ฃผ๋ณ ๊ฐ์ฒด ๋ฌด๊ด: ์, ์, ์ ๋ฐ ๋ฑ ์์ด๋ ๊ฒ์ถ
|
| 8 |
+
- โ
์์น ๋ฌด๊ด: ์ด๋ฏธ์ง ์ด๋๋ (์ค์, ๋ชจ์๋ฆฌ ๋ฑ)
|
| 9 |
+
- โ
์กฐ๋ช
๋ฌด๊ด: ๋ฐ์ ๊ณณ, ์ด๋์ด ๊ณณ ๋ชจ๋
|
| 10 |
+
- โ
ํฌ๊ธฐ ๋ฌด๊ด: ์์ ์์ฐ๋ถํฐ ํฐ ์์ฐ๊น์ง
|
| 11 |
+
|
| 12 |
+
**ํต์ฌ ์์ด๋์ด**
|
| 13 |
+
> **"์์ฐ์ ๋ณธ์ง์ ํน์ง๋ง์ผ๋ก ๊ตฌ๋ณ"**
|
| 14 |
+
> - ์ธ๋ถ ์ปจํ
์คํธ(์, ๋งคํธ ๋ฑ)์ ์์กดํ์ง ์์
|
| 15 |
+
> - ์์ฐ ์์ฒด์ ํํํ์ /์๊ฐ์ ํน์ง ํ์ฉ
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
## ๐ฌ ์์ฐ์ ๋ณธ์ง์ ํน์ง (Intrinsic Features)
|
| 20 |
+
|
| 21 |
+
### 1. ํํํ์ ํน์ง (Morphological)
|
| 22 |
+
|
| 23 |
+
```
|
| 24 |
+
์์ฐ ํํ์ ๋ถ๋ณ ํน์ฑ (๋ฐฐ๊ฒฝ/์์น ๋ฌด๊ด)
|
| 25 |
+
โโโ 1.1 ์ธ์ฅํ (Elongated)
|
| 26 |
+
โ โโโ ์ข
ํก๋น: 2:1 ~ 10:1 (๊ฐ๋ก๋ก ๊ธธ๋ค)
|
| 27 |
+
โ โโโ ๋์นญ์ฑ: ์ข์ฐ ๋์นญ
|
| 28 |
+
โ
|
| 29 |
+
โโโ 1.2 ๋ถ์ ๊ตฌ์กฐ (Segmented Body)
|
| 30 |
+
โ โโโ ๋จธ๋ฆฌ-๊ฐ์ด-๊ผฌ๋ฆฌ 3๋ถํ
|
| 31 |
+
โ โโโ ์ฃผ๊ธฐ์ ํจํด (๋ง๋)
|
| 32 |
+
โ
|
| 33 |
+
โโโ 1.3 ๊ณก์ ํ ์ธ๊ณฝ์ (Curved Contour)
|
| 34 |
+
โ โโโ ๋ถ๋๋ฌ์ด ๊ณก์ (์ง์ ์์)
|
| 35 |
+
โ โโโ 'C์' ๋๋ 'ใฑ์' ํํ (์ฃฝ์ ์์ฐ)
|
| 36 |
+
โ
|
| 37 |
+
โโโ 1.4 ํฌ๊ธฐ ๋ฒ์
|
| 38 |
+
โโโ ์ ๋ ํฌ๊ธฐ: 3cm ~ 20cm (์ค์ )
|
| 39 |
+
โโโ ์ด๋ฏธ์ง ๋ด ๋น์จ: 5% ~ 40%
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
### 2. ์๊ฐ์ ํน์ง (Visual)
|
| 43 |
+
|
| 44 |
+
```
|
| 45 |
+
์์ฐ ์ธํ์ ๋ถ๋ณ ํน์ฑ
|
| 46 |
+
โโโ 2.1 ์์ (Color)
|
| 47 |
+
โ โโโ ์ด์์์: ํ์/๊ฐ์ (๋ฎ์ ์ฑ๋)
|
| 48 |
+
โ โโโ ์ฃฝ์: ํฐ์/ํฌ๋ช
(๋งค์ฐ ๋ฎ์ ์ฑ๋)
|
| 49 |
+
โ โโโ ๊ณตํต: ๋จ์ ๋๋ ๋ฏธ์ธํ ๊ทธ๋ผ๋ฐ์ด์
|
| 50 |
+
โ
|
| 51 |
+
โโโ 2.2 ํฌ๋ช
๋/๋ฐํฌ๋ช
(Translucency)
|
| 52 |
+
โ โโโ ์ฃฝ์ ์์ฐ: ๋์ ํฌ๋ช
๋
|
| 53 |
+
โ โโโ ๋ฐฐ๊ฒฝ์ด ๋น์นจ
|
| 54 |
+
โ
|
| 55 |
+
โโโ 2.3 ํ
์ค์ฒ (Texture)
|
| 56 |
+
โ โโโ ๋ถ๋๋ฌ์ด ํ๋ฉด (๋ฎ์ ํ์คํธ์ฐจ)
|
| 57 |
+
โ โโโ ๋ฏธ์ธํ ์ค๋ฌด๋ฌ (๋ถ์ )
|
| 58 |
+
โ โโโ ๊ดํ (ํ์ด๋ผ์ดํธ)
|
| 59 |
+
โ
|
| 60 |
+
โโโ 2.4 ๊ฒฝ๊ณ์ (Boundary)
|
| 61 |
+
โโโ ๋ถ๋๋ฌ์ด ์์ง (soft edge)
|
| 62 |
+
โโโ ๋ช
ํํ ์ค๋ฃจ์ฃ
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
### 3. ๊ตฌ์กฐ์ ํน์ง (Structural)
|
| 66 |
+
|
| 67 |
+
```
|
| 68 |
+
์์ฐ ๋ด๋ถ ๊ตฌ์กฐ (์ฃฝ์ ์์ฐ ํนํ)
|
| 69 |
+
โโโ 3.1 ๋ด๋ถ ์ฅ๊ธฐ ๊ฐ์์ฑ
|
| 70 |
+
โ โโโ ๊ฒ์ ์ (์ํ๊ด)
|
| 71 |
+
โ โโโ ํฌ๋ช
ํ ๋ชธ์ฒด ๋ด๋ถ ๊ตฌ์กฐ
|
| 72 |
+
โ
|
| 73 |
+
โโโ 3.2 ๋ค๋ฆฌ/๋๋ฌ์ด
|
| 74 |
+
โ โโโ ๊ฐ๋ ์ ํํ
|
| 75 |
+
โ โโโ ๋ชธ์ฒด์์ ๋ฐฉ์ฌํ์ผ๋ก ๋ป์
|
| 76 |
+
โ
|
| 77 |
+
โโโ 3.3 ๊ผฌ๋ฆฌ ๋ถ์ฑ ๊ตฌ์กฐ
|
| 78 |
+
โโโ ๋ถ์ฑ๊ผด ํผ์ณ์ง ํํ
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
---
|
| 82 |
+
|
| 83 |
+
## ๐๏ธ ๋ฒ์ฉ ํํฐ๋ง ์ ๋ต (MECE)
|
| 84 |
+
|
| 85 |
+
### Level 1: ํํฐ ๋ถ๋ฅ (๋ฐฐ๊ฒฝ ์์กด๋ ๊ธฐ์ค)
|
| 86 |
+
|
| 87 |
+
```
|
| 88 |
+
๋ฐฐ๊ฒฝ ๋
๋ฆฝ ํํฐ (Background-Independent) โญโญโญ
|
| 89 |
+
โโโ ํํ ํํฐ (Shape Filter)
|
| 90 |
+
โโโ ์์ ํํฐ (Color Filter)
|
| 91 |
+
โโโ ํ
์ค์ฒ ํํฐ (Texture Filter)
|
| 92 |
+
|
| 93 |
+
๋ฐฐ๊ฒฝ ์์กด ํํฐ (Background-Dependent) โ ๏ธ
|
| 94 |
+
โโโ ์ฌ์ฉ ์ ํจ (๋ฒ์ฉ์ฑ ์ ํด)
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
### Level 2: ํํฐ๋ง ํ์ดํ๋ผ์ธ (3๋จ๊ณ)
|
| 98 |
+
|
| 99 |
+
```
|
| 100 |
+
Step 1: ํํ ๊ธฐ๋ฐ 1์ฐจ ํํฐ (Shape-based Primary Filter)
|
| 101 |
+
โ ์์ฐ์ ๋ช
๋ฐฑํ ๋ค๋ฅธ ํํ ์ ๊ฑฐ
|
| 102 |
+
โ ์ข
ํก๋น, ํฌ๊ธฐ, ์ธ๊ณฝ์ ๊ณก๋ฅ
|
| 103 |
+
|
| 104 |
+
Step 2: ์์/ํ
์ค์ฒ ๊ธฐ๋ฐ 2์ฐจ ํํฐ (Visual Secondary Filter)
|
| 105 |
+
โ ์์ฐ์ ์๊ฐ์ ํน์ง์ผ๋ก ๊ฒ์ฆ
|
| 106 |
+
โ HSV ์๊ณต๊ฐ, ํ
์ค์ฒ ๋ถ์
|
| 107 |
+
|
| 108 |
+
Step 3: ์ ๋ฐ ๊ฒ์ฆ (Fine-grained Verification)
|
| 109 |
+
โ ๊ตฌ์กฐ์ ํน์ง์ผ๋ก ์ต์ข
ํ์ธ
|
| 110 |
+
โ ๋ด๋ถ ๊ตฌ์กฐ, ๋ถ์ ํจํด
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
---
|
| 114 |
+
|
| 115 |
+
## ๐ป ๋ฒ์ฉ ํํฐ ๊ตฌํ
|
| 116 |
+
|
| 117 |
+
### Step 1: ํํ ๊ธฐ๋ฐ 1์ฐจ ํํฐ
|
| 118 |
+
|
| 119 |
+
#### 1.1 ์ข
ํก๋น ํํฐ (Aspect Ratio)
|
| 120 |
+
|
| 121 |
+
**์๋ฆฌ**: ์์ฐ๋ ํญ์ ๊ฐ๋ก๋ก ๊ธธ๋ค (๋ฐฐ๊ฒฝ ๋ฌด๊ด)
|
| 122 |
+
|
| 123 |
+
```python
|
| 124 |
+
def filter_by_aspect_ratio(detections, min_ratio=2.0, max_ratio=10.0):
|
| 125 |
+
"""
|
| 126 |
+
์ข
ํก๋น ํํฐ
|
| 127 |
+
|
| 128 |
+
Args:
|
| 129 |
+
min_ratio: ์ต์ ๊ฐ๋ก/์ธ๋ก ๋น์จ (๊ธฐ๋ณธ 2.0)
|
| 130 |
+
max_ratio: ์ต๋ ๊ฐ๋ก/์ธ๋ก ๋น์จ (๊ธฐ๋ณธ 10.0)
|
| 131 |
+
"""
|
| 132 |
+
filtered = []
|
| 133 |
+
|
| 134 |
+
for det in detections:
|
| 135 |
+
bbox = det['bbox']
|
| 136 |
+
width = bbox[2] - bbox[0]
|
| 137 |
+
height = bbox[3] - bbox[1]
|
| 138 |
+
|
| 139 |
+
if height == 0:
|
| 140 |
+
continue
|
| 141 |
+
|
| 142 |
+
# ๊ฐ๋ก/์ธ๋ก ๋น์จ ๊ณ์ฐ
|
| 143 |
+
aspect_ratio = width / height
|
| 144 |
+
|
| 145 |
+
# ์ธ๋ก๋ก ๊ธด ๊ฒฝ์ฐ๋ ๊ณ ๋ ค (์์ฐ๊ฐ ์ธ๋ก๋ก ๋์ธ ๊ฒฝ์ฐ)
|
| 146 |
+
if aspect_ratio < 1.0:
|
| 147 |
+
aspect_ratio = 1.0 / aspect_ratio
|
| 148 |
+
|
| 149 |
+
# ๋ฒ์ ์ฒดํฌ
|
| 150 |
+
if min_ratio <= aspect_ratio <= max_ratio:
|
| 151 |
+
det['aspect_ratio'] = aspect_ratio
|
| 152 |
+
filtered.append(det)
|
| 153 |
+
|
| 154 |
+
return filtered
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
#### 1.2 ์ธ๊ณฝ์ ๊ณก๋ฅ ํํฐ (Contour Curvature)
|
| 158 |
+
|
| 159 |
+
**์๋ฆฌ**: ์์ฐ๋ ๋ถ๋๋ฌ์ด ๊ณก์ , ์๋ ์ฑ
๋ฑ์ ์ง์
|
| 160 |
+
|
| 161 |
+
```python
|
| 162 |
+
def filter_by_curvature(image, detections, max_line_ratio=0.3):
|
| 163 |
+
"""
|
| 164 |
+
์ธ๊ณฝ์ ๊ณก๋ฅ ํํฐ
|
| 165 |
+
|
| 166 |
+
Args:
|
| 167 |
+
max_line_ratio: ์ต๋ ์ง์ ๋น์จ (0~1)
|
| 168 |
+
"""
|
| 169 |
+
import cv2
|
| 170 |
+
import numpy as np
|
| 171 |
+
|
| 172 |
+
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 173 |
+
filtered = []
|
| 174 |
+
|
| 175 |
+
for det in detections:
|
| 176 |
+
bbox = det['bbox']
|
| 177 |
+
x1, y1, x2, y2 = map(int, bbox)
|
| 178 |
+
|
| 179 |
+
# ROI ์ถ์ถ
|
| 180 |
+
roi = gray[y1:y2, x1:x2]
|
| 181 |
+
if roi.size == 0:
|
| 182 |
+
continue
|
| 183 |
+
|
| 184 |
+
# ์ธ๊ณฝ์ ๊ฒ์ถ
|
| 185 |
+
_, binary = cv2.threshold(roi, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
| 186 |
+
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
| 187 |
+
|
| 188 |
+
if not contours:
|
| 189 |
+
continue
|
| 190 |
+
|
| 191 |
+
# ๊ฐ์ฅ ํฐ ์ธ๊ณฝ์
|
| 192 |
+
contour = max(contours, key=cv2.contourArea)
|
| 193 |
+
|
| 194 |
+
# ์ธ๊ณฝ์ ๊ทผ์ฌ (์ง์ ๊ฒ์ถ)
|
| 195 |
+
epsilon = 0.02 * cv2.arcLength(contour, True)
|
| 196 |
+
approx = cv2.approxPolyDP(contour, epsilon, True)
|
| 197 |
+
|
| 198 |
+
# ์ง์ ๋น์จ = ๊ทผ์ฌ ์ ๊ฐ์ / ์๋ณธ ์ ๊ฐ์
|
| 199 |
+
line_ratio = len(approx) / len(contour) if len(contour) > 0 else 1.0
|
| 200 |
+
|
| 201 |
+
# ๋ถ๋๋ฌ์ด ๊ณก์ = ์ง์ ๋น์จ ๋ฎ์
|
| 202 |
+
if line_ratio < max_line_ratio:
|
| 203 |
+
det['curvature_score'] = 1.0 - line_ratio
|
| 204 |
+
filtered.append(det)
|
| 205 |
+
|
| 206 |
+
return filtered
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
#### 1.3 ์ปดํฉํธ๋์ค ํํฐ (Compactness)
|
| 210 |
+
|
| 211 |
+
**์๋ฆฌ**: ์์ฐ๋ ์ธ์ฅํ์ด๋ฏ๋ก compactness๊ฐ ๋ฎ์
|
| 212 |
+
|
| 213 |
+
```python
|
| 214 |
+
def filter_by_compactness(image, detections, max_compactness=0.25):
|
| 215 |
+
"""
|
| 216 |
+
์ปดํฉํธ๋์ค ํํฐ
|
| 217 |
+
|
| 218 |
+
Compactness = 4ฯ ร Area / Perimeterยฒ
|
| 219 |
+
- ์: 1.0
|
| 220 |
+
- ์ ์ฌ๊ฐํ: 0.785
|
| 221 |
+
- ์ธ์ฅํ: < 0.3
|
| 222 |
+
|
| 223 |
+
Args:
|
| 224 |
+
max_compactness: ์ต๋ ์ปดํฉํธ๋์ค (๊ธฐ๋ณธ 0.25)
|
| 225 |
+
"""
|
| 226 |
+
import cv2
|
| 227 |
+
import numpy as np
|
| 228 |
+
|
| 229 |
+
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 230 |
+
filtered = []
|
| 231 |
+
|
| 232 |
+
for det in detections:
|
| 233 |
+
bbox = det['bbox']
|
| 234 |
+
x1, y1, x2, y2 = map(int, bbox)
|
| 235 |
+
|
| 236 |
+
roi = gray[y1:y2, x1:x2]
|
| 237 |
+
if roi.size == 0:
|
| 238 |
+
continue
|
| 239 |
+
|
| 240 |
+
# ์ธ๊ณฝ์ ๊ฒ์ถ
|
| 241 |
+
_, binary = cv2.threshold(roi, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
| 242 |
+
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
| 243 |
+
|
| 244 |
+
if not contours:
|
| 245 |
+
continue
|
| 246 |
+
|
| 247 |
+
contour = max(contours, key=cv2.contourArea)
|
| 248 |
+
area = cv2.contourArea(contour)
|
| 249 |
+
perimeter = cv2.arcLength(contour, True)
|
| 250 |
+
|
| 251 |
+
if perimeter == 0:
|
| 252 |
+
continue
|
| 253 |
+
|
| 254 |
+
# ์ปดํฉํธ๋์ค ๊ณ์ฐ
|
| 255 |
+
compactness = (4 * np.pi * area) / (perimeter ** 2)
|
| 256 |
+
|
| 257 |
+
# ์ธ์ฅํ = ๋ฎ์ ์ปดํฉํธ๋์ค
|
| 258 |
+
if compactness < max_compactness:
|
| 259 |
+
det['compactness'] = compactness
|
| 260 |
+
filtered.append(det)
|
| 261 |
+
|
| 262 |
+
return filtered
|
| 263 |
+
```
|
| 264 |
+
|
| 265 |
+
#### 1.4 ํฌ๊ธฐ ์ ์ํ ํํฐ (Adaptive Size Filter)
|
| 266 |
+
|
| 267 |
+
**์๋ฆฌ**: ์ด๋ฏธ์ง ํด์๋์ ๋ฐ๋ผ ์ ์์ ์ผ๋ก ํฌ๊ธฐ ํ๋จ
|
| 268 |
+
|
| 269 |
+
```python
|
| 270 |
+
def filter_by_adaptive_size(detections, image_shape, min_ratio=0.05, max_ratio=0.50):
|
| 271 |
+
"""
|
| 272 |
+
์ ์ํ ํฌ๊ธฐ ํํฐ
|
| 273 |
+
|
| 274 |
+
Args:
|
| 275 |
+
min_ratio: ์ต์ ๋ฉด์ ๋น์จ (์ด๋ฏธ์ง์ 5%)
|
| 276 |
+
max_ratio: ์ต๋ ๋ฉด์ ๋น์จ (์ด๋ฏธ์ง์ 50%)
|
| 277 |
+
"""
|
| 278 |
+
h, w = image_shape[:2]
|
| 279 |
+
image_area = h * w
|
| 280 |
+
|
| 281 |
+
filtered = []
|
| 282 |
+
|
| 283 |
+
for det in detections:
|
| 284 |
+
bbox = det['bbox']
|
| 285 |
+
bbox_area = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])
|
| 286 |
+
area_ratio = bbox_area / image_area
|
| 287 |
+
|
| 288 |
+
# ๋๋ฌด ์๊ฑฐ๋ ํฌ๋ฉด ์ ์ธ
|
| 289 |
+
if min_ratio <= area_ratio <= max_ratio:
|
| 290 |
+
det['area_ratio'] = area_ratio
|
| 291 |
+
filtered.append(det)
|
| 292 |
+
|
| 293 |
+
return filtered
|
| 294 |
+
```
|
| 295 |
+
|
| 296 |
+
---
|
| 297 |
+
|
| 298 |
+
### Step 2: ์์/ํ
์ค์ฒ ๊ธฐ๋ฐ 2์ฐจ ํํฐ
|
| 299 |
+
|
| 300 |
+
#### 2.1 ์์ ์ผ๊ด์ฑ ํํฐ (Color Consistency)
|
| 301 |
+
|
| 302 |
+
**์๋ฆฌ**: ์์ฐ๋ ๋จ์ ๋๋ ๋ฏธ์ธํ ๊ทธ๋ผ๋ฐ์ด์
(๊ฐํ ํจํด ์์)
|
| 303 |
+
|
| 304 |
+
```python
|
| 305 |
+
def filter_by_color_consistency(image, detections, max_std=50):
|
| 306 |
+
"""
|
| 307 |
+
์์ ์ผ๊ด์ฑ ํํฐ
|
| 308 |
+
|
| 309 |
+
Args:
|
| 310 |
+
max_std: ์ต๋ ์์ ํ์คํธ์ฐจ (๋จ์์ ๊ฐ๊น์)
|
| 311 |
+
"""
|
| 312 |
+
import cv2
|
| 313 |
+
import numpy as np
|
| 314 |
+
|
| 315 |
+
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
|
| 316 |
+
filtered = []
|
| 317 |
+
|
| 318 |
+
for det in detections:
|
| 319 |
+
bbox = det['bbox']
|
| 320 |
+
x1, y1, x2, y2 = map(int, bbox)
|
| 321 |
+
|
| 322 |
+
roi_hsv = hsv_image[y1:y2, x1:x2]
|
| 323 |
+
if roi_hsv.size == 0:
|
| 324 |
+
continue
|
| 325 |
+
|
| 326 |
+
# ์์(H), ์ฑ๋(S), ๋ช
๋(V) ํ์คํธ์ฐจ
|
| 327 |
+
h_std = np.std(roi_hsv[:, :, 0])
|
| 328 |
+
s_std = np.std(roi_hsv[:, :, 1])
|
| 329 |
+
v_std = np.std(roi_hsv[:, :, 2])
|
| 330 |
+
|
| 331 |
+
# ํ๊ท ํ์คํธ์ฐจ
|
| 332 |
+
avg_std = (h_std + s_std + v_std) / 3
|
| 333 |
+
|
| 334 |
+
# ๋จ์์ ๊ฐ๊น์ = ๋ฎ์ ํ์คํธ์ฐจ
|
| 335 |
+
if avg_std < max_std:
|
| 336 |
+
det['color_consistency'] = 1.0 - (avg_std / max_std)
|
| 337 |
+
filtered.append(det)
|
| 338 |
+
|
| 339 |
+
return filtered
|
| 340 |
+
```
|
| 341 |
+
|
| 342 |
+
#### 2.2 ์ฑ๋ ๊ธฐ๋ฐ ํํฐ (Saturation-based)
|
| 343 |
+
|
| 344 |
+
**์๋ฆฌ**: ์ฃฝ์ ์์ฐ = ๋งค์ฐ ๋ฎ์ ์ฑ๋ (ํฐ์/ํ์)
|
| 345 |
+
|
| 346 |
+
```python
|
| 347 |
+
def filter_by_saturation(image, detections, max_saturation=150):
|
| 348 |
+
"""
|
| 349 |
+
์ฑ๋ ํํฐ (์ฃฝ์ ์์ฐ ์ ์ฉ)
|
| 350 |
+
|
| 351 |
+
Args:
|
| 352 |
+
max_saturation: ์ต๋ ์ฑ๋ (0~255)
|
| 353 |
+
"""
|
| 354 |
+
import cv2
|
| 355 |
+
|
| 356 |
+
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
|
| 357 |
+
filtered = []
|
| 358 |
+
|
| 359 |
+
for det in detections:
|
| 360 |
+
bbox = det['bbox']
|
| 361 |
+
x1, y1, x2, y2 = map(int, bbox)
|
| 362 |
+
|
| 363 |
+
roi_hsv = hsv_image[y1:y2, x1:x2]
|
| 364 |
+
if roi_hsv.size == 0:
|
| 365 |
+
continue
|
| 366 |
+
|
| 367 |
+
# ํ๊ท ์ฑ๋
|
| 368 |
+
mean_saturation = roi_hsv[:, :, 1].mean()
|
| 369 |
+
|
| 370 |
+
# ๋ฎ์ ์ฑ๋ = ์ฃฝ์ ์์ฐ
|
| 371 |
+
if mean_saturation < max_saturation:
|
| 372 |
+
det['saturation_score'] = 1.0 - (mean_saturation / 255)
|
| 373 |
+
filtered.append(det)
|
| 374 |
+
|
| 375 |
+
return filtered
|
| 376 |
+
```
|
| 377 |
+
|
| 378 |
+
#### 2.3 ํ
์ค์ฒ ๊ท ์ง์ฑ ํํฐ (Texture Homogeneity)
|
| 379 |
+
|
| 380 |
+
**์๋ฆฌ**: ์์ฐ๋ ๋ถ๋๋ฌ์ด ํ
์ค์ฒ (์๋ ๋งคํธ๋ ๊ฐํ ํจํด)
|
| 381 |
+
|
| 382 |
+
```python
|
| 383 |
+
def filter_by_texture_homogeneity(image, detections, max_texture_std=40):
|
| 384 |
+
"""
|
| 385 |
+
ํ
์ค์ฒ ๊ท ์ง์ฑ ํํฐ
|
| 386 |
+
|
| 387 |
+
Args:
|
| 388 |
+
max_texture_std: ์ต๋ ํ
์ค์ฒ ํ์คํธ์ฐจ
|
| 389 |
+
"""
|
| 390 |
+
import cv2
|
| 391 |
+
import numpy as np
|
| 392 |
+
|
| 393 |
+
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 394 |
+
filtered = []
|
| 395 |
+
|
| 396 |
+
for det in detections:
|
| 397 |
+
bbox = det['bbox']
|
| 398 |
+
x1, y1, x2, y2 = map(int, bbox)
|
| 399 |
+
|
| 400 |
+
roi = gray[y1:y2, x1:x2]
|
| 401 |
+
if roi.size == 0:
|
| 402 |
+
continue
|
| 403 |
+
|
| 404 |
+
# ํ
์ค์ฒ ๋ณต์ก๋ (ํ์คํธ์ฐจ)
|
| 405 |
+
texture_std = np.std(roi)
|
| 406 |
+
|
| 407 |
+
# ๋ถ๋๋ฌ์ด ํ
์ค์ฒ = ๋ฎ์ ํ์คํธ์ฐจ
|
| 408 |
+
if texture_std < max_texture_std:
|
| 409 |
+
det['texture_homogeneity'] = 1.0 - (texture_std / 100)
|
| 410 |
+
filtered.append(det)
|
| 411 |
+
|
| 412 |
+
return filtered
|
| 413 |
+
```
|
| 414 |
+
|
| 415 |
+
#### 2.4 ์์ง ํจํด ํํฐ (Edge Pattern)
|
| 416 |
+
|
| 417 |
+
**์๋ฆฌ**: ์์ฐ๋ ๋ถ๋๋ฌ์ด ๊ณก์ ์์ง (์ง์ ์์ง ์์)
|
| 418 |
+
|
| 419 |
+
```python
|
| 420 |
+
def filter_by_edge_pattern(image, detections, min_edge_smoothness=0.6):
|
| 421 |
+
"""
|
| 422 |
+
์์ง ํจํด ํํฐ
|
| 423 |
+
|
| 424 |
+
Args:
|
| 425 |
+
min_edge_smoothness: ์ต์ ์์ง ๋ถ๋๋ฌ์ (0~1)
|
| 426 |
+
"""
|
| 427 |
+
import cv2
|
| 428 |
+
import numpy as np
|
| 429 |
+
|
| 430 |
+
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 431 |
+
edges = cv2.Canny(gray, 50, 150)
|
| 432 |
+
|
| 433 |
+
filtered = []
|
| 434 |
+
|
| 435 |
+
for det in detections:
|
| 436 |
+
bbox = det['bbox']
|
| 437 |
+
x1, y1, x2, y2 = map(int, bbox)
|
| 438 |
+
|
| 439 |
+
roi_edges = edges[y1:y2, x1:x2]
|
| 440 |
+
if roi_edges.size == 0:
|
| 441 |
+
continue
|
| 442 |
+
|
| 443 |
+
# Hough Line ๋ณํ์ผ๋ก ์ง์ ๊ฒ์ถ
|
| 444 |
+
lines = cv2.HoughLinesP(roi_edges, 1, np.pi/180, threshold=30,
|
| 445 |
+
minLineLength=20, maxLineGap=5)
|
| 446 |
+
|
| 447 |
+
# ์ง์ ๊ฐ์
|
| 448 |
+
num_lines = len(lines) if lines is not None else 0
|
| 449 |
+
|
| 450 |
+
# ์์ง ํฝ์
๊ฐ์
|
| 451 |
+
total_edges = np.sum(roi_edges > 0)
|
| 452 |
+
|
| 453 |
+
if total_edges == 0:
|
| 454 |
+
continue
|
| 455 |
+
|
| 456 |
+
# ์ง์ ๋น์จ = ์ง์ ๊ฐ์ / ์ด ์์ง ํฝ์
|
| 457 |
+
# (๋ฎ์์๋ก ๊ณก์ ํ)
|
| 458 |
+
line_ratio = num_lines / (total_edges / 100) # ์ ๊ทํ
|
| 459 |
+
|
| 460 |
+
edge_smoothness = max(0, 1.0 - line_ratio)
|
| 461 |
+
|
| 462 |
+
if edge_smoothness > min_edge_smoothness:
|
| 463 |
+
det['edge_smoothness'] = edge_smoothness
|
| 464 |
+
filtered.append(det)
|
| 465 |
+
|
| 466 |
+
return filtered
|
| 467 |
+
```
|
| 468 |
+
|
| 469 |
+
---
|
| 470 |
+
|
| 471 |
+
### Step 3: ์ ๋ฐ ๊ฒ์ฆ
|
| 472 |
+
|
| 473 |
+
#### 3.1 ๋ด๋ถ ๊ตฌ์กฐ ๊ฒ์ฆ (Internal Structure)
|
| 474 |
+
|
| 475 |
+
**์๋ฆฌ**: ์ฃฝ์ ์์ฐ๋ ํฌ๋ช
ํ์ฌ ๋ด๋ถ ์ํ๊ด(๊ฒ์ ์ ) ๋ณด์
|
| 476 |
+
|
| 477 |
+
```python
|
| 478 |
+
def verify_internal_structure(image, detections, threshold=0.3):
|
| 479 |
+
"""
|
| 480 |
+
๋ด๋ถ ๊ตฌ์กฐ ๊ฒ์ฆ (์ฃฝ์ ์์ฐ ์ ์ฉ)
|
| 481 |
+
|
| 482 |
+
Args:
|
| 483 |
+
threshold: ๊ฒ์ ์ ๊ฒ์ถ ์๊ณ๊ฐ
|
| 484 |
+
"""
|
| 485 |
+
import cv2
|
| 486 |
+
import numpy as np
|
| 487 |
+
|
| 488 |
+
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 489 |
+
filtered = []
|
| 490 |
+
|
| 491 |
+
for det in detections:
|
| 492 |
+
bbox = det['bbox']
|
| 493 |
+
x1, y1, x2, y2 = map(int, bbox)
|
| 494 |
+
|
| 495 |
+
roi = gray[y1:y2, x1:x2]
|
| 496 |
+
if roi.size == 0:
|
| 497 |
+
continue
|
| 498 |
+
|
| 499 |
+
# ์ด๋์ด ํฝ์
(์ํ๊ด) ๋น์จ
|
| 500 |
+
dark_pixels = np.sum(roi < 100)
|
| 501 |
+
total_pixels = roi.size
|
| 502 |
+
dark_ratio = dark_pixels / total_pixels
|
| 503 |
+
|
| 504 |
+
# ์ ๋นํ ์ด๋์ด ์์ญ (์ํ๊ด)
|
| 505 |
+
if 0.05 < dark_ratio < 0.40:
|
| 506 |
+
det['internal_structure_score'] = dark_ratio
|
| 507 |
+
filtered.append(det)
|
| 508 |
+
else:
|
| 509 |
+
# ๋ด๋ถ ๊ตฌ์กฐ ์์ด๋ ํต๊ณผ (ํฌ๋ช
ํ์ง ์์ ์์ฐ)
|
| 510 |
+
filtered.append(det)
|
| 511 |
+
|
| 512 |
+
return filtered
|
| 513 |
+
```
|
| 514 |
+
|
| 515 |
+
#### 3.2 ๋์นญ์ฑ ๊ฒ์ฆ (Symmetry)
|
| 516 |
+
|
| 517 |
+
**์๋ฆฌ**: ์์ฐ๋ ์ข์ฐ ๋์นญ (์์๋๋ ๋น๋์นญ)
|
| 518 |
+
|
| 519 |
+
```python
|
| 520 |
+
def verify_symmetry(image, detections, min_symmetry=0.6):
|
| 521 |
+
"""
|
| 522 |
+
๋์นญ์ฑ ๊ฒ์ฆ
|
| 523 |
+
|
| 524 |
+
Args:
|
| 525 |
+
min_symmetry: ์ต์ ๋์นญ์ฑ ์ ์ (0~1)
|
| 526 |
+
"""
|
| 527 |
+
import cv2
|
| 528 |
+
import numpy as np
|
| 529 |
+
|
| 530 |
+
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 531 |
+
filtered = []
|
| 532 |
+
|
| 533 |
+
for det in detections:
|
| 534 |
+
bbox = det['bbox']
|
| 535 |
+
x1, y1, x2, y2 = map(int, bbox)
|
| 536 |
+
|
| 537 |
+
roi = gray[y1:y2, x1:x2]
|
| 538 |
+
if roi.size == 0:
|
| 539 |
+
continue
|
| 540 |
+
|
| 541 |
+
h, w = roi.shape
|
| 542 |
+
|
| 543 |
+
# ์ข์ฐ ๋์นญ ๋น๊ต
|
| 544 |
+
left_half = roi[:, :w//2]
|
| 545 |
+
right_half = roi[:, w//2:]
|
| 546 |
+
right_half_flipped = np.fliplr(right_half)
|
| 547 |
+
|
| 548 |
+
# ํฌ๊ธฐ ๋ง์ถ๊ธฐ
|
| 549 |
+
min_width = min(left_half.shape[1], right_half_flipped.shape[1])
|
| 550 |
+
left_half = left_half[:, :min_width]
|
| 551 |
+
right_half_flipped = right_half_flipped[:, :min_width]
|
| 552 |
+
|
| 553 |
+
# ์๊ด๊ณ์ (๋์นญ๋)
|
| 554 |
+
correlation = np.corrcoef(left_half.flatten(), right_half_flipped.flatten())[0, 1]
|
| 555 |
+
|
| 556 |
+
if not np.isnan(correlation) and correlation > min_symmetry:
|
| 557 |
+
det['symmetry_score'] = correlation
|
| 558 |
+
filtered.append(det)
|
| 559 |
+
|
| 560 |
+
return filtered
|
| 561 |
+
```
|
| 562 |
+
|
| 563 |
+
---
|
| 564 |
+
|
| 565 |
+
## ๐ ํตํฉ ํ์ดํ๋ผ์ธ
|
| 566 |
+
|
| 567 |
+
```python
|
| 568 |
+
class UniversalShrimpFilter:
|
| 569 |
+
"""๋ฒ์ฉ ์์ฐ ๊ฒ์ถ ํํฐ (๋ฐฐ๊ฒฝ/์์น ๋ฌด๊ด)"""
|
| 570 |
+
|
| 571 |
+
def __init__(self):
|
| 572 |
+
# ํ๋ผ๋ฏธํฐ (์กฐ์ ๊ฐ๋ฅ)
|
| 573 |
+
self.params = {
|
| 574 |
+
# Step 1: ํํ ํํฐ
|
| 575 |
+
'min_aspect_ratio': 2.0,
|
| 576 |
+
'max_aspect_ratio': 10.0,
|
| 577 |
+
'max_line_ratio': 0.3,
|
| 578 |
+
'max_compactness': 0.25,
|
| 579 |
+
'min_area_ratio': 0.05,
|
| 580 |
+
'max_area_ratio': 0.50,
|
| 581 |
+
|
| 582 |
+
# Step 2: ์์/ํ
์ค์ฒ ํํฐ
|
| 583 |
+
'max_color_std': 50,
|
| 584 |
+
'max_saturation': 150,
|
| 585 |
+
'max_texture_std': 40,
|
| 586 |
+
'min_edge_smoothness': 0.6,
|
| 587 |
+
|
| 588 |
+
# Step 3: ์ ๋ฐ ๊ฒ์ฆ
|
| 589 |
+
'min_symmetry': 0.6,
|
| 590 |
+
|
| 591 |
+
# ์ข
ํฉ
|
| 592 |
+
'min_total_score': 60, # 0~100
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
def filter(self, image, detections):
|
| 596 |
+
"""
|
| 597 |
+
๋ฒ์ฉ ํํฐ๋ง ํ์ดํ๋ผ์ธ
|
| 598 |
+
|
| 599 |
+
Args:
|
| 600 |
+
image: ์
๋ ฅ ์ด๋ฏธ์ง (BGR)
|
| 601 |
+
detections: RT-DETR ๊ฒ์ถ ๊ฒฐ๊ณผ
|
| 602 |
+
|
| 603 |
+
Returns:
|
| 604 |
+
ํํฐ๋ง๋ ๊ฒ์ถ (์์ฐ๋ง)
|
| 605 |
+
"""
|
| 606 |
+
import numpy as np
|
| 607 |
+
|
| 608 |
+
print(f"[INFO] Initial detections: {len(detections)}")
|
| 609 |
+
|
| 610 |
+
# Step 1: ํํ ํํฐ
|
| 611 |
+
detections = filter_by_aspect_ratio(
|
| 612 |
+
detections,
|
| 613 |
+
self.params['min_aspect_ratio'],
|
| 614 |
+
self.params['max_aspect_ratio']
|
| 615 |
+
)
|
| 616 |
+
print(f"[STEP 1-1] After aspect ratio filter: {len(detections)}")
|
| 617 |
+
|
| 618 |
+
if not detections:
|
| 619 |
+
return []
|
| 620 |
+
|
| 621 |
+
detections = filter_by_curvature(
|
| 622 |
+
image, detections,
|
| 623 |
+
self.params['max_line_ratio']
|
| 624 |
+
)
|
| 625 |
+
print(f"[STEP 1-2] After curvature filter: {len(detections)}")
|
| 626 |
+
|
| 627 |
+
if not detections:
|
| 628 |
+
return []
|
| 629 |
+
|
| 630 |
+
detections = filter_by_compactness(
|
| 631 |
+
image, detections,
|
| 632 |
+
self.params['max_compactness']
|
| 633 |
+
)
|
| 634 |
+
print(f"[STEP 1-3] After compactness filter: {len(detections)}")
|
| 635 |
+
|
| 636 |
+
if not detections:
|
| 637 |
+
return []
|
| 638 |
+
|
| 639 |
+
detections = filter_by_adaptive_size(
|
| 640 |
+
detections, image.shape,
|
| 641 |
+
self.params['min_area_ratio'],
|
| 642 |
+
self.params['max_area_ratio']
|
| 643 |
+
)
|
| 644 |
+
print(f"[STEP 1-4] After size filter: {len(detections)}")
|
| 645 |
+
|
| 646 |
+
if not detections:
|
| 647 |
+
return []
|
| 648 |
+
|
| 649 |
+
# Step 2: ์์/ํ
์ค์ฒ ํํฐ
|
| 650 |
+
detections = filter_by_color_consistency(
|
| 651 |
+
image, detections,
|
| 652 |
+
self.params['max_color_std']
|
| 653 |
+
)
|
| 654 |
+
print(f"[STEP 2-1] After color consistency filter: {len(detections)}")
|
| 655 |
+
|
| 656 |
+
if not detections:
|
| 657 |
+
return []
|
| 658 |
+
|
| 659 |
+
detections = filter_by_saturation(
|
| 660 |
+
image, detections,
|
| 661 |
+
self.params['max_saturation']
|
| 662 |
+
)
|
| 663 |
+
print(f"[STEP 2-2] After saturation filter: {len(detections)}")
|
| 664 |
+
|
| 665 |
+
if not detections:
|
| 666 |
+
return []
|
| 667 |
+
|
| 668 |
+
detections = filter_by_texture_homogeneity(
|
| 669 |
+
image, detections,
|
| 670 |
+
self.params['max_texture_std']
|
| 671 |
+
)
|
| 672 |
+
print(f"[STEP 2-3] After texture filter: {len(detections)}")
|
| 673 |
+
|
| 674 |
+
if not detections:
|
| 675 |
+
return []
|
| 676 |
+
|
| 677 |
+
# Step 3: ์ ๋ฐ ๊ฒ์ฆ (์ ํ์ )
|
| 678 |
+
detections = verify_internal_structure(image, detections)
|
| 679 |
+
print(f"[STEP 3-1] After internal structure verify: {len(detections)}")
|
| 680 |
+
|
| 681 |
+
detections = verify_symmetry(
|
| 682 |
+
image, detections,
|
| 683 |
+
self.params['min_symmetry']
|
| 684 |
+
)
|
| 685 |
+
print(f"[STEP 3-2] After symmetry verify: {len(detections)}")
|
| 686 |
+
|
| 687 |
+
# ์ข
ํฉ ์ ์ ๊ณ์ฐ ๋ฐ ์ต์ข
์ ํ
|
| 688 |
+
detections = self._calculate_total_scores(detections)
|
| 689 |
+
detections = [d for d in detections if d['total_score'] >= self.params['min_total_score']]
|
| 690 |
+
detections = sorted(detections, key=lambda x: x['total_score'], reverse=True)
|
| 691 |
+
|
| 692 |
+
print(f"[FINAL] After scoring: {len(detections)}")
|
| 693 |
+
|
| 694 |
+
return detections[:1] if detections else [] # ์์ 1๊ฐ
|
| 695 |
+
|
| 696 |
+
def _calculate_total_scores(self, detections):
|
| 697 |
+
"""์ข
ํฉ ์ ์ ๊ณ์ฐ (0~100)"""
|
| 698 |
+
|
| 699 |
+
# ๊ฐ์ค์น
|
| 700 |
+
WEIGHTS = {
|
| 701 |
+
'aspect_ratio': 0.15,
|
| 702 |
+
'curvature': 0.15,
|
| 703 |
+
'compactness': 0.10,
|
| 704 |
+
'area_ratio': 0.10,
|
| 705 |
+
'color_consistency': 0.15,
|
| 706 |
+
'saturation': 0.10,
|
| 707 |
+
'texture_homogeneity': 0.10,
|
| 708 |
+
'edge_smoothness': 0.05,
|
| 709 |
+
'symmetry': 0.10,
|
| 710 |
+
}
|
| 711 |
+
|
| 712 |
+
for det in detections:
|
| 713 |
+
score = 0.0
|
| 714 |
+
|
| 715 |
+
# ๊ฐ ์ ์ ํฉ์ฐ
|
| 716 |
+
if 'aspect_ratio' in det:
|
| 717 |
+
# ์ด์์ ์ข
ํก๋น์ ๊ฐ๊น์ธ์๋ก ๋์ ์ ์
|
| 718 |
+
ideal_aspect = 4.5
|
| 719 |
+
fitness = max(0, 1 - abs(det['aspect_ratio'] - ideal_aspect) / ideal_aspect)
|
| 720 |
+
score += fitness * WEIGHTS['aspect_ratio']
|
| 721 |
+
|
| 722 |
+
if 'curvature_score' in det:
|
| 723 |
+
score += det['curvature_score'] * WEIGHTS['curvature']
|
| 724 |
+
|
| 725 |
+
if 'compactness' in det:
|
| 726 |
+
# ๋ฎ์์๋ก ์ข์
|
| 727 |
+
fitness = max(0, 1 - det['compactness'] / 0.25)
|
| 728 |
+
score += fitness * WEIGHTS['compactness']
|
| 729 |
+
|
| 730 |
+
if 'area_ratio' in det:
|
| 731 |
+
# ์ด์์ ํฌ๊ธฐ: 15% ์ ๋
|
| 732 |
+
ideal_area = 0.15
|
| 733 |
+
fitness = max(0, 1 - abs(det['area_ratio'] - ideal_area) / ideal_area)
|
| 734 |
+
score += fitness * WEIGHTS['area_ratio']
|
| 735 |
+
|
| 736 |
+
if 'color_consistency' in det:
|
| 737 |
+
score += det['color_consistency'] * WEIGHTS['color_consistency']
|
| 738 |
+
|
| 739 |
+
if 'saturation_score' in det:
|
| 740 |
+
score += det['saturation_score'] * WEIGHTS['saturation']
|
| 741 |
+
|
| 742 |
+
if 'texture_homogeneity' in det:
|
| 743 |
+
score += det['texture_homogeneity'] * WEIGHTS['texture_homogeneity']
|
| 744 |
+
|
| 745 |
+
if 'edge_smoothness' in det:
|
| 746 |
+
score += det['edge_smoothness'] * WEIGHTS['edge_smoothness']
|
| 747 |
+
|
| 748 |
+
if 'symmetry_score' in det:
|
| 749 |
+
score += det['symmetry_score'] * WEIGHTS['symmetry']
|
| 750 |
+
|
| 751 |
+
det['total_score'] = score * 100
|
| 752 |
+
|
| 753 |
+
return detections
|
| 754 |
+
|
| 755 |
+
|
| 756 |
+
# ์ฌ์ฉ ์์
|
| 757 |
+
if __name__ == "__main__":
|
| 758 |
+
import cv2
|
| 759 |
+
|
| 760 |
+
# ์ด๋ฏธ์ง ๋ก๋
|
| 761 |
+
image = cv2.imread("shrimp_image.jpg")
|
| 762 |
+
|
| 763 |
+
# RT-DETR ๊ฒ์ถ (๊ฐ์)
|
| 764 |
+
rtdetr_detections = [
|
| 765 |
+
{'bbox': [100, 200, 500, 280], 'confidence': 0.85, 'class_id': 1},
|
| 766 |
+
{'bbox': [50, 50, 150, 500], 'confidence': 0.92, 'class_id': 2}, # ์ (์ ๊ฑฐ๋จ)
|
| 767 |
+
]
|
| 768 |
+
|
| 769 |
+
# ๋ฒ์ฉ ํํฐ ์ ์ฉ
|
| 770 |
+
universal_filter = UniversalShrimpFilter()
|
| 771 |
+
shrimp_detections = universal_filter.filter(image, rtdetr_detections)
|
| 772 |
+
|
| 773 |
+
print(f"\nโ
Final shrimp detections: {len(shrimp_detections)}")
|
| 774 |
+
for i, det in enumerate(shrimp_detections, 1):
|
| 775 |
+
print(f" Shrimp #{i}: score={det['total_score']:.2f}/100")
|
| 776 |
+
```
|
| 777 |
+
|
| 778 |
+
---
|
| 779 |
+
|
| 780 |
+
## ๐ ๋ฒ์ฉ์ฑ ๊ฒ์ฆ ์๋๋ฆฌ์ค
|
| 781 |
+
|
| 782 |
+
### ๋ฐฐ๊ฒฝ ์ ํ๋ณ ํ
์คํธ (MECE)
|
| 783 |
+
|
| 784 |
+
```
|
| 785 |
+
A. ๋ฐฐ๊ฒฝ ์ ํ
|
| 786 |
+
โโโ A1. ์ธก์ ๋งคํธ (ํ๋์)
|
| 787 |
+
โโโ A2. ํฐ์ ์ ์
|
| 788 |
+
โโโ A3. ๋๋ฌด ๋๋ง
|
| 789 |
+
โโโ A4. ์๋ฐ๋ฅ
|
| 790 |
+
โโโ A5. ์์กฐ (๋ฌผ์)
|
| 791 |
+
โโโ A6. ๋ณต์กํ ๋ฐฐ๊ฒฝ (์ก์ง, ์ ๋ฌธ ๋ฑ)
|
| 792 |
+
|
| 793 |
+
B. ์ฃผ๋ณ ๊ฐ์ฒด
|
| 794 |
+
โโโ B1. ์์ฐ๋ง
|
| 795 |
+
โโโ B2. ์์ฐ + ์
|
| 796 |
+
โโโ B3. ์์ฐ + ์
|
| 797 |
+
โโโ B4. ์์ฐ + ๋ค์ํ ๋ฌผ์ฒด
|
| 798 |
+
โโโ B5. ์์ฐ ์์ (Negative)
|
| 799 |
+
|
| 800 |
+
C. ์์ฐ ์์น
|
| 801 |
+
โโโ C1. ์ค์
|
| 802 |
+
โโโ C2. ๋ชจ์๋ฆฌ
|
| 803 |
+
โโโ C3. ๊ฐ์ฅ์๋ฆฌ
|
| 804 |
+
โโโ C4. ์ฌ๋ฌ ๋ง๋ฆฌ (์ฐ์ฌ)
|
| 805 |
+
```
|
| 806 |
+
|
| 807 |
+
### ์์ ์ฑ๋ฅ
|
| 808 |
+
|
| 809 |
+
| ์๋๋ฆฌ์ค | Precision | Recall | F1 |
|
| 810 |
+
|---------|-----------|--------|-----|
|
| 811 |
+
| ๋งคํธ ์ (๊นจ๋) | 85% | 90% | 87% |
|
| 812 |
+
| ์ ์ ์ | 80% | 85% | 82% |
|
| 813 |
+
| ์๋ฐ๋ฅ ์ | 75% | 80% | 77% |
|
| 814 |
+
| ๋ณต์กํ ๋ฐฐ๊ฒฝ | 60% | 70% | 65% |
|
| 815 |
+
| **ํ๊ท ** | **75%** | **81%** | **78%** |
|
| 816 |
+
|
| 817 |
+
---
|
| 818 |
+
|
| 819 |
+
## ๐ฏ ํต์ฌ ์ฅ์
|
| 820 |
+
|
| 821 |
+
### โ
๋ฒ์ฉ์ฑ (Universality)
|
| 822 |
+
1. **๋ฐฐ๊ฒฝ ๋
๋ฆฝ**: ๋งคํธ, ์ ์, ์๋ฐ๋ฅ ๋ฑ ์ด๋๋
|
| 823 |
+
2. **๊ฐ์ฒด ๋
๋ฆฝ**: ์, ์ ๋ฑ ์ฃผ๋ณ ๊ฐ์ฒด ๋ถํ์
|
| 824 |
+
3. **์์น ๋
๋ฆฝ**: ์ด๋ฏธ์ง ์ด๋์ ์๋ ๊ฒ์ถ
|
| 825 |
+
4. **ํฌ๊ธฐ ๋
๋ฆฝ**: ์์/ํฐ ์์ฐ ๋ชจ๋ ๊ฒ์ถ
|
| 826 |
+
|
| 827 |
+
### โ
๊ฐ๊ฑด์ฑ (Robustness)
|
| 828 |
+
1. **์กฐ๋ช
๋ณํ**: HSV ์๊ณต๊ฐ์ผ๋ก ์กฐ๋ช
๋ฌด๊ด
|
| 829 |
+
2. **ํ์ ๋ณํ**: ์ข
ํก๋น๋ ๋ฐฉํฅ ๊ณ ๋ ค
|
| 830 |
+
3. **์ค์ผ์ผ ๋ณํ**: ์ ์ํ ํฌ๊ธฐ ํํฐ
|
| 831 |
+
|
| 832 |
+
### โ
์ ๋ฐ์ฑ (Precision)
|
| 833 |
+
1. **๋ค๋จ๊ณ ํํฐ**: ํํ โ ์์ โ ๊ตฌ์กฐ
|
| 834 |
+
2. **์ข
ํฉ ์ ์**: ์ฌ๋ฌ ํน์ง์ ๊ฐ์ค ํ๊ท
|
| 835 |
+
3. **์๊ณ๊ฐ ์กฐ์ **: ์ํฉ์ ๋ง๊ฒ ํ๋ผ๋ฏธํฐ ํ๋
|
| 836 |
+
|
| 837 |
+
---
|
| 838 |
+
|
| 839 |
+
## ๐ง ํ๋ผ๋ฏธํฐ ํ๋ ๊ฐ์ด๋
|
| 840 |
+
|
| 841 |
+
```python
|
| 842 |
+
# ๊ฒ์ถ ์ ๋จ (Recall ๋ฎ์)
|
| 843 |
+
โ ์๊ณ๊ฐ ์ํ
|
| 844 |
+
- min_aspect_ratio: 2.0 โ 1.5
|
| 845 |
+
- max_compactness: 0.25 โ 0.30
|
| 846 |
+
- min_total_score: 60 โ 50
|
| 847 |
+
|
| 848 |
+
# ๊ณผ๋ค ๊ฒ์ถ (Precision ๋ฎ์)
|
| 849 |
+
โ ์๊ณ๊ฐ ๊ฐํ
|
| 850 |
+
- min_aspect_ratio: 2.0 โ 2.5
|
| 851 |
+
- max_saturation: 150 โ 120
|
| 852 |
+
- min_total_score: 60 โ 70
|
| 853 |
+
|
| 854 |
+
# ์๋ ๊ฐ์
|
| 855 |
+
โ ํํฐ ์์ ์ต์ ํ
|
| 856 |
+
1. ๊ฐ์ฅ ๋น ๋ฅด๊ณ ํจ๊ณผ์ ์ธ ํํฐ ๋จผ์
|
| 857 |
+
2. ๋น์ฉ ๋์ ํํฐ(์ธ๊ณฝ์ , ๋์นญ์ฑ)๋ ํ๋ฐ
|
| 858 |
+
```
|
| 859 |
+
|
| 860 |
+
---
|
| 861 |
+
|
| 862 |
+
## ๐ ์ฑ๋ฅ ๋น๊ต
|
| 863 |
+
|
| 864 |
+
| ์ ๋ต | ๋ฒ์ฉ์ฑ | ์ ํ๋ | ์๋ | ๊ตฌํ ๋์ด๋ |
|
| 865 |
+
|------|-------|--------|------|------------|
|
| 866 |
+
| **์ปจํ
์คํธ ์์กด** (์, ๋งคํธ ๋ฑ) | โญโญ | โญโญโญโญ | โญโญโญโญ | โญโญ |
|
| 867 |
+
| **๋ณธ์ง์ ํน์ง ๊ธฐ๋ฐ** (ํ์ฌ) | โญโญโญโญโญ | โญโญโญ | โญโญโญ | โญโญโญ |
|
| 868 |
+
| **Roboflow ํ์ธํ๋** | โญโญโญโญโญ | โญโญโญโญโญ | โญโญโญโญ | โญโญโญโญ |
|
| 869 |
+
|
| 870 |
+
---
|
| 871 |
+
|
| 872 |
+
## ๐ ๋ค์ ๋จ๊ณ
|
| 873 |
+
|
| 874 |
+
1. **์ฆ์ ๊ตฌํ**: ๋ฒ์ฉ ํํฐ ์ฝ๋ ์ ์ฉ
|
| 875 |
+
2. **ํ
์คํธ**: ๋ค์ํ ๋ฐฐ๊ฒฝ/์์น ์ด๋ฏธ์ง๋ก ๊ฒ์ฆ
|
| 876 |
+
3. **ํ๋**: ํ๋ผ๋ฏธํฐ ์ต์ ํ
|
| 877 |
+
4. **์ค๊ธฐ**: Roboflow ํ์ธํ๋์ผ๋ก ์ ํ๋ ํฅ์
|
| 878 |
+
|
| 879 |
+
---
|
| 880 |
+
|
| 881 |
+
**์์ฑ์ผ**: 2025-11-07
|
| 882 |
+
**๋ฒ์ **: 2.0 (Universal)
|
| 883 |
+
**์์ฑ์**: VIDraft Team
|
interactive_validation.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""
|
| 3 |
+
๋ํํ ๊ฒ์ฆ ์ธํฐํ์ด์ค
|
| 4 |
+
์ค์๊ฐ์ผ๋ก ํ๋ผ๋ฏธํฐ๋ฅผ ์กฐ์ ํ๋ฉฐ ๊ฒ์ถ ๊ฒฐ๊ณผ ํ์ธ
|
| 5 |
+
"""
|
| 6 |
+
import sys
|
| 7 |
+
sys.stdout.reconfigure(encoding='utf-8')
|
| 8 |
+
|
| 9 |
+
import gradio as gr
|
| 10 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 11 |
+
import numpy as np
|
| 12 |
+
from test_visual_validation import (
|
| 13 |
+
load_rtdetr_model,
|
| 14 |
+
detect_with_rtdetr,
|
| 15 |
+
apply_universal_filter,
|
| 16 |
+
calculate_morphological_features,
|
| 17 |
+
calculate_visual_features
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
# ์ ์ญ ๋ชจ๋ธ (ํ ๋ฒ๋ง ๋ก๋)
|
| 21 |
+
processor, model = load_rtdetr_model()
|
| 22 |
+
|
| 23 |
+
def interactive_detect(image, confidence, filter_threshold, show_all=False):
|
| 24 |
+
"""๋ํํ ๊ฒ์ถ"""
|
| 25 |
+
if image is None:
|
| 26 |
+
return None, "โ ๏ธ ์ด๋ฏธ์ง๋ฅผ ์
๋ก๋ํ์ธ์."
|
| 27 |
+
|
| 28 |
+
try:
|
| 29 |
+
# RT-DETR ๊ฒ์ถ
|
| 30 |
+
all_detections = detect_with_rtdetr(image, processor, model, confidence)
|
| 31 |
+
|
| 32 |
+
# ํํฐ ์ ์ฉ
|
| 33 |
+
filtered_detections = apply_universal_filter(all_detections, image, filter_threshold)
|
| 34 |
+
|
| 35 |
+
# ์๊ฐํ
|
| 36 |
+
img = image.copy()
|
| 37 |
+
draw = ImageDraw.Draw(img)
|
| 38 |
+
|
| 39 |
+
try:
|
| 40 |
+
font = ImageFont.truetype("arial.ttf", 14)
|
| 41 |
+
font_large = ImageFont.truetype("arial.ttf", 18)
|
| 42 |
+
font_small = ImageFont.truetype("arial.ttf", 10)
|
| 43 |
+
except:
|
| 44 |
+
font = ImageFont.load_default()
|
| 45 |
+
font_large = ImageFont.load_default()
|
| 46 |
+
font_small = ImageFont.load_default()
|
| 47 |
+
|
| 48 |
+
# ์ ์ฒด ๊ฒ์ถ ํ์ (์ต์
)
|
| 49 |
+
if show_all:
|
| 50 |
+
for det in all_detections:
|
| 51 |
+
x1, y1, x2, y2 = det['bbox']
|
| 52 |
+
draw.rectangle([x1, y1, x2, y2], outline="gray", width=1)
|
| 53 |
+
|
| 54 |
+
# ํํฐ๋ง๋ ๊ฒฐ๊ณผ
|
| 55 |
+
for idx, det in enumerate(filtered_detections, 1):
|
| 56 |
+
x1, y1, x2, y2 = det['bbox']
|
| 57 |
+
score = det['filter_score']
|
| 58 |
+
|
| 59 |
+
# ์ ์์ ๋ฐ๋ผ ์์
|
| 60 |
+
if score >= 75:
|
| 61 |
+
color = "lime"
|
| 62 |
+
elif score >= 50:
|
| 63 |
+
color = "yellow"
|
| 64 |
+
else:
|
| 65 |
+
color = "orange"
|
| 66 |
+
|
| 67 |
+
# ๋ฐ์ค
|
| 68 |
+
draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
|
| 69 |
+
|
| 70 |
+
# ๋ผ๋ฒจ
|
| 71 |
+
label = f"#{idx} {score:.0f}์ "
|
| 72 |
+
bbox = draw.textbbox((x1, y1 - 25), label, font=font)
|
| 73 |
+
draw.rectangle(bbox, fill=color)
|
| 74 |
+
draw.text((x1, y1 - 25), label, fill="black", font=font)
|
| 75 |
+
|
| 76 |
+
# ์ธ๋ถ ์ ๋ณด (์๊ฒ) - RT-DETR ์ ๋ขฐ๋ ๋ช
์
|
| 77 |
+
details = f"RT-DETR:{det['confidence']:.0%}"
|
| 78 |
+
draw.text((x1, y2 + 5), details, fill=color, font=font_small)
|
| 79 |
+
|
| 80 |
+
# ํค๋
|
| 81 |
+
header = f"๊ฒ์ถ: {len(filtered_detections)}๊ฐ (์ ์ฒด: {len(all_detections)}๊ฐ)"
|
| 82 |
+
header_bbox = draw.textbbox((10, 10), header, font=font_large)
|
| 83 |
+
draw.rectangle([5, 5, header_bbox[2]+10, header_bbox[3]+10],
|
| 84 |
+
fill="black", outline="lime", width=2)
|
| 85 |
+
draw.text((10, 10), header, fill="lime", font=font_large)
|
| 86 |
+
|
| 87 |
+
# ์ ๋ณด ์์ฑ
|
| 88 |
+
info = f"""
|
| 89 |
+
### ๐ ๊ฒ์ถ ๊ฒฐ๊ณผ
|
| 90 |
+
|
| 91 |
+
- **์ ์ฒด ๊ฒ์ถ**: {len(all_detections)}๊ฐ
|
| 92 |
+
- **ํํฐ๋ง ํ**: {len(filtered_detections)}๊ฐ
|
| 93 |
+
- **์ ๊ฑฐ๋จ**: {len(all_detections) - len(filtered_detections)}๊ฐ
|
| 94 |
+
|
| 95 |
+
---
|
| 96 |
+
|
| 97 |
+
### ๐ฏ ๊ฒ์ถ๋ ๊ฐ์ฒด ์์ธ
|
| 98 |
+
|
| 99 |
+
"""
|
| 100 |
+
|
| 101 |
+
for idx, det in enumerate(filtered_detections, 1):
|
| 102 |
+
info += f"""
|
| 103 |
+
**#{idx} - ์ ์: {det['filter_score']:.0f}์ ** (RT-DETR ์ ๋ขฐ๋: {det['confidence']:.0%})
|
| 104 |
+
|
| 105 |
+
"""
|
| 106 |
+
# ์ฃผ์ ํน์ง๋ง 3๊ฐ
|
| 107 |
+
for reason in det['filter_reasons'][:3]:
|
| 108 |
+
info += f"- {reason}\n"
|
| 109 |
+
|
| 110 |
+
if not filtered_detections:
|
| 111 |
+
info += """
|
| 112 |
+
โ ๏ธ **๊ฒ์ถ๋ ๊ฐ์ฒด๊ฐ ์์ต๋๋ค.**
|
| 113 |
+
|
| 114 |
+
**์กฐ์ ๋ฐฉ๋ฒ:**
|
| 115 |
+
1. **์ ๋ขฐ๋ ์๊ณ๊ฐ**์ ๋ฎ์ถฐ๋ณด์ธ์ (0.2~0.3)
|
| 116 |
+
2. **ํํฐ ์ ์ ์๊ณ๊ฐ**์ ๋ฎ์ถฐ๋ณด์ธ์ (30~40)
|
| 117 |
+
3. "์ ์ฒด ๊ฒ์ถ ํ์"๋ฅผ ์ผ์ ์๋ณธ ๊ฒ์ถ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ์ธ์
|
| 118 |
+
"""
|
| 119 |
+
|
| 120 |
+
info += f"""
|
| 121 |
+
|
| 122 |
+
---
|
| 123 |
+
|
| 124 |
+
### โ๏ธ ํ์ฌ ์ค์
|
| 125 |
+
|
| 126 |
+
- **RT-DETR ์ ๋ขฐ๋**: {confidence:.0%}
|
| 127 |
+
- **ํํฐ ์ ์ ์๊ณ๊ฐ**: {filter_threshold}์
|
| 128 |
+
- **์ ์ฒด ๊ฒ์ถ ํ์**: {'์ผ์ง' if show_all else '๊บผ์ง'}
|
| 129 |
+
"""
|
| 130 |
+
|
| 131 |
+
return img, info
|
| 132 |
+
|
| 133 |
+
except Exception as e:
|
| 134 |
+
import traceback
|
| 135 |
+
error_detail = traceback.format_exc()
|
| 136 |
+
return None, f"โ ์ค๋ฅ ๋ฐ์:\n\n```\n{error_detail}\n```"
|
| 137 |
+
|
| 138 |
+
def analyze_single_detection(image, x1, y1, x2, y2):
|
| 139 |
+
"""๋จ์ผ ๊ฒ์ถ ์์ญ ๋ถ์ (์๋ ๋ฐ์ด๋ฉ ๋ฐ์ค)"""
|
| 140 |
+
if image is None:
|
| 141 |
+
return "โ ๏ธ ์ด๋ฏธ์ง๋ฅผ ์
๋ก๋ํ์ธ์."
|
| 142 |
+
|
| 143 |
+
try:
|
| 144 |
+
bbox = [float(x1), float(y1), float(x2), float(y2)]
|
| 145 |
+
|
| 146 |
+
# ํํํ์ ํน์ง
|
| 147 |
+
morph = calculate_morphological_features(bbox, image.size)
|
| 148 |
+
|
| 149 |
+
# ์๊ฐ์ ํน์ง
|
| 150 |
+
visual = calculate_visual_features(image, bbox)
|
| 151 |
+
|
| 152 |
+
analysis = f"""
|
| 153 |
+
### ๐ฌ ๋ฐ์ด๋ฉ ๋ฐ์ค ๋ถ์
|
| 154 |
+
|
| 155 |
+
**์ขํ**: ({x1:.0f}, {y1:.0f}) โ ({x2:.0f}, {y2:.0f})
|
| 156 |
+
|
| 157 |
+
---
|
| 158 |
+
|
| 159 |
+
### ๐ ํํํ์ ํน์ง
|
| 160 |
+
|
| 161 |
+
- **์ข
ํก๋น**: {morph['aspect_ratio']:.2f} {'โ
(2~10 ๋ฒ์)' if 2 <= morph['aspect_ratio'] <= 10 else 'โ'}
|
| 162 |
+
- **์ธ์ฅ๋ (Compactness)**: {morph['compactness']:.3f} {'โ
(<0.25)' if morph['compactness'] < 0.25 else 'โ'}
|
| 163 |
+
- **๋ฉด์ ๋น**: {morph['area_ratio']*100:.1f}% {'โ
(5~50%)' if 0.05 <= morph['area_ratio'] <= 0.5 else 'โ'}
|
| 164 |
+
- **๋๋นร๋์ด**: {morph['width']:.0f} ร {morph['height']:.0f}
|
| 165 |
+
|
| 166 |
+
---
|
| 167 |
+
|
| 168 |
+
### ๐จ ์๊ฐ์ ํน์ง
|
| 169 |
+
|
| 170 |
+
- **์ฑ๋ (Saturation)**: {visual['saturation']:.0f} {'โ
(<150)' if visual['saturation'] < 150 else 'โ'}
|
| 171 |
+
- **์์ ์ผ๊ด์ฑ (Std)**: {visual['color_std']:.1f} {'โ
(<30)' if visual['color_std'] < 30 else 'โ'}
|
| 172 |
+
|
| 173 |
+
---
|
| 174 |
+
|
| 175 |
+
### ๐ฏ ์์ ์ ์
|
| 176 |
+
|
| 177 |
+
"""
|
| 178 |
+
# ์ ์ ๊ณ์ฐ
|
| 179 |
+
score = 0
|
| 180 |
+
if 2.0 <= morph['aspect_ratio'] <= 10.0:
|
| 181 |
+
score += 15
|
| 182 |
+
if morph['compactness'] < 0.25:
|
| 183 |
+
score += 15
|
| 184 |
+
if 0.05 <= morph['area_ratio'] <= 0.50:
|
| 185 |
+
score += 10
|
| 186 |
+
if visual['saturation'] < 150:
|
| 187 |
+
score += 20
|
| 188 |
+
if visual['color_std'] < 30:
|
| 189 |
+
score += 15
|
| 190 |
+
|
| 191 |
+
analysis += f"**์ด์ **: {score}/75์ (RT-DETR ์ ๋ขฐ๋ ์ ์ธ)\n\n"
|
| 192 |
+
|
| 193 |
+
if score >= 50:
|
| 194 |
+
analysis += "โ
**ํ์ **: ์์ฐ๋ก ๋ถ๋ฅ๋ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค."
|
| 195 |
+
else:
|
| 196 |
+
analysis += "โ **ํ์ **: ์์ฐ๊ฐ ์๋ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค."
|
| 197 |
+
|
| 198 |
+
return analysis
|
| 199 |
+
|
| 200 |
+
except Exception as e:
|
| 201 |
+
return f"โ ์ค๋ฅ: {str(e)}"
|
| 202 |
+
|
| 203 |
+
# Gradio ์ธํฐํ์ด์ค
|
| 204 |
+
with gr.Blocks(title="๐งช ์์ฐ ๊ฒ์ถ ๋ํํ ๊ฒ์ฆ", theme=gr.themes.Soft()) as demo:
|
| 205 |
+
|
| 206 |
+
gr.Markdown("""
|
| 207 |
+
# ๐งช ์์ฐ ๊ฒ์ถ ๋ํํ ๊ฒ์ฆ ๋๊ตฌ
|
| 208 |
+
|
| 209 |
+
์ค์๊ฐ์ผ๋ก ํ๋ผ๋ฏธํฐ๋ฅผ ์กฐ์ ํ๋ฉฐ ๊ฒ์ถ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
|
| 210 |
+
|
| 211 |
+
---
|
| 212 |
+
""")
|
| 213 |
+
|
| 214 |
+
with gr.Tabs():
|
| 215 |
+
# ํญ 1: ์๋ ๊ฒ์ถ
|
| 216 |
+
with gr.TabItem("๐ค ์๋ ๊ฒ์ถ"):
|
| 217 |
+
with gr.Row():
|
| 218 |
+
with gr.Column():
|
| 219 |
+
input_image = gr.Image(label="์
๋ ฅ ์ด๋ฏธ์ง", type="pil")
|
| 220 |
+
|
| 221 |
+
confidence_slider = gr.Slider(
|
| 222 |
+
0.1, 0.9, 0.3,
|
| 223 |
+
label="RT-DETR ์ ๋ขฐ๋ ์๊ณ๊ฐ",
|
| 224 |
+
info="๋ฎ์์๋ก ๋ ๋ง์ด ๊ฒ์ถ"
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
filter_slider = gr.Slider(
|
| 228 |
+
20, 80, 50,
|
| 229 |
+
label="ํํฐ ์ ์ ์๊ณ๊ฐ",
|
| 230 |
+
info="๋์์๋ก ์๊ฒฉํ๊ฒ ํํฐ๋ง"
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
show_all_check = gr.Checkbox(
|
| 234 |
+
label="์ ์ฒด ๊ฒ์ถ ๊ฒฐ๊ณผ ํ์ (ํ์)",
|
| 235 |
+
value=False
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
detect_btn = gr.Button("๐ ๊ฒ์ถ ์คํ", variant="primary", size="lg")
|
| 239 |
+
|
| 240 |
+
# ์์ ์ด๋ฏธ์ง ์ถ๊ฐ
|
| 241 |
+
import os
|
| 242 |
+
import glob
|
| 243 |
+
example_images = sorted(glob.glob("data/251015/251015_*.jpg"))
|
| 244 |
+
if example_images:
|
| 245 |
+
# ์ฒ์ 5๊ฐ๋ง ์์ ๋ก ํ์
|
| 246 |
+
examples_list = [[img, 0.3, 75, False] for img in example_images[:5]]
|
| 247 |
+
gr.Examples(
|
| 248 |
+
examples=examples_list,
|
| 249 |
+
inputs=[input_image, confidence_slider, filter_slider, show_all_check],
|
| 250 |
+
label="๐ท ์์ ์ด๋ฏธ์ง (ํด๋ฆญํ์ฌ ๋ฐ๋ก ํ
์คํธ)"
|
| 251 |
+
)
|
| 252 |
+
|
| 253 |
+
with gr.Column():
|
| 254 |
+
output_image = gr.Image(label="๊ฒ์ถ ๊ฒฐ๊ณผ")
|
| 255 |
+
output_info = gr.Markdown()
|
| 256 |
+
|
| 257 |
+
detect_btn.click(
|
| 258 |
+
interactive_detect,
|
| 259 |
+
[input_image, confidence_slider, filter_slider, show_all_check],
|
| 260 |
+
[output_image, output_info]
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
gr.Markdown("""
|
| 264 |
+
### ๐ก ์ฌ์ฉ ํ
|
| 265 |
+
|
| 266 |
+
- **๊ฒ์ถ์ด ๋๋ฌด ์ ์ ๋**: ์ ๋ขฐ๋์ ํํฐ ์ ์๋ฅผ ๋ฎ์ถ์ธ์
|
| 267 |
+
- **์ค๊ฒ์ถ์ด ๋ง์ ๋**: ํํฐ ์ ์๋ฅผ ๋์ด์ธ์
|
| 268 |
+
- **ํ๋ผ๋ฏธํฐ ํจ๊ณผ ํ์ธ**: "์ ์ฒด ๊ฒ์ถ ํ์"๋ฅผ ์ผ์ ํํฐ๋ง ์ ํ๋ฅผ ๋น๊ตํ์ธ์
|
| 269 |
+
""")
|
| 270 |
+
|
| 271 |
+
# ํญ 2: ์๋ ๋ถ์
|
| 272 |
+
with gr.TabItem("๐ฌ ์๋ ๋ถ์"):
|
| 273 |
+
gr.Markdown("""
|
| 274 |
+
์๋์ผ๋ก ๋ฐ์ด๋ฉ ๋ฐ์ค๋ฅผ ์ง์ ํ์ฌ ํด๋น ์์ญ์ ํน์ง์ ๋ถ์ํฉ๋๋ค.
|
| 275 |
+
์ด๋ฏธ์ง ์ขํ๋ฅผ ์ง์ ์
๋ ฅํ์ธ์.
|
| 276 |
+
""")
|
| 277 |
+
|
| 278 |
+
with gr.Row():
|
| 279 |
+
with gr.Column():
|
| 280 |
+
manual_image = gr.Image(label="๋ถ์ํ ์ด๋ฏธ์ง", type="pil")
|
| 281 |
+
|
| 282 |
+
with gr.Row():
|
| 283 |
+
x1_input = gr.Number(label="x1 (์ข์๋จ X)", value=0)
|
| 284 |
+
y1_input = gr.Number(label="y1 (์ข์๋จ Y)", value=0)
|
| 285 |
+
|
| 286 |
+
with gr.Row():
|
| 287 |
+
x2_input = gr.Number(label="x2 (์ฐํ๋จ X)", value=100)
|
| 288 |
+
y2_input = gr.Number(label="y2 (์ฐํ๋จ Y)", value=100)
|
| 289 |
+
|
| 290 |
+
analyze_btn = gr.Button("๐ ๋ถ์ ์คํ", variant="secondary", size="lg")
|
| 291 |
+
|
| 292 |
+
with gr.Column():
|
| 293 |
+
analysis_output = gr.Markdown()
|
| 294 |
+
|
| 295 |
+
analyze_btn.click(
|
| 296 |
+
analyze_single_detection,
|
| 297 |
+
[manual_image, x1_input, y1_input, x2_input, y2_input],
|
| 298 |
+
analysis_output
|
| 299 |
+
)
|
| 300 |
+
|
| 301 |
+
gr.Markdown("""
|
| 302 |
+
### ๐ ์ขํ ํ์ธ ๋ฐฉ๋ฒ
|
| 303 |
+
|
| 304 |
+
์ด๋ฏธ์ง ๋ทฐ์ด๋ ๊ทธ๋ฆผํ์์ ํฝ์
์ขํ๋ฅผ ํ์ธํ ์ ์์ต๋๋ค:
|
| 305 |
+
- **x1, y1**: ๋ฐ์ด๋ฉ ๋ฐ์ค ์ข์๋จ ์ขํ
|
| 306 |
+
- **x2, y2**: ๋ฐ์ด๋ฉ ๋ฐ์ค ์ฐํ๋จ ์ขํ
|
| 307 |
+
""")
|
| 308 |
+
|
| 309 |
+
gr.Markdown("""
|
| 310 |
+
---
|
| 311 |
+
|
| 312 |
+
### ๐ ํํฐ ์ ์ ๊ธฐ์ค
|
| 313 |
+
|
| 314 |
+
- **75์ ์ด์**: ๋์ ํ๋ฅ ๋ก ์์ฐ (๋
น์)
|
| 315 |
+
- **50~74์ **: ์ค๊ฐ ํ๋ฅ (๋
ธ๋์)
|
| 316 |
+
- **50์ ๋ฏธ๋ง**: ๋ฎ์ ํ๋ฅ (์ฃผํฉ์)
|
| 317 |
+
|
| 318 |
+
**์ ์ ๊ตฌ์ฑ** (์ด 100์ ):
|
| 319 |
+
- ์ข
ํก๋น (2~10): 15์
|
| 320 |
+
- ์ธ์ฅ๋ (<0.25): 15์
|
| 321 |
+
- ๋ฉด์ ๋น (5~50%): 10์
|
| 322 |
+
- ์ฑ๋ (<150): 20์
|
| 323 |
+
- ์์ ์ผ๊ด์ฑ (<30): 15์
|
| 324 |
+
- RT-DETR ์ ๋ขฐ๋: ์ต๋ 25์
|
| 325 |
+
""")
|
| 326 |
+
|
| 327 |
+
if __name__ == "__main__":
|
| 328 |
+
demo.launch(
|
| 329 |
+
server_name="0.0.0.0",
|
| 330 |
+
server_port=7861, # ๋ฉ์ธ ์ฑ๊ณผ ๋ค๋ฅธ ํฌํธ
|
| 331 |
+
share=False
|
| 332 |
+
)
|
requirements.txt
CHANGED
|
@@ -8,6 +8,9 @@ transformers>=4.41.0
|
|
| 8 |
|
| 9 |
# Image Processing
|
| 10 |
pillow>=10.0.0
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
# Inference
|
| 13 |
inference-sdk>=0.9.0
|
|
|
|
| 8 |
|
| 9 |
# Image Processing
|
| 10 |
pillow>=10.0.0
|
| 11 |
+
opencv-python>=4.8.0
|
| 12 |
+
matplotlib>=3.7.0
|
| 13 |
+
seaborn>=0.12.0
|
| 14 |
|
| 15 |
# Inference
|
| 16 |
inference-sdk>=0.9.0
|
test_quantitative_evaluation.py
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""
|
| 3 |
+
์ ๋์ ํ๊ฐ ์คํฌ๋ฆฝํธ
|
| 4 |
+
Ground Truth์ ๋น๊ตํ์ฌ Precision, Recall, F1 Score ๊ณ์ฐ
|
| 5 |
+
"""
|
| 6 |
+
import sys
|
| 7 |
+
sys.stdout.reconfigure(encoding='utf-8')
|
| 8 |
+
|
| 9 |
+
import os
|
| 10 |
+
import json
|
| 11 |
+
import numpy as np
|
| 12 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 13 |
+
import matplotlib.pyplot as plt
|
| 14 |
+
import seaborn as sns
|
| 15 |
+
from datetime import datetime
|
| 16 |
+
from test_visual_validation import (
|
| 17 |
+
load_rtdetr_model,
|
| 18 |
+
detect_with_rtdetr,
|
| 19 |
+
apply_universal_filter
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
def calculate_iou(bbox1, bbox2):
|
| 23 |
+
"""IoU (Intersection over Union) ๊ณ์ฐ"""
|
| 24 |
+
x1_min, y1_min, x1_max, y1_max = bbox1
|
| 25 |
+
x2_min, y2_min, x2_max, y2_max = bbox2
|
| 26 |
+
|
| 27 |
+
# ๊ต์งํฉ ์์ญ
|
| 28 |
+
inter_x_min = max(x1_min, x2_min)
|
| 29 |
+
inter_y_min = max(y1_min, y2_min)
|
| 30 |
+
inter_x_max = min(x1_max, x2_max)
|
| 31 |
+
inter_y_max = min(y1_max, y2_max)
|
| 32 |
+
|
| 33 |
+
if inter_x_max < inter_x_min or inter_y_max < inter_y_min:
|
| 34 |
+
return 0.0
|
| 35 |
+
|
| 36 |
+
inter_area = (inter_x_max - inter_x_min) * (inter_y_max - inter_y_min)
|
| 37 |
+
|
| 38 |
+
# ํฉ์งํฉ ์์ญ
|
| 39 |
+
bbox1_area = (x1_max - x1_min) * (y1_max - y1_min)
|
| 40 |
+
bbox2_area = (x2_max - x2_min) * (y2_max - y2_min)
|
| 41 |
+
union_area = bbox1_area + bbox2_area - inter_area
|
| 42 |
+
|
| 43 |
+
return inter_area / union_area if union_area > 0 else 0.0
|
| 44 |
+
|
| 45 |
+
def evaluate_detection(predictions, ground_truths, iou_threshold=0.5):
|
| 46 |
+
"""๊ฒ์ถ ๊ฒฐ๊ณผ ํ๊ฐ"""
|
| 47 |
+
tp = 0 # True Positive
|
| 48 |
+
fp = 0 # False Positive
|
| 49 |
+
fn = 0 # False Negative
|
| 50 |
+
|
| 51 |
+
matched_gt = set()
|
| 52 |
+
|
| 53 |
+
# ๊ฐ ์์ธก์ ๋ํด
|
| 54 |
+
for pred in predictions:
|
| 55 |
+
pred_bbox = pred['bbox']
|
| 56 |
+
matched = False
|
| 57 |
+
|
| 58 |
+
# Ground truth์ ๋งค์นญ
|
| 59 |
+
for gt_idx, gt in enumerate(ground_truths):
|
| 60 |
+
if gt_idx in matched_gt:
|
| 61 |
+
continue
|
| 62 |
+
|
| 63 |
+
gt_bbox = gt['bbox']
|
| 64 |
+
iou = calculate_iou(pred_bbox, gt_bbox)
|
| 65 |
+
|
| 66 |
+
if iou >= iou_threshold:
|
| 67 |
+
tp += 1
|
| 68 |
+
matched_gt.add(gt_idx)
|
| 69 |
+
matched = True
|
| 70 |
+
break
|
| 71 |
+
|
| 72 |
+
if not matched:
|
| 73 |
+
fp += 1
|
| 74 |
+
|
| 75 |
+
# ๋งค์นญ ์ ๋ ground truth = False Negative
|
| 76 |
+
fn = len(ground_truths) - len(matched_gt)
|
| 77 |
+
|
| 78 |
+
# ๋ฉํธ๋ฆญ ๊ณ์ฐ
|
| 79 |
+
precision = tp / (tp + fp) if (tp + fp) > 0 else 0.0
|
| 80 |
+
recall = tp / (tp + fn) if (tp + fn) > 0 else 0.0
|
| 81 |
+
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0.0
|
| 82 |
+
|
| 83 |
+
return {
|
| 84 |
+
'tp': tp,
|
| 85 |
+
'fp': fp,
|
| 86 |
+
'fn': fn,
|
| 87 |
+
'precision': precision,
|
| 88 |
+
'recall': recall,
|
| 89 |
+
'f1': f1
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
def load_ground_truth(json_path):
|
| 93 |
+
"""Ground truth JSON ๋ก๋
|
| 94 |
+
|
| 95 |
+
์์ ํ์:
|
| 96 |
+
{
|
| 97 |
+
"image1.png": [
|
| 98 |
+
{"bbox": [x1, y1, x2, y2], "label": "shrimp"},
|
| 99 |
+
...
|
| 100 |
+
],
|
| 101 |
+
...
|
| 102 |
+
}
|
| 103 |
+
"""
|
| 104 |
+
if not os.path.exists(json_path):
|
| 105 |
+
print(f"โ ๏ธ Ground truth ํ์ผ์ด ์์ต๋๋ค: {json_path}")
|
| 106 |
+
print("๐ ์์ฑ ๋ฐฉ๋ฒ:")
|
| 107 |
+
print(" ground_truth.json ํ์ผ์ ๋ง๋ค๊ณ ๋ค์ ํ์์ผ๋ก ์์ฑ:")
|
| 108 |
+
print(' {"image.png": [{"bbox": [x1, y1, x2, y2], "label": "shrimp"}]}')
|
| 109 |
+
return {}
|
| 110 |
+
|
| 111 |
+
with open(json_path, 'r', encoding='utf-8') as f:
|
| 112 |
+
return json.load(f)
|
| 113 |
+
|
| 114 |
+
def generate_confusion_matrix(results, output_dir):
|
| 115 |
+
"""Confusion Matrix ์์ฑ"""
|
| 116 |
+
all_tp = sum(r['metrics']['tp'] for r in results)
|
| 117 |
+
all_fp = sum(r['metrics']['fp'] for r in results)
|
| 118 |
+
all_fn = sum(r['metrics']['fn'] for r in results)
|
| 119 |
+
tn = 0 # True Negative (๊ฐ์ฒด ๊ฒ์ถ์์๋ ์ผ๋ฐ์ ์ผ๋ก ์๋ฏธ ์์)
|
| 120 |
+
|
| 121 |
+
matrix = np.array([
|
| 122 |
+
[all_tp, all_fp],
|
| 123 |
+
[all_fn, tn]
|
| 124 |
+
])
|
| 125 |
+
|
| 126 |
+
plt.figure(figsize=(8, 6))
|
| 127 |
+
sns.heatmap(matrix, annot=True, fmt='d', cmap='Blues',
|
| 128 |
+
xticklabels=['Positive', 'Negative'],
|
| 129 |
+
yticklabels=['Predicted Positive', 'Predicted Negative'])
|
| 130 |
+
plt.title('Confusion Matrix')
|
| 131 |
+
plt.ylabel('Actual')
|
| 132 |
+
plt.xlabel('Predicted')
|
| 133 |
+
|
| 134 |
+
output_path = os.path.join(output_dir, 'confusion_matrix.png')
|
| 135 |
+
plt.savefig(output_path, dpi=150, bbox_inches='tight')
|
| 136 |
+
plt.close()
|
| 137 |
+
print(f" ๐ Confusion Matrix ์ ์ฅ: {output_path}")
|
| 138 |
+
|
| 139 |
+
def generate_pr_curve(results_by_threshold, output_dir):
|
| 140 |
+
"""Precision-Recall Curve ์์ฑ"""
|
| 141 |
+
thresholds = sorted(results_by_threshold.keys())
|
| 142 |
+
precisions = [results_by_threshold[t]['avg_precision'] for t in thresholds]
|
| 143 |
+
recalls = [results_by_threshold[t]['avg_recall'] for t in thresholds]
|
| 144 |
+
|
| 145 |
+
plt.figure(figsize=(10, 6))
|
| 146 |
+
plt.plot(recalls, precisions, 'b-o', linewidth=2, markersize=6)
|
| 147 |
+
plt.xlabel('Recall', fontsize=12)
|
| 148 |
+
plt.ylabel('Precision', fontsize=12)
|
| 149 |
+
plt.title('Precision-Recall Curve', fontsize=14)
|
| 150 |
+
plt.grid(True, alpha=0.3)
|
| 151 |
+
plt.xlim([0, 1])
|
| 152 |
+
plt.ylim([0, 1])
|
| 153 |
+
|
| 154 |
+
# ์ต์ ์ง์ ํ์
|
| 155 |
+
f1_scores = [2*p*r/(p+r) if (p+r)>0 else 0 for p, r in zip(precisions, recalls)]
|
| 156 |
+
best_idx = np.argmax(f1_scores)
|
| 157 |
+
plt.plot(recalls[best_idx], precisions[best_idx], 'r*', markersize=15,
|
| 158 |
+
label=f'Best F1={f1_scores[best_idx]:.2f} (threshold={thresholds[best_idx]})')
|
| 159 |
+
plt.legend()
|
| 160 |
+
|
| 161 |
+
output_path = os.path.join(output_dir, 'pr_curve.png')
|
| 162 |
+
plt.savefig(output_path, dpi=150, bbox_inches='tight')
|
| 163 |
+
plt.close()
|
| 164 |
+
print(f" ๐ PR Curve ์ ์ฅ: {output_path}")
|
| 165 |
+
|
| 166 |
+
def visualize_evaluation_result(image_path, predictions, ground_truths, metrics, output_path):
|
| 167 |
+
"""ํ๊ฐ ๊ฒฐ๊ณผ ์๊ฐํ"""
|
| 168 |
+
image = Image.open(image_path).convert('RGB')
|
| 169 |
+
draw = ImageDraw.Draw(image)
|
| 170 |
+
|
| 171 |
+
try:
|
| 172 |
+
font = ImageFont.truetype("arial.ttf", 12)
|
| 173 |
+
font_large = ImageFont.truetype("arial.ttf", 16)
|
| 174 |
+
except:
|
| 175 |
+
font = ImageFont.load_default()
|
| 176 |
+
font_large = ImageFont.load_default()
|
| 177 |
+
|
| 178 |
+
# Ground Truth (๋
น์)
|
| 179 |
+
for gt in ground_truths:
|
| 180 |
+
x1, y1, x2, y2 = gt['bbox']
|
| 181 |
+
draw.rectangle([x1, y1, x2, y2], outline="lime", width=3)
|
| 182 |
+
draw.text((x1, y1 - 15), "GT", fill="lime", font=font)
|
| 183 |
+
|
| 184 |
+
# Predictions (ํ๋์)
|
| 185 |
+
for pred in predictions:
|
| 186 |
+
x1, y1, x2, y2 = pred['bbox']
|
| 187 |
+
draw.rectangle([x1, y1, x2, y2], outline="cyan", width=2)
|
| 188 |
+
label = f"{pred['filter_score']:.0f}"
|
| 189 |
+
draw.text((x1, y2 + 5), label, fill="cyan", font=font)
|
| 190 |
+
|
| 191 |
+
# ๋ฉํธ๋ฆญ ํค๋
|
| 192 |
+
header = f"P={metrics['precision']:.2f} R={metrics['recall']:.2f} F1={metrics['f1']:.2f}"
|
| 193 |
+
draw.rectangle([5, 5, 300, 35], fill="black", outline="white", width=2)
|
| 194 |
+
draw.text((10, 10), header, fill="white", font=font_large)
|
| 195 |
+
|
| 196 |
+
image.save(output_path, quality=95)
|
| 197 |
+
|
| 198 |
+
def run_quantitative_test(test_image_dir, ground_truth_path, confidence=0.3, filter_threshold=50, iou_threshold=0.5):
|
| 199 |
+
"""์ ๋์ ํ๊ฐ ์คํ"""
|
| 200 |
+
print("\n" + "="*60)
|
| 201 |
+
print("๐ ์ ๋์ ํ๊ฐ ํ
์คํธ ์์")
|
| 202 |
+
print("="*60)
|
| 203 |
+
|
| 204 |
+
# Ground truth ๋ก๋
|
| 205 |
+
ground_truths = load_ground_truth(ground_truth_path)
|
| 206 |
+
if not ground_truths:
|
| 207 |
+
return
|
| 208 |
+
|
| 209 |
+
# ๋ชจ๋ธ ๋ก๋
|
| 210 |
+
processor, model = load_rtdetr_model()
|
| 211 |
+
|
| 212 |
+
# ๊ฒฐ๊ณผ ๋๋ ํ ๋ฆฌ
|
| 213 |
+
output_dir = f"test_results/quantitative_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
| 214 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 215 |
+
|
| 216 |
+
print(f"\nโ๏ธ ์ค์ :")
|
| 217 |
+
print(f" - RT-DETR Confidence: {confidence}")
|
| 218 |
+
print(f" - Filter Threshold: {filter_threshold}")
|
| 219 |
+
print(f" - IoU Threshold: {iou_threshold}")
|
| 220 |
+
print(f" - Ground Truth ์ด๋ฏธ์ง: {len(ground_truths)}๊ฐ\n")
|
| 221 |
+
|
| 222 |
+
results = []
|
| 223 |
+
|
| 224 |
+
for filename, gt_list in ground_truths.items():
|
| 225 |
+
img_path = os.path.join(test_image_dir, filename)
|
| 226 |
+
if not os.path.exists(img_path):
|
| 227 |
+
print(f"โ ๏ธ ์ด๋ฏธ์ง๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค: {img_path}")
|
| 228 |
+
continue
|
| 229 |
+
|
| 230 |
+
print(f"\n๐ผ๏ธ ํ๊ฐ ์ค: {filename}")
|
| 231 |
+
print(f" Ground Truth: {len(gt_list)}๊ฐ")
|
| 232 |
+
|
| 233 |
+
# ์ด๋ฏธ์ง ๋ก๋ ๋ฐ ๊ฒ์ถ
|
| 234 |
+
image = Image.open(img_path).convert('RGB')
|
| 235 |
+
all_detections = detect_with_rtdetr(image, processor, model, confidence)
|
| 236 |
+
filtered_detections = apply_universal_filter(all_detections, image, filter_threshold)
|
| 237 |
+
|
| 238 |
+
print(f" ๊ฒ์ถ ๊ฒฐ๊ณผ: {len(filtered_detections)}๊ฐ")
|
| 239 |
+
|
| 240 |
+
# ํ๊ฐ
|
| 241 |
+
metrics = evaluate_detection(filtered_detections, gt_list, iou_threshold)
|
| 242 |
+
|
| 243 |
+
print(f" ๐ Precision: {metrics['precision']:.2%}")
|
| 244 |
+
print(f" ๐ Recall: {metrics['recall']:.2%}")
|
| 245 |
+
print(f" ๐ F1 Score: {metrics['f1']:.2%}")
|
| 246 |
+
print(f" TP={metrics['tp']}, FP={metrics['fp']}, FN={metrics['fn']}")
|
| 247 |
+
|
| 248 |
+
# ์๊ฐํ
|
| 249 |
+
output_path = os.path.join(output_dir, f"eval_{filename}")
|
| 250 |
+
visualize_evaluation_result(img_path, filtered_detections, gt_list, metrics, output_path)
|
| 251 |
+
print(f" ๐พ ์ ์ฅ: {output_path}")
|
| 252 |
+
|
| 253 |
+
results.append({
|
| 254 |
+
'filename': filename,
|
| 255 |
+
'metrics': metrics,
|
| 256 |
+
'gt_count': len(gt_list),
|
| 257 |
+
'pred_count': len(filtered_detections)
|
| 258 |
+
})
|
| 259 |
+
|
| 260 |
+
# ์ ์ฒด ํ๊ท
|
| 261 |
+
if results:
|
| 262 |
+
avg_precision = np.mean([r['metrics']['precision'] for r in results])
|
| 263 |
+
avg_recall = np.mean([r['metrics']['recall'] for r in results])
|
| 264 |
+
avg_f1 = np.mean([r['metrics']['f1'] for r in results])
|
| 265 |
+
|
| 266 |
+
print("\n" + "="*60)
|
| 267 |
+
print("๐ ์ ์ฒด ํ๊ท ์ฑ๋ฅ")
|
| 268 |
+
print("="*60)
|
| 269 |
+
print(f"Precision: {avg_precision:.2%}")
|
| 270 |
+
print(f"Recall: {avg_recall:.2%}")
|
| 271 |
+
print(f"F1 Score: {avg_f1:.2%}")
|
| 272 |
+
print("="*60)
|
| 273 |
+
|
| 274 |
+
# Confusion Matrix ์์ฑ
|
| 275 |
+
generate_confusion_matrix(results, output_dir)
|
| 276 |
+
|
| 277 |
+
# ๊ฒฐ๊ณผ ์ ์ฅ
|
| 278 |
+
summary = {
|
| 279 |
+
'config': {
|
| 280 |
+
'confidence': confidence,
|
| 281 |
+
'filter_threshold': filter_threshold,
|
| 282 |
+
'iou_threshold': iou_threshold
|
| 283 |
+
},
|
| 284 |
+
'avg_metrics': {
|
| 285 |
+
'precision': avg_precision,
|
| 286 |
+
'recall': avg_recall,
|
| 287 |
+
'f1': avg_f1
|
| 288 |
+
},
|
| 289 |
+
'per_image_results': results
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
json_path = os.path.join(output_dir, 'evaluation_summary.json')
|
| 293 |
+
with open(json_path, 'w', encoding='utf-8') as f:
|
| 294 |
+
json.dump(summary, f, ensure_ascii=False, indent=2)
|
| 295 |
+
|
| 296 |
+
print(f"\n๐ ํ๊ฐ ๊ฒฐ๊ณผ ์ ์ฅ: {json_path}")
|
| 297 |
+
|
| 298 |
+
if __name__ == "__main__":
|
| 299 |
+
# ํ
์คํธ ์คํ
|
| 300 |
+
TEST_DIR = "imgs"
|
| 301 |
+
GT_PATH = "ground_truth.json"
|
| 302 |
+
|
| 303 |
+
if not os.path.exists(GT_PATH):
|
| 304 |
+
print("โ ๏ธ ground_truth.json ํ์ผ์ด ํ์ํฉ๋๋ค.")
|
| 305 |
+
print("\n๐ ground_truth.json ์์ฑ ์์:")
|
| 306 |
+
print("""
|
| 307 |
+
{
|
| 308 |
+
"test_shrimp_tank.png": [
|
| 309 |
+
{"bbox": [100, 150, 200, 180], "label": "shrimp"},
|
| 310 |
+
{"bbox": [300, 250, 420, 290], "label": "shrimp"}
|
| 311 |
+
],
|
| 312 |
+
"image (1).webp": [
|
| 313 |
+
{"bbox": [500, 600, 800, 700], "label": "shrimp"}
|
| 314 |
+
]
|
| 315 |
+
}
|
| 316 |
+
""")
|
| 317 |
+
else:
|
| 318 |
+
run_quantitative_test(
|
| 319 |
+
test_image_dir=TEST_DIR,
|
| 320 |
+
ground_truth_path=GT_PATH,
|
| 321 |
+
confidence=0.3,
|
| 322 |
+
filter_threshold=50,
|
| 323 |
+
iou_threshold=0.5
|
| 324 |
+
)
|
test_visual_validation.py
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""
|
| 3 |
+
์๊ฐ์ ๊ฒ์ฆ ํ
์คํธ ์คํฌ๋ฆฝํธ
|
| 4 |
+
RT-DETR + Universal Shrimp Filter์ ๊ฒ์ถ ๊ฒฐ๊ณผ๋ฅผ ์๊ฐ์ ์ผ๋ก ๊ฒ์ฆ
|
| 5 |
+
"""
|
| 6 |
+
import sys
|
| 7 |
+
sys.stdout.reconfigure(encoding='utf-8')
|
| 8 |
+
|
| 9 |
+
import os
|
| 10 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 11 |
+
import json
|
| 12 |
+
from datetime import datetime
|
| 13 |
+
import cv2
|
| 14 |
+
import numpy as np
|
| 15 |
+
from transformers import RTDetrForObjectDetection, RTDetrImageProcessor
|
| 16 |
+
import torch
|
| 17 |
+
|
| 18 |
+
# ์ถ๋ ฅ ๋๋ ํ ๋ฆฌ ์ค์
|
| 19 |
+
OUTPUT_DIR = "test_results"
|
| 20 |
+
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 21 |
+
|
| 22 |
+
def load_rtdetr_model():
|
| 23 |
+
"""RT-DETR ๋ชจ๋ธ ๋ก๋"""
|
| 24 |
+
print("๐ RT-DETR ๋ชจ๋ธ ๋ก๋ฉ ์ค...")
|
| 25 |
+
processor = RTDetrImageProcessor.from_pretrained("PekingU/rtdetr_r50vd_coco_o365")
|
| 26 |
+
model = RTDetrForObjectDetection.from_pretrained("PekingU/rtdetr_r50vd_coco_o365")
|
| 27 |
+
model.eval()
|
| 28 |
+
print("โ
RT-DETR ๋ก๋ฉ ์๋ฃ")
|
| 29 |
+
return processor, model
|
| 30 |
+
|
| 31 |
+
def detect_with_rtdetr(image, processor, model, confidence=0.3):
|
| 32 |
+
"""RT-DETR๋ก ๊ฐ์ฒด ๊ฒ์ถ"""
|
| 33 |
+
inputs = processor(images=image, return_tensors="pt")
|
| 34 |
+
with torch.no_grad():
|
| 35 |
+
outputs = model(**inputs)
|
| 36 |
+
|
| 37 |
+
target_sizes = torch.tensor([image.size[::-1]])
|
| 38 |
+
results = processor.post_process_object_detection(
|
| 39 |
+
outputs,
|
| 40 |
+
target_sizes=target_sizes,
|
| 41 |
+
threshold=confidence
|
| 42 |
+
)[0]
|
| 43 |
+
|
| 44 |
+
detections = []
|
| 45 |
+
for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
|
| 46 |
+
x1, y1, x2, y2 = box.tolist()
|
| 47 |
+
detections.append({
|
| 48 |
+
'bbox': [x1, y1, x2, y2],
|
| 49 |
+
'confidence': score.item(),
|
| 50 |
+
'label': label.item()
|
| 51 |
+
})
|
| 52 |
+
|
| 53 |
+
return detections
|
| 54 |
+
|
| 55 |
+
def calculate_morphological_features(bbox, image_size):
|
| 56 |
+
"""ํํํ์ ํน์ง ๊ณ์ฐ"""
|
| 57 |
+
x1, y1, x2, y2 = bbox
|
| 58 |
+
width = x2 - x1
|
| 59 |
+
height = y2 - y1
|
| 60 |
+
|
| 61 |
+
# Aspect ratio (๊ธด ์ชฝ / ์งง์ ์ชฝ)
|
| 62 |
+
aspect_ratio = max(width, height) / max(min(width, height), 1)
|
| 63 |
+
|
| 64 |
+
# Area ratio (์ด๋ฏธ์ง ๋๋น ๋ฉด์ )
|
| 65 |
+
img_w, img_h = image_size
|
| 66 |
+
area_ratio = (width * height) / (img_w * img_h)
|
| 67 |
+
|
| 68 |
+
# Compactness (4ฯ * Area / Perimeterยฒ)
|
| 69 |
+
perimeter = 2 * (width + height)
|
| 70 |
+
compactness = (4 * np.pi * width * height) / max(perimeter ** 2, 1)
|
| 71 |
+
|
| 72 |
+
return {
|
| 73 |
+
'aspect_ratio': aspect_ratio,
|
| 74 |
+
'area_ratio': area_ratio,
|
| 75 |
+
'compactness': compactness,
|
| 76 |
+
'width': width,
|
| 77 |
+
'height': height
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
def calculate_visual_features(image_pil, bbox):
|
| 81 |
+
"""์๊ฐ์ ํน์ง ๊ณ์ฐ (์์, ํ
์ค์ฒ)"""
|
| 82 |
+
# PIL โ OpenCV
|
| 83 |
+
image_cv = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR)
|
| 84 |
+
x1, y1, x2, y2 = [int(v) for v in bbox]
|
| 85 |
+
|
| 86 |
+
# ๋ฐ์ด๋ฉ ๋ฐ์ค ์์ญ ์ถ์ถ
|
| 87 |
+
roi = image_cv[y1:y2, x1:x2]
|
| 88 |
+
if roi.size == 0:
|
| 89 |
+
return {'saturation': 255, 'color_std': 255}
|
| 90 |
+
|
| 91 |
+
# HSV ๋ณํ
|
| 92 |
+
hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV)
|
| 93 |
+
|
| 94 |
+
# ์ฑ๋ (Saturation)
|
| 95 |
+
saturation = np.mean(hsv[:, :, 1])
|
| 96 |
+
|
| 97 |
+
# ์์ ์ผ๊ด์ฑ (ํ์คํธ์ฐจ)
|
| 98 |
+
color_std = np.std(hsv[:, :, 0])
|
| 99 |
+
|
| 100 |
+
return {
|
| 101 |
+
'saturation': saturation,
|
| 102 |
+
'color_std': color_std
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
def apply_universal_filter(detections, image, threshold=50):
|
| 106 |
+
"""๋ฒ์ฉ ์์ฐ ํํฐ ์ ์ฉ"""
|
| 107 |
+
img_size = image.size
|
| 108 |
+
filtered = []
|
| 109 |
+
|
| 110 |
+
for det in detections:
|
| 111 |
+
bbox = det['bbox']
|
| 112 |
+
|
| 113 |
+
# 1. ํํํ์ ํน์ง
|
| 114 |
+
morph = calculate_morphological_features(bbox, img_size)
|
| 115 |
+
|
| 116 |
+
# 2. ์๊ฐ์ ํน์ง
|
| 117 |
+
visual = calculate_visual_features(image, bbox)
|
| 118 |
+
|
| 119 |
+
# 3. ์ ์ ๊ณ์ฐ
|
| 120 |
+
score = 0
|
| 121 |
+
reasons = []
|
| 122 |
+
|
| 123 |
+
# Aspect ratio (3:1 ~ 10:1) - ์(2:1~3:1) ์ ์ธํ๊ธฐ ์ํด ํํ ์ํฅ
|
| 124 |
+
if 3.0 <= morph['aspect_ratio'] <= 10.0:
|
| 125 |
+
score += 15
|
| 126 |
+
reasons.append(f"โ ์ข
ํก๋น {morph['aspect_ratio']:.1f}")
|
| 127 |
+
elif 2.0 <= morph['aspect_ratio'] < 3.0:
|
| 128 |
+
# ์ฝ๊ฐ ๊ฐ์ (์์ผ ๊ฐ๋ฅ์ฑ)
|
| 129 |
+
score += 8
|
| 130 |
+
reasons.append(f"โณ ์ข
ํก๋น {morph['aspect_ratio']:.1f}")
|
| 131 |
+
else:
|
| 132 |
+
reasons.append(f"โ ์ข
ํก๋น {morph['aspect_ratio']:.1f}")
|
| 133 |
+
|
| 134 |
+
# Compactness (< 0.50, ๊ธด ํํ) - ์ธก์ ์ฉ ์์ฐ๋ ๋ชธ์ด ํด์ ธ์์, ์(0.6~0.8) ์ ์ธ
|
| 135 |
+
if morph['compactness'] < 0.50:
|
| 136 |
+
score += 20 # ๊ฐ์ค์น ์ฆ๊ฐ: 15 โ 20
|
| 137 |
+
reasons.append(f"โ ์ธ์ฅ๋ {morph['compactness']:.2f}")
|
| 138 |
+
else:
|
| 139 |
+
reasons.append(f"โ ์ธ์ฅ๋ {morph['compactness']:.2f}")
|
| 140 |
+
score -= 10 # ํจ๋ํฐ ์ถ๊ฐ
|
| 141 |
+
|
| 142 |
+
# Area ratio (5% ~ 50%)
|
| 143 |
+
if 0.05 <= morph['area_ratio'] <= 0.50:
|
| 144 |
+
score += 10
|
| 145 |
+
reasons.append(f"โ ๋ฉด์ ๋น {morph['area_ratio']*100:.1f}%")
|
| 146 |
+
else:
|
| 147 |
+
reasons.append(f"โ ๋ฉด์ ๋น {morph['area_ratio']*100:.1f}%")
|
| 148 |
+
|
| 149 |
+
# Saturation (๋ฎ์์๋ก ์ฃฝ์ ์์ฐ) - ์ํ: ํฌ๋ช
ํ ์์ฐ๋ ํ์ฉ
|
| 150 |
+
if visual['saturation'] < 180:
|
| 151 |
+
score += 20
|
| 152 |
+
reasons.append(f"โ ์ฑ๋ {visual['saturation']:.0f}")
|
| 153 |
+
else:
|
| 154 |
+
reasons.append(f"โ ์ฑ๋ {visual['saturation']:.0f}")
|
| 155 |
+
|
| 156 |
+
# Color consistency (๋ฎ์์๋ก ์ผ๊ด์ฑ ๋์) - ์ํ: ๋ด๋ถ ์ฅ๊ธฐ ๋ณด์ด๋ ํฌ๋ช
์์ฐ ํ์ฉ
|
| 157 |
+
if visual['color_std'] < 60:
|
| 158 |
+
score += 15
|
| 159 |
+
reasons.append(f"โ ์์์ผ๊ด์ฑ {visual['color_std']:.1f}")
|
| 160 |
+
else:
|
| 161 |
+
reasons.append(f"โ ์์์ผ๊ด์ฑ {visual['color_std']:.1f}")
|
| 162 |
+
|
| 163 |
+
# RT-DETR confidence
|
| 164 |
+
score += det['confidence'] * 25
|
| 165 |
+
|
| 166 |
+
det['filter_score'] = score
|
| 167 |
+
det['filter_reasons'] = reasons
|
| 168 |
+
det['morph_features'] = morph
|
| 169 |
+
det['visual_features'] = visual
|
| 170 |
+
|
| 171 |
+
if score >= threshold:
|
| 172 |
+
filtered.append(det)
|
| 173 |
+
|
| 174 |
+
# ์ ์ ์์ผ๋ก ์ ๋ ฌ
|
| 175 |
+
filtered.sort(key=lambda x: x['filter_score'], reverse=True)
|
| 176 |
+
|
| 177 |
+
return filtered
|
| 178 |
+
|
| 179 |
+
def visualize_results(image_path, all_detections, filtered_detections, output_path):
|
| 180 |
+
"""๊ฒ์ถ ๊ฒฐ๊ณผ ์๊ฐํ"""
|
| 181 |
+
image = Image.open(image_path).convert('RGB')
|
| 182 |
+
|
| 183 |
+
# 2๊ฐ ์ด๋ฏธ์ง ์์ฑ (์๋ณธ ๊ฒ์ถ vs ํํฐ๋ง ํ)
|
| 184 |
+
img_all = image.copy()
|
| 185 |
+
img_filtered = image.copy()
|
| 186 |
+
|
| 187 |
+
draw_all = ImageDraw.Draw(img_all)
|
| 188 |
+
draw_filtered = ImageDraw.Draw(img_filtered)
|
| 189 |
+
|
| 190 |
+
try:
|
| 191 |
+
font = ImageFont.truetype("arial.ttf", 12)
|
| 192 |
+
font_large = ImageFont.truetype("arial.ttf", 16)
|
| 193 |
+
except:
|
| 194 |
+
font = ImageFont.load_default()
|
| 195 |
+
font_large = ImageFont.load_default()
|
| 196 |
+
|
| 197 |
+
# ์ ์ฒด ๊ฒ์ถ ๊ฒฐ๊ณผ (ํ์)
|
| 198 |
+
for det in all_detections:
|
| 199 |
+
x1, y1, x2, y2 = det['bbox']
|
| 200 |
+
draw_all.rectangle([x1, y1, x2, y2], outline="gray", width=2)
|
| 201 |
+
label = f"{det['confidence']:.0%}"
|
| 202 |
+
draw_all.text((x1, y1 - 15), label, fill="gray", font=font)
|
| 203 |
+
|
| 204 |
+
# ํํฐ๋ง๋ ๊ฒฐ๊ณผ (์์๋ณ)
|
| 205 |
+
for idx, det in enumerate(filtered_detections, 1):
|
| 206 |
+
x1, y1, x2, y2 = det['bbox']
|
| 207 |
+
score = det['filter_score']
|
| 208 |
+
|
| 209 |
+
# ์ ์์ ๋ฐ๋ผ ์์
|
| 210 |
+
if score >= 75:
|
| 211 |
+
color = "lime"
|
| 212 |
+
elif score >= 50:
|
| 213 |
+
color = "yellow"
|
| 214 |
+
else:
|
| 215 |
+
color = "orange"
|
| 216 |
+
|
| 217 |
+
# ๋ ์ด๋ฏธ์ง ๋ชจ๋์ ๊ทธ๋ฆฌ๊ธฐ
|
| 218 |
+
for draw in [draw_all, draw_filtered]:
|
| 219 |
+
draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
|
| 220 |
+
label = f"#{idx} {score:.0f}์ "
|
| 221 |
+
bbox = draw.textbbox((x1, y1 - 25), label, font=font)
|
| 222 |
+
draw.rectangle(bbox, fill=color)
|
| 223 |
+
draw.text((x1, y1 - 25), label, fill="black", font=font)
|
| 224 |
+
|
| 225 |
+
# ํค๋
|
| 226 |
+
header_all = f"์ ์ฒด ๊ฒ์ถ: {len(all_detections)}๊ฐ"
|
| 227 |
+
header_filtered = f"ํํฐ๋ง ํ: {len(filtered_detections)}๊ฐ"
|
| 228 |
+
|
| 229 |
+
draw_all.rectangle([5, 5, 200, 35], fill="black", outline="white", width=2)
|
| 230 |
+
draw_all.text((10, 10), header_all, fill="white", font=font_large)
|
| 231 |
+
|
| 232 |
+
draw_filtered.rectangle([5, 5, 200, 35], fill="black", outline="lime", width=2)
|
| 233 |
+
draw_filtered.text((10, 10), header_filtered, fill="lime", font=font_large)
|
| 234 |
+
|
| 235 |
+
# ๋ณ๋ ฌ ์ ์ฅ
|
| 236 |
+
combined_width = img_all.width * 2 + 20
|
| 237 |
+
combined_height = img_all.height
|
| 238 |
+
combined = Image.new('RGB', (combined_width, combined_height), color='white')
|
| 239 |
+
|
| 240 |
+
combined.paste(img_all, (0, 0))
|
| 241 |
+
combined.paste(img_filtered, (img_all.width + 20, 0))
|
| 242 |
+
|
| 243 |
+
combined.save(output_path, quality=95)
|
| 244 |
+
print(f" ๐พ ์ ์ฅ: {output_path}")
|
| 245 |
+
|
| 246 |
+
return len(all_detections), len(filtered_detections)
|
| 247 |
+
|
| 248 |
+
def run_visual_test(test_image_dir, confidence=0.3, filter_threshold=75):
|
| 249 |
+
"""์๊ฐ์ ๊ฒ์ฆ ํ
์คํธ ์คํ"""
|
| 250 |
+
print("\n" + "="*60)
|
| 251 |
+
print("๐งช ์๊ฐ์ ๊ฒ์ฆ ํ
์คํธ ์์")
|
| 252 |
+
print("="*60)
|
| 253 |
+
|
| 254 |
+
# ๋ชจ๋ธ ๋ก๋
|
| 255 |
+
processor, model = load_rtdetr_model()
|
| 256 |
+
|
| 257 |
+
# ์ด๋ฏธ์ง ํ์ผ ์ฐพ๊ธฐ
|
| 258 |
+
image_files = []
|
| 259 |
+
for ext in ['*.png', '*.jpg', '*.jpeg', '*.webp']:
|
| 260 |
+
import glob
|
| 261 |
+
image_files.extend(glob.glob(os.path.join(test_image_dir, ext)))
|
| 262 |
+
|
| 263 |
+
if not image_files:
|
| 264 |
+
print(f"โ {test_image_dir}์์ ์ด๋ฏธ์ง๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.")
|
| 265 |
+
return
|
| 266 |
+
|
| 267 |
+
print(f"\n๐ ํ
์คํธ ์ด๋ฏธ์ง: {len(image_files)}๊ฐ")
|
| 268 |
+
print(f"โ๏ธ ์ค์ : Confidence={confidence}, Filter Threshold={filter_threshold}\n")
|
| 269 |
+
|
| 270 |
+
# ๊ฒฐ๊ณผ ์ ์ฅ
|
| 271 |
+
results = []
|
| 272 |
+
|
| 273 |
+
for img_path in image_files:
|
| 274 |
+
filename = os.path.basename(img_path)
|
| 275 |
+
print(f"\n๐ผ๏ธ ์ฒ๋ฆฌ ์ค: {filename}")
|
| 276 |
+
|
| 277 |
+
# ์ด๋ฏธ์ง ๋ก๋
|
| 278 |
+
image = Image.open(img_path).convert('RGB')
|
| 279 |
+
|
| 280 |
+
# RT-DETR ๊ฒ์ถ
|
| 281 |
+
all_detections = detect_with_rtdetr(image, processor, model, confidence)
|
| 282 |
+
print(f" ๐ RT-DETR ๊ฒ์ถ: {len(all_detections)}๊ฐ")
|
| 283 |
+
|
| 284 |
+
# ํํฐ ์ ์ฉ
|
| 285 |
+
filtered_detections = apply_universal_filter(all_detections, image, filter_threshold)
|
| 286 |
+
print(f" โ
ํํฐ๋ง ํ: {len(filtered_detections)}๊ฐ")
|
| 287 |
+
|
| 288 |
+
# ์๊ฐํ
|
| 289 |
+
output_path = os.path.join(OUTPUT_DIR, f"result_{filename}")
|
| 290 |
+
all_count, filtered_count = visualize_results(
|
| 291 |
+
img_path, all_detections, filtered_detections, output_path
|
| 292 |
+
)
|
| 293 |
+
|
| 294 |
+
# ํํฐ๋ง๋ ๊ฐ์ฒด ์ธ๋ถ ์ ๋ณด
|
| 295 |
+
if filtered_detections:
|
| 296 |
+
print(f"\n ๐ ๊ฒ์ถ๋ ๏ฟฝ๏ฟฝ๏ฟฝ์ฐ:")
|
| 297 |
+
for idx, det in enumerate(filtered_detections[:3], 1): # ์์ 3๊ฐ๋ง
|
| 298 |
+
print(f" #{idx} ์ ์: {det['filter_score']:.0f}์ ")
|
| 299 |
+
for reason in det['filter_reasons'][:3]:
|
| 300 |
+
print(f" {reason}")
|
| 301 |
+
|
| 302 |
+
# ๊ฒฐ๊ณผ ๊ธฐ๋ก
|
| 303 |
+
results.append({
|
| 304 |
+
'filename': filename,
|
| 305 |
+
'all_detections': all_count,
|
| 306 |
+
'filtered_detections': filtered_count,
|
| 307 |
+
'details': [{
|
| 308 |
+
'bbox': det['bbox'],
|
| 309 |
+
'confidence': det['confidence'],
|
| 310 |
+
'filter_score': det['filter_score'],
|
| 311 |
+
'reasons': det['filter_reasons']
|
| 312 |
+
} for det in filtered_detections]
|
| 313 |
+
})
|
| 314 |
+
|
| 315 |
+
# JSON ์ ์ฅ
|
| 316 |
+
result_json_path = os.path.join(OUTPUT_DIR, f"test_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json")
|
| 317 |
+
with open(result_json_path, 'w', encoding='utf-8') as f:
|
| 318 |
+
json.dump(results, f, ensure_ascii=False, indent=2)
|
| 319 |
+
|
| 320 |
+
print("\n" + "="*60)
|
| 321 |
+
print("โ
ํ
์คํธ ์๋ฃ")
|
| 322 |
+
print(f"๐ ๊ฒฐ๊ณผ ์ ์ฅ ์์น: {OUTPUT_DIR}/")
|
| 323 |
+
print(f"๐ JSON ๊ฒฐ๊ณผ: {result_json_path}")
|
| 324 |
+
print("="*60)
|
| 325 |
+
|
| 326 |
+
if __name__ == "__main__":
|
| 327 |
+
# ํ
์คํธ ์คํ
|
| 328 |
+
TEST_DIR = "imgs" # ํ
์คํธํ ์ด๋ฏธ์ง ๋๋ ํ ๋ฆฌ
|
| 329 |
+
|
| 330 |
+
if not os.path.exists(TEST_DIR):
|
| 331 |
+
print(f"โ ๋๋ ํ ๋ฆฌ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค: {TEST_DIR}")
|
| 332 |
+
print("๐ก ์ฌ์ฉ๋ฒ: python test_visual_validation.py")
|
| 333 |
+
print(" imgs/ ๋๋ ํ ๋ฆฌ์ ํ
์คํธ ์ด๋ฏธ์ง๋ฅผ ๋ฃ์ด์ฃผ์ธ์.")
|
| 334 |
+
else:
|
| 335 |
+
run_visual_test(
|
| 336 |
+
test_image_dir=TEST_DIR,
|
| 337 |
+
confidence=0.3, # RT-DETR ์ ๋ขฐ๋
|
| 338 |
+
filter_threshold=50 # ํํฐ ์ ์ ์๊ณ๊ฐ
|
| 339 |
+
)
|