Iris314 commited on
Commit
a813fbf
·
verified ·
1 Parent(s): cf22f71

Upload 4 files

Browse files
frige_detect/annotated_image.jpg ADDED
frige_detect/detect.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Detect ingredients using a Roboflow model with preprocessing:
4
+ - Resize images to 640x640 if needed.
5
+ - Perform detection.
6
+ - Classify object sizes via K-Means.
7
+ - Generate JSON and annotated image outputs.
8
+ """
9
+
10
+ import json
11
+ import os
12
+ import tempfile
13
+ from dataclasses import dataclass
14
+
15
+ import cv2
16
+ import numpy as np
17
+ from roboflow import Roboflow
18
+ from sklearn.cluster import KMeans
19
+ import supervision as sv
20
+
21
+
22
+ @dataclass
23
+ class RoboflowCredentials:
24
+ api_key: str
25
+ project_name: str
26
+ version: int = 1
27
+
28
+
29
+ def load_roboflow_credentials(path: str) -> RoboflowCredentials:
30
+ """Load Roboflow API credentials from a simple key=value text file."""
31
+ if not os.path.exists(path):
32
+ raise FileNotFoundError(
33
+ f"Roboflow credential file not found: {path}."
34
+ )
35
+
36
+ api_key = None
37
+ project_name = None
38
+ version = 1
39
+
40
+ with open(path, "r", encoding="utf-8") as f:
41
+ for line in f:
42
+ line = line.strip()
43
+ if not line or line.startswith("#"):
44
+ continue
45
+ if "=" not in line:
46
+ continue
47
+ key, value = line.split("=", 1)
48
+ key = key.strip().lower()
49
+ value = value.strip()
50
+ if key == "api_key":
51
+ api_key = value
52
+ elif key == "project_name":
53
+ project_name = value
54
+ elif key == "version":
55
+ try:
56
+ version = int(value)
57
+ except ValueError:
58
+ raise ValueError("Version in credential file must be an integer") from None
59
+
60
+ if not api_key or not project_name:
61
+ raise ValueError(
62
+ "Credential file must contain api_key and project_name entries."
63
+ )
64
+
65
+ return RoboflowCredentials(api_key=api_key, project_name=project_name, version=version)
66
+
67
+ def compute_area_ratios(predictions, img_shape):
68
+ """Compute area ratio (bbox area / image area) for each detection."""
69
+ img_area = float(img_shape[0] * img_shape[1])
70
+ ratios = []
71
+ for pred in predictions:
72
+ area = pred["width"] * pred["height"]
73
+ ratios.append(area / img_area)
74
+ return np.array(ratios).reshape(-1, 1)
75
+
76
+ def cluster_sizes(area_ratios):
77
+ """Cluster area ratios into two groups using K-Means and return size labels."""
78
+ kmeans = KMeans(n_clusters=2, init="k-means++", random_state=0)
79
+ labels = kmeans.fit_predict(area_ratios)
80
+ centroids = kmeans.cluster_centers_.flatten()
81
+ large_cluster = np.argmax(centroids)
82
+ return ["large" if lbl == large_cluster else "small" for lbl in labels]
83
+
84
+ def detect_and_generate(
85
+ image_path: str,
86
+ credentials: RoboflowCredentials,
87
+ conf_threshold: float = 0.4,
88
+ overlap_threshold: float = 0.3,
89
+ conf_split: float = 0.7,
90
+ output_json: str = "recipe_input.json",
91
+ output_image: str = "annotated_image.jpg"
92
+ ):
93
+ """
94
+ Resize image if necessary, run detection, classify sizes via K-Means, and
95
+ create both JSON output and annotated image.
96
+
97
+ Args:
98
+ image_path (str): Path to the original image.
99
+ api_key (str): Roboflow API key.
100
+ project_name (str): Roboflow project name.
101
+ version (int): Model version.
102
+ conf_threshold (float): Minimum confidence threshold (0–1).
103
+ overlap_threshold (float): NMS overlap threshold (0–1).
104
+ conf_split (float): Threshold for high/low confidence lists.
105
+ output_json (str): Output JSON filename.
106
+ output_image (str): Output annotated image filename.
107
+
108
+ Returns:
109
+ dict: Recipe input JSON structure.
110
+ """
111
+ # Load original image
112
+ original_img = cv2.imread(image_path)
113
+ if original_img is None:
114
+ raise FileNotFoundError(f"Image not found: {image_path}")
115
+
116
+ height, width = original_img.shape[:2]
117
+
118
+ # Preprocess: resize to 640x640 if needed, and save to a temp file
119
+ if height != 640 or width != 640:
120
+ resized_img = cv2.resize(original_img, (640, 640))
121
+ # create temporary file via mkstemp; close fd to avoid locking
122
+ fd, tmp_path = tempfile.mkstemp(suffix=".jpg")
123
+ os.close(fd)
124
+ cv2.imwrite(tmp_path, resized_img)
125
+ detection_path = tmp_path
126
+ img_for_annotation = resized_img
127
+ else:
128
+ detection_path = image_path
129
+ img_for_annotation = original_img
130
+
131
+ # Initialize Roboflow model
132
+ rf = Roboflow(api_key=credentials.api_key)
133
+ model = rf.workspace().project(credentials.project_name).version(credentials.version).model
134
+
135
+ # Run prediction
136
+ response = model.predict(
137
+ detection_path,
138
+ confidence=int(conf_threshold * 100),
139
+ overlap=int(overlap_threshold * 100)
140
+ ).json()
141
+ predictions = response["predictions"]
142
+
143
+ # Classify sizes using K-Means
144
+ area_ratios = compute_area_ratios(predictions, img_for_annotation.shape)
145
+ size_labels = cluster_sizes(area_ratios)
146
+
147
+ # Build JSON structure
148
+ ingredients = []
149
+ high_conf = []
150
+ low_conf = []
151
+ for pred, size_label in zip(predictions, size_labels):
152
+ name = pred["class"]
153
+ conf = pred["confidence"]
154
+ ingredients.append({
155
+ "name": name,
156
+ "quantity": size_label,
157
+ "confidence": round(conf, 2)
158
+ })
159
+ if conf >= conf_split:
160
+ high_conf.append(name)
161
+ else:
162
+ low_conf.append(name)
163
+
164
+ recipe_json = {
165
+ "ingredients": ingredients,
166
+ "high_confidence_ingredients": high_conf,
167
+ "low_confidence_ingredients": low_conf
168
+ }
169
+
170
+ # Write JSON to file
171
+ with open(output_json, "w", encoding="utf-8") as jf:
172
+ json.dump(recipe_json, jf, indent=4)
173
+
174
+ # Annotate image with bounding boxes and confidence labels
175
+ detections = sv.Detections.from_inference(response)
176
+ label_annotator = sv.LabelAnnotator()
177
+ box_annotator = sv.BoxAnnotator()
178
+
179
+ labels_for_annotation = [
180
+ f"{pred['class']} ({pred['confidence']:.2f})" for pred in predictions
181
+ ]
182
+
183
+ annotated_img = box_annotator.annotate(
184
+ scene=img_for_annotation.copy(),
185
+ detections=detections
186
+ )
187
+ annotated_img = label_annotator.annotate(
188
+ scene=annotated_img,
189
+ detections=detections,
190
+ labels=labels_for_annotation
191
+ )
192
+
193
+ cv2.imwrite(output_image, annotated_img)
194
+
195
+ # Display annotated image (optional, for notebooks)
196
+ # Clean up temporary file
197
+ if height != 640 or width != 640:
198
+ try:
199
+ os.remove(tmp_path)
200
+ except PermissionError:
201
+ # If still locked on Windows, delay deletion or log a warning
202
+ pass
203
+
204
+ return {
205
+ "recipe_json": recipe_json,
206
+ "output_json_path": output_json,
207
+ "annotated_image_path": output_image,
208
+ }
frige_detect/recipe_input.json ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ingredients": [
3
+ {
4
+ "name": "sugar",
5
+ "quantity": "large",
6
+ "confidence": 0.91
7
+ },
8
+ {
9
+ "name": "chicken",
10
+ "quantity": "large",
11
+ "confidence": 0.91
12
+ },
13
+ {
14
+ "name": "milk",
15
+ "quantity": "large",
16
+ "confidence": 0.89
17
+ },
18
+ {
19
+ "name": "flour",
20
+ "quantity": "large",
21
+ "confidence": 0.88
22
+ },
23
+ {
24
+ "name": "eggs",
25
+ "quantity": "small",
26
+ "confidence": 0.88
27
+ },
28
+ {
29
+ "name": "apple",
30
+ "quantity": "large",
31
+ "confidence": 0.86
32
+ },
33
+ {
34
+ "name": "corn",
35
+ "quantity": "small",
36
+ "confidence": 0.85
37
+ },
38
+ {
39
+ "name": "blueberries",
40
+ "quantity": "small",
41
+ "confidence": 0.83
42
+ },
43
+ {
44
+ "name": "chicken_breast",
45
+ "quantity": "large",
46
+ "confidence": 0.82
47
+ },
48
+ {
49
+ "name": "ground_beef",
50
+ "quantity": "large",
51
+ "confidence": 0.81
52
+ },
53
+ {
54
+ "name": "beef",
55
+ "quantity": "large",
56
+ "confidence": 0.77
57
+ },
58
+ {
59
+ "name": "carrot",
60
+ "quantity": "large",
61
+ "confidence": 0.75
62
+ },
63
+ {
64
+ "name": "bread",
65
+ "quantity": "large",
66
+ "confidence": 0.51
67
+ }
68
+ ],
69
+ "high_confidence_ingredients": [
70
+ "sugar",
71
+ "chicken",
72
+ "milk",
73
+ "flour",
74
+ "eggs",
75
+ "apple",
76
+ "corn",
77
+ "blueberries",
78
+ "chicken_breast",
79
+ "ground_beef",
80
+ "beef",
81
+ "carrot"
82
+ ],
83
+ "low_confidence_ingredients": [
84
+ "bread"
85
+ ]
86
+ }
frige_detect/roboflow_credentials.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # Roboflow credentials used by the app and detector
2
+ api_key=DgOLnmYH3XuE2Aikk7a6
3
+ project_name=nutrition-object-detection
4
+ version=1