1st classifier upd
Browse files- app.py +6 -3
- models.py +1 -1
- utils.py +39 -63
- weights/{class1.pth → class.pth} +1 -1
app.py
CHANGED
|
@@ -78,7 +78,9 @@ async def predict2(
|
|
| 78 |
mask_256 = decode_base64_mask(mask_256_base64)
|
| 79 |
|
| 80 |
# 3. Letterbox + маска + crop + resize до 100×100
|
| 81 |
-
cropped_100 = apply_mask_and_crop_letterbox(original_np, mask_256
|
|
|
|
|
|
|
| 82 |
|
| 83 |
# 4. Препроцессинг для классификатора
|
| 84 |
input_tensor = preprocess_for_classifier(cropped_100).unsqueeze(0).to(DEVICE)
|
|
@@ -112,10 +114,11 @@ async def predict3(
|
|
| 112 |
mask_256 = decode_base64_mask(mask_256_base64)
|
| 113 |
|
| 114 |
# Вырезаем и готовим 224×224
|
| 115 |
-
cropped_224 =
|
| 116 |
original_np,
|
| 117 |
mask_256,
|
| 118 |
-
margin_ratio=0.05,
|
|
|
|
| 119 |
bg_color=(255, 255, 255)
|
| 120 |
)
|
| 121 |
|
|
|
|
| 78 |
mask_256 = decode_base64_mask(mask_256_base64)
|
| 79 |
|
| 80 |
# 3. Letterbox + маска + crop + resize до 100×100
|
| 81 |
+
cropped_100 = apply_mask_and_crop_letterbox(original_np, mask_256, margin_ratio=0.02,
|
| 82 |
+
target_size=100,
|
| 83 |
+
bg_color=(255, 255, 255))
|
| 84 |
|
| 85 |
# 4. Препроцессинг для классификатора
|
| 86 |
input_tensor = preprocess_for_classifier(cropped_100).unsqueeze(0).to(DEVICE)
|
|
|
|
| 114 |
mask_256 = decode_base64_mask(mask_256_base64)
|
| 115 |
|
| 116 |
# Вырезаем и готовим 224×224
|
| 117 |
+
cropped_224 = apply_mask_and_crop_letterbox(
|
| 118 |
original_np,
|
| 119 |
mask_256,
|
| 120 |
+
margin_ratio=0.05,
|
| 121 |
+
target_size=224,
|
| 122 |
bg_color=(255, 255, 255)
|
| 123 |
)
|
| 124 |
|
models.py
CHANGED
|
@@ -24,7 +24,7 @@ def load_model1(weights_path='weights/seg.pth'):
|
|
| 24 |
return model1
|
| 25 |
|
| 26 |
|
| 27 |
-
def load_model2(weights_path='weights/
|
| 28 |
global model2
|
| 29 |
if model2 is None:
|
| 30 |
model2 = models.mobilenet_v2(pretrained=False)
|
|
|
|
| 24 |
return model1
|
| 25 |
|
| 26 |
|
| 27 |
+
def load_model2(weights_path='weights/class.pth'):
|
| 28 |
global model2
|
| 29 |
if model2 is None:
|
| 30 |
model2 = models.mobilenet_v2(pretrained=False)
|
utils.py
CHANGED
|
@@ -44,8 +44,7 @@ def mask_to_base64(mask: np.ndarray) -> str:
|
|
| 44 |
# ────────────────────────────────────────────────
|
| 45 |
|
| 46 |
# Новые для классификации
|
| 47 |
-
FRUIT_CLASSES = ['apple', 'banana', 'orange', '
|
| 48 |
-
'tomato', 'pear', 'peach', 'cherry', 'lemon']
|
| 49 |
|
| 50 |
|
| 51 |
def decode_base64_mask(base64_str: str) -> np.ndarray:
|
|
@@ -81,36 +80,56 @@ def letterbox_resize(img: np.ndarray, target_size: int = 256) -> tuple[np.ndarra
|
|
| 81 |
return padded, scale, (top, bottom, left, right)
|
| 82 |
|
| 83 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
def apply_mask_and_crop_letterbox(
|
| 85 |
-
orig_img: np.ndarray,
|
| 86 |
-
mask_256: np.ndarray
|
|
|
|
|
|
|
|
|
|
| 87 |
) -> np.ndarray:
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
2. Применяем маску
|
| 91 |
-
3. Находим bbox
|
| 92 |
-
4. Вырезаем + margin
|
| 93 |
-
5. Ресайзим до 100×100
|
| 94 |
-
"""
|
| 95 |
-
letterbox_img, scale, paddings = letterbox_resize(orig_img, 256)
|
| 96 |
top, bottom, left, right = paddings
|
| 97 |
|
| 98 |
-
# Маска уже 256×256 — применяем напрямую
|
| 99 |
masked = letterbox_img.copy()
|
| 100 |
-
masked[mask_256 < 0.5] =
|
| 101 |
|
| 102 |
-
# Находим контуры / bbox
|
| 103 |
mask_bin = (mask_256 > 0.5).astype(np.uint8) * 255
|
| 104 |
contours, _ = cv2.findContours(mask_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
| 105 |
|
| 106 |
if not contours:
|
| 107 |
-
return np.
|
| 108 |
|
| 109 |
cnt = max(contours, key=cv2.contourArea)
|
| 110 |
x, y, bw, bh = cv2.boundingRect(cnt)
|
| 111 |
|
| 112 |
-
|
| 113 |
-
margin = int(max(bw, bh) * 0.02)
|
| 114 |
x1 = max(0, x - margin)
|
| 115 |
y1 = max(0, y - margin)
|
| 116 |
x2 = min(256, x + bw + margin)
|
|
@@ -118,8 +137,8 @@ def apply_mask_and_crop_letterbox(
|
|
| 118 |
|
| 119 |
cropped = masked[y1:y2, x1:x2]
|
| 120 |
|
| 121 |
-
#
|
| 122 |
-
final =
|
| 123 |
|
| 124 |
return final
|
| 125 |
|
|
@@ -149,46 +168,3 @@ def preprocess_for_freshness(img_224: np.ndarray) -> torch.Tensor:
|
|
| 149 |
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
|
| 150 |
])
|
| 151 |
return transform(img_224)
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
def apply_mask_and_crop_letterbox_224(
|
| 155 |
-
orig_img: np.ndarray,
|
| 156 |
-
mask_256: np.ndarray,
|
| 157 |
-
margin_ratio: float = 0.05, # можно подкрутить
|
| 158 |
-
bg_color: tuple = (255, 255, 255) # белый фон — важно!
|
| 159 |
-
) -> np.ndarray:
|
| 160 |
-
"""
|
| 161 |
-
Аналог apply_mask_and_crop_letterbox, но для 224×224
|
| 162 |
-
"""
|
| 163 |
-
# Letterbox до 224×224
|
| 164 |
-
letterbox_img, scale, paddings = letterbox_resize(orig_img, target_size=224)
|
| 165 |
-
top, bottom, left, right = paddings
|
| 166 |
-
|
| 167 |
-
# Применяем маску (маска 256→ресайзим до 224)
|
| 168 |
-
mask_resized = cv2.resize(mask_256, (224, 224), interpolation=cv2.INTER_NEAREST)
|
| 169 |
-
|
| 170 |
-
masked = letterbox_img.copy()
|
| 171 |
-
masked[mask_resized < 0.5] = bg_color # белый фон
|
| 172 |
-
|
| 173 |
-
# Контуры
|
| 174 |
-
mask_bin = (mask_resized > 0.5).astype(np.uint8) * 255
|
| 175 |
-
contours, _ = cv2.findContours(mask_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
| 176 |
-
|
| 177 |
-
if not contours:
|
| 178 |
-
return np.full((224, 224, 3), bg_color, dtype=np.uint8)
|
| 179 |
-
|
| 180 |
-
cnt = max(contours, key=cv2.contourArea)
|
| 181 |
-
x, y, bw, bh = cv2.boundingRect(cnt)
|
| 182 |
-
|
| 183 |
-
margin = int(max(bw, bh) * margin_ratio)
|
| 184 |
-
x1 = max(0, x - margin)
|
| 185 |
-
y1 = max(0, y - margin)
|
| 186 |
-
x2 = min(224, x + bw + margin)
|
| 187 |
-
y2 = min(224, y + bh + margin)
|
| 188 |
-
|
| 189 |
-
cropped = masked[y1:y2, x1:x2]
|
| 190 |
-
|
| 191 |
-
# Финальный resize до 224×224 (если обрезали меньше)
|
| 192 |
-
final = cv2.resize(cropped, (224, 224), interpolation=cv2.INTER_AREA)
|
| 193 |
-
|
| 194 |
-
return final
|
|
|
|
| 44 |
# ────────────────────────────────────────────────
|
| 45 |
|
| 46 |
# Новые для классификации
|
| 47 |
+
FRUIT_CLASSES = ['apple', 'banana', 'orange', 'strawberry', 'pear', 'lemon', 'cucumber', 'plum', 'raspberry', 'watermelon']
|
|
|
|
| 48 |
|
| 49 |
|
| 50 |
def decode_base64_mask(base64_str: str) -> np.ndarray:
|
|
|
|
| 80 |
return padded, scale, (top, bottom, left, right)
|
| 81 |
|
| 82 |
|
| 83 |
+
def letterbox_any_size(
|
| 84 |
+
img: np.ndarray,
|
| 85 |
+
target_size: int = 100, # или 224
|
| 86 |
+
bg_color: tuple = (255, 255, 255)
|
| 87 |
+
) -> np.ndarray:
|
| 88 |
+
""" Универсальный letterbox для любого входного изображения """
|
| 89 |
+
h, w = img.shape[:2]
|
| 90 |
+
scale = min(target_size / h, target_size / w)
|
| 91 |
+
new_h, new_w = int(h * scale), int(w * scale)
|
| 92 |
+
|
| 93 |
+
resized = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_AREA)
|
| 94 |
+
|
| 95 |
+
pad_h = target_size - new_h
|
| 96 |
+
pad_w = target_size - new_w
|
| 97 |
+
top = pad_h // 2
|
| 98 |
+
bottom = pad_h - top
|
| 99 |
+
left = pad_w // 2
|
| 100 |
+
right = pad_w - left
|
| 101 |
+
|
| 102 |
+
padded = cv2.copyMakeBorder(
|
| 103 |
+
resized, top, bottom, left, right,
|
| 104 |
+
cv2.BORDER_CONSTANT, value=bg_color
|
| 105 |
+
)
|
| 106 |
+
return padded
|
| 107 |
+
|
| 108 |
+
|
| 109 |
def apply_mask_and_crop_letterbox(
|
| 110 |
+
orig_img: np.ndarray,
|
| 111 |
+
mask_256: np.ndarray,
|
| 112 |
+
margin_ratio: float = 0.02,
|
| 113 |
+
target_size: int = 100,
|
| 114 |
+
bg_color: tuple = (255, 255, 255)
|
| 115 |
) -> np.ndarray:
|
| 116 |
+
# Letterbox оригинала до 256×256 (для совместимости с маской)
|
| 117 |
+
letterbox_img, _, paddings = letterbox_resize(orig_img, target_size=256)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
top, bottom, left, right = paddings
|
| 119 |
|
|
|
|
| 120 |
masked = letterbox_img.copy()
|
| 121 |
+
masked[mask_256 < 0.5] = bg_color
|
| 122 |
|
|
|
|
| 123 |
mask_bin = (mask_256 > 0.5).astype(np.uint8) * 255
|
| 124 |
contours, _ = cv2.findContours(mask_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
| 125 |
|
| 126 |
if not contours:
|
| 127 |
+
return np.full((target_size, target_size, 3), bg_color, dtype=np.uint8)
|
| 128 |
|
| 129 |
cnt = max(contours, key=cv2.contourArea)
|
| 130 |
x, y, bw, bh = cv2.boundingRect(cnt)
|
| 131 |
|
| 132 |
+
margin = int(max(bw, bh) * margin_ratio)
|
|
|
|
| 133 |
x1 = max(0, x - margin)
|
| 134 |
y1 = max(0, y - margin)
|
| 135 |
x2 = min(256, x + bw + margin)
|
|
|
|
| 137 |
|
| 138 |
cropped = masked[y1:y2, x1:x2]
|
| 139 |
|
| 140 |
+
# Самое важное: letterbox вместо force-resize
|
| 141 |
+
final = letterbox_any_size(cropped, target_size=target_size, bg_color=bg_color)
|
| 142 |
|
| 143 |
return final
|
| 144 |
|
|
|
|
| 168 |
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
|
| 169 |
])
|
| 170 |
return transform(img_224)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
weights/{class1.pth → class.pth}
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 9205515
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0720a4afc5d0649b9af6e1532947af363f7230276172f6345aae6b951be071e5
|
| 3 |
size 9205515
|