+++ config.py
... | ... | @@ -0,0 +1,2 @@ |
1 | +CLASS_NUM = 1 | |
2 | +CLASS_NAME = ["water_body"] |
--- hls_streaming/hls.py
+++ hls_streaming/hls.py
... | ... | @@ -14,35 +14,36 @@ |
14 | 14 |
:param hls_url: hls address |
15 | 15 |
:param cctv_id: cctv_id number(whatever it is, this exists to distinguish from where. Further disscusion is needed with frontend developers.) |
16 | 16 |
:param interval: interval of sampling in seconds |
17 |
- :param buffer_duration: video buffer, 15 seconds is default for ITS video streaming |
|
17 |
+ :param buffer_duration: video buffer, 15 seconds is default for ITS HLS video streaming |
|
18 | 18 |
:param time_zone: default Asia/Seoul |
19 |
+ :param endpoint: API endpoint |
|
19 | 20 |
''' |
20 | 21 |
self.hls_url = hls_url |
21 | 22 |
self.interval = interval |
22 | 23 |
self.buffer_duration = buffer_duration |
23 |
- self.buffer_size = 600 |
|
24 |
+ self.buffer_size = buffer_size |
|
24 | 25 |
self.frame_buffer = [] |
25 |
- self.frame_buffer_lock = Lock() |
|
26 |
+ self.frame_buffer_lock = Lock() # for no memory sharing between receive_stream_packet and process_frames |
|
26 | 27 |
self.captured_frame_count = 0 |
27 | 28 |
self.last_capture_time = 0 |
28 | 29 |
self.start_time = time.time() |
29 | 30 |
self.stop_event = Event() |
30 |
- self.setup_stream() |
|
31 |
- self.cctvid = cctv_id |
|
32 |
- self.time_zone = ZoneInfo(time_zone) |
|
33 |
- self.endpoint = endpoint |
|
34 | 31 |
|
35 |
- def setup_stream(self): |
|
36 | 32 |
self.input_stream = av.open(self.hls_url) |
37 | 33 |
self.video_stream = next(s for s in self.input_stream.streams if s.type == 'video') |
38 | 34 |
self.fps = self.video_stream.guessed_rate.numerator |
39 | 35 |
self.capture_interval = 1 / self.fps |
40 | 36 |
|
41 |
- # ```capture_frames``` and ```process_frames``` work asynchronously (called with Thread) |
|
37 |
+ self.cctvid = cctv_id |
|
38 |
+ self.time_zone = ZoneInfo(time_zone) |
|
39 |
+ self.endpoint = endpoint |
|
40 |
+ |
|
41 |
+ |
|
42 |
+ # ```receive_stream_packet``` and ```process_frames``` work asynchronously (called with Thread) |
|
42 | 43 |
# so that it always run as intended (for every '''interval''' sec, send a photo) |
43 |
- # regardless of what buffer frames are now. |
|
44 |
+ # regardless of how you buffer frames as long as there are enough buffer. |
|
44 | 45 |
# They are triggered by ```start``` and halts by ```stop``` |
45 |
- def capture_frames(self): |
|
46 |
+ def receive_stream_packet(self): |
|
46 | 47 |
for packet in self.input_stream.demux(self.video_stream): |
47 | 48 |
for frame in packet.decode(): |
48 | 49 |
with self.frame_buffer_lock: |
... | ... | @@ -59,13 +60,13 @@ |
59 | 60 |
if len(self.frame_buffer) > self.buffer_size: |
60 | 61 |
self.frame_buffer = self.frame_buffer[-self.buffer_size:] |
61 | 62 |
buffered_frame = self.frame_buffer[-1] |
62 |
- print(len(self.frame_buffer)) |
|
63 |
+ # print(len(self.frame_buffer)) |
|
63 | 64 |
img = buffered_frame.to_image() |
64 | 65 |
img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) |
65 | 66 |
frame_name = f"captured_frame_{self.captured_frame_count}.jpg" |
66 | 67 |
img_binary = cv2.imencode('.png', img) |
67 | 68 |
self.send_image_to_server(img_binary, self.endpoint) |
68 |
- cv2.imwrite(f'hls_streaming/captured_frame_/{datetime.now()}_{frame_name}', img) |
|
69 |
+ # cv2.imwrite(f'hls_streaming/captured_frame_/{datetime.now()}_{frame_name}', img) |
|
69 | 70 |
self.last_capture_time = current_time |
70 | 71 |
print(f"Captured {frame_name} at time: {current_time - self.start_time:.2f}s") |
71 | 72 |
self.captured_frame_count +=1 |
... | ... | @@ -82,17 +83,18 @@ |
82 | 83 |
try: |
83 | 84 |
requests.post(endpoint, headers=header, files=image) |
84 | 85 |
except: |
85 |
- print("Can not connect to the analyzer server. Check the endpoint address or connection.") |
|
86 |
+ print("Can not connect to the analyzer server. Check the endpoint address or connection.\n" |
|
87 |
+ f"Can not connect to : {self.endpoint}") |
|
86 | 88 |
|
87 | 89 |
def start(self): |
88 |
- self.capture_thread = Thread(target=self.capture_frames) |
|
90 |
+ self.receive_stream_packet = Thread(target=self.receive_stream_packet) |
|
89 | 91 |
self.process_thread = Thread(target=self.process_frames) |
90 |
- self.capture_thread.start() |
|
92 |
+ self.receive_stream_packet.start() |
|
91 | 93 |
self.process_thread.start() |
92 | 94 |
|
93 | 95 |
def stop(self): |
94 | 96 |
self.stop_event.set() |
95 |
- self.capture_thread.join() |
|
97 |
+ self.receive_stream_packet.join() |
|
96 | 98 |
self.process_thread.join() |
97 | 99 |
self.input_stream.close() |
98 | 100 |
|
... | ... | @@ -102,7 +104,7 @@ |
102 | 104 |
capturer = FrameCapturer( |
103 | 105 |
'http://cctvsec.ktict.co.kr/73496/' |
104 | 106 |
'7xhDlyfDPK1AtaOUkAUDUJgZvfqvRXYYZUmRLxgPgKXk+eEtIJIfGkiC/gcQmysaz7zhDW2Jd8qhPCxgpo7cn5VqArnowyKjUePjdAmuQQ8=', |
105 |
- 101, 300 |
|
107 |
+ 101, 10 |
|
106 | 108 |
) |
107 | 109 |
t1 = time.time() |
108 | 110 |
try: |
... | ... | @@ -110,6 +112,7 @@ |
110 | 112 |
time.sleep(600000) |
111 | 113 |
finally: |
112 | 114 |
capturer.stop() |
115 |
+ del capturer |
|
113 | 116 |
t2 = time.time() |
114 | 117 |
with open("result.txt", "w") as file: |
115 | 118 |
file.write(f'{t2-t1} seconds before terminating') |
--- inferenece.py
... | ... | @@ -1,0 +0,0 @@ |
+++ yoloseg/config/classes.txt
... | ... | @@ -0,0 +1,1 @@ |
1 | +water_body(파일 끝에 줄바꿈 문자 없음) |
+++ yoloseg/img.png
Binary file is not shown |
+++ yoloseg/inference_.py
... | ... | @@ -0,0 +1,235 @@ |
1 | +import cv2 | |
2 | +import numpy as np | |
3 | +import random | |
4 | +from config import CLASS_NAME, CLASS_NUM | |
5 | + | |
6 | +class Inference: | |
7 | + def __init__(self, onnx_model_path, model_input_shape, classes_txt_file, run_with_cuda): | |
8 | + self.model_path = onnx_model_path | |
9 | + self.model_shape = model_input_shape | |
10 | + self.classes_path = classes_txt_file | |
11 | + self.cuda_enabled = run_with_cuda | |
12 | + self.letter_box_for_square = True | |
13 | + self.model_score_threshold = 0.3 | |
14 | + self.model_nms_threshold = 0.5 | |
15 | + self.classes = [] | |
16 | + | |
17 | + self.load_onnx_network() | |
18 | + self.load_classes_from_file() | |
19 | + | |
20 | + def sigmoid(self, x): | |
21 | + return 1 / (1 + np.exp(-x)) | |
22 | + | |
23 | + def inverse_sigmoid(self, x): | |
24 | + return np.log(x/(1-x)) | |
25 | + | |
26 | + def run_inference(self, input_image): | |
27 | + model_input = input_image | |
28 | + if self.letter_box_for_square and self.model_shape[0] == self.model_shape[1]: | |
29 | + model_input = self.format_to_square(model_input) | |
30 | + | |
31 | + blob = cv2.dnn.blobFromImage(model_input, 1.0 / 255.0, self.model_shape, (0, 0, 0), True, False) | |
32 | + self.net.setInput(blob) | |
33 | + | |
34 | + outputs = self.net.forward(self.net.getUnconnectedOutLayersNames()) | |
35 | + outputs_bbox = outputs[0] | |
36 | + outputs_mask = outputs[1] | |
37 | + | |
38 | + detections = self.process_detections(outputs_bbox, model_input) | |
39 | + mask_maps = self.process_mask_output(detections, outputs_mask, model_input.shape) | |
40 | + | |
41 | + return detections, mask_maps | |
42 | + | |
43 | + def process_detections(self, outputs_bbox, model_input): | |
44 | + # Assuming outputs_bbox is already in the (x, y, w, h, confidence, class_probs...) format | |
45 | + x_factor = model_input.shape[1] / self.model_shape[0] | |
46 | + y_factor = model_input.shape[0] / self.model_shape[1] | |
47 | + | |
48 | + class_ids = [] | |
49 | + confidences = [] | |
50 | + mask_coefficients = [] | |
51 | + boxes = [] | |
52 | + | |
53 | + for detection in outputs_bbox[0].T: | |
54 | + # when your weight is trained from pretrained weight, the resulting wieght | |
55 | + # may have leftover classes (that does nothing), hence this. | |
56 | + scores_classification = detection[4:4+CLASS_NUM] | |
57 | + scores_segmentation = detection[4+CLASS_NUM:] | |
58 | + class_id = np.argmax(scores_classification, axis=0) | |
59 | + confidence = scores_classification[class_id] | |
60 | + | |
61 | + thres = self.model_score_threshold | |
62 | + if confidence > thres: | |
63 | + x, y, w, h = detection[:4] | |
64 | + left = int((x - 0.5 * w) * x_factor) | |
65 | + top = int((y - 0.5 * h) * y_factor) | |
66 | + width = int(w * x_factor) | |
67 | + height = int(h * y_factor) | |
68 | + | |
69 | + boxes.append([left, top, width, height]) | |
70 | + confidences.append(float(confidence)) | |
71 | + mask_coefficients.append(scores_segmentation) | |
72 | + class_ids.append(class_id) | |
73 | + confidences = (confidences) | |
74 | + indices = cv2.dnn.NMSBoxes(boxes, confidences, self.model_score_threshold, self.model_nms_threshold) | |
75 | + | |
76 | + detections = [] | |
77 | + for i in indices: | |
78 | + idx = i | |
79 | + result = { | |
80 | + 'class_id': class_ids[i], | |
81 | + 'confidence': confidences[i], | |
82 | + 'mask_coefficients': np.array(mask_coefficients[i]), | |
83 | + 'box': boxes[idx], | |
84 | + 'class_name': self.classes[class_ids[i]], | |
85 | + 'color': (random.randint(100, 255), random.randint(100, 255), random.randint(100, 255)) | |
86 | + } | |
87 | + detections.append(result) | |
88 | + | |
89 | + return detections | |
90 | + | |
91 | + def process_mask_output(self, detections, proto_masks, image_shape): | |
92 | + if not detections: | |
93 | + return [] | |
94 | + | |
95 | + batch_size, num_protos, proto_height, proto_width = proto_masks.shape # Correct shape unpacking | |
96 | + full_masks = np.zeros((len(detections), image_shape[0], image_shape[1]), dtype=np.float32) | |
97 | + | |
98 | + for idx, det in enumerate(detections): | |
99 | + box = det['box'] | |
100 | + x1, y1, w, h = box | |
101 | + x1, y1, x2, y2 = x1, y1, x1 + w, y1 + h | |
102 | + | |
103 | + if y2 > image_shape[1]: | |
104 | + h = h + image_shape[1] - h - y1 | |
105 | + if x2 > image_shape[0]: | |
106 | + w = w + image_shape[1] - w - y1 | |
107 | + | |
108 | + # Get the corresponding mask coefficients for this detection | |
109 | + coeffs = det["mask_coefficients"] | |
110 | + | |
111 | + # Compute the linear combination of proto masks | |
112 | + # for now, plural batch operation is not supported, and this is the point where you should start. | |
113 | + # instead of proto_masks[0], do some iterative operation. | |
114 | + mask = np.tensordot(coeffs, proto_masks[0], axes=[0, 0]) # Dot product along the number of prototypes | |
115 | + | |
116 | + # Resize mask to the bounding box size, using sigmoid to normalize | |
117 | + resized_mask = cv2.resize(mask, (w, h)) | |
118 | + resized_mask = self.sigmoid(resized_mask) | |
119 | + | |
120 | + # Threshold to create a binary mask | |
121 | + final_mask = (resized_mask > 0.5).astype(np.uint8) | |
122 | + | |
123 | + # Place the mask in the corresponding location on a full-sized mask image | |
124 | + full_mask = np.zeros((image_shape[0], image_shape[1]), dtype=np.uint8) | |
125 | + print(final_mask.shape) | |
126 | + print(full_mask[y1:y2, x1:x2].shape) | |
127 | + full_mask[y1:y2, x1:x2] = final_mask | |
128 | + | |
129 | + # Combine the mask with the masks of other detections | |
130 | + full_masks[idx] = full_mask | |
131 | + | |
132 | + return full_masks | |
133 | + | |
134 | + def load_classes_from_file(self): | |
135 | + with open(self.classes_path, 'r') as f: | |
136 | + self.classes = f.read().strip().split('\n') | |
137 | + | |
138 | + def load_onnx_network(self): | |
139 | + self.net = cv2.dnn.readNetFromONNX(self.model_path) | |
140 | + if self.cuda_enabled: | |
141 | + print("\nRunning on CUDA") | |
142 | + self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA) | |
143 | + self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA) | |
144 | + else: | |
145 | + print("\nRunning on CPU") | |
146 | + self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV) | |
147 | + self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU) | |
148 | + | |
149 | + def format_to_square(self, source): | |
150 | + col, row = source.shape[1], source.shape[0] | |
151 | + max_side = max(col, row) | |
152 | + result = np.zeros((max_side, max_side, 3), dtype=np.uint8) | |
153 | + result[0:row, 0:col] = source | |
154 | + return result | |
155 | + | |
156 | +def overlay_mask(image, mask, color=(0, 255, 0), alpha=0.5): | |
157 | + """ | |
158 | + Overlays a mask onto an image using a specified color and transparency level. | |
159 | + | |
160 | + Parameters: | |
161 | + image (np.ndarray): The original image. | |
162 | + mask (np.ndarray): The mask to overlay. Must be the same size as the image. | |
163 | + color (tuple): The color for the mask overlay in BGR format (default is green). | |
164 | + alpha (float): Transparency factor for the mask; 0 is fully transparent, 1 is opaque. | |
165 | + | |
166 | + Returns: | |
167 | + np.ndarray: The image with the overlay. | |
168 | + """ | |
169 | + # Ensure the mask is a binary mask | |
170 | + mask = (mask > 0).astype(np.uint8) # Convert mask to binary if not already | |
171 | + | |
172 | + # Create an overlay with the same size as the image but only using the mask area | |
173 | + overlay = np.zeros_like(image, dtype=np.uint8) | |
174 | + overlay[mask == 1] = color | |
175 | + | |
176 | + # Blend the overlay with the image using the alpha factor | |
177 | + return cv2.addWeighted(src1=overlay, alpha=alpha, src2=image, beta=1 - alpha, gamma=0) | |
178 | + | |
179 | + | |
180 | + | |
181 | +def main(): | |
182 | + import time | |
183 | + | |
184 | + | |
185 | + # Path to your ONNX model and classes text file | |
186 | + model_path = 'yoloseg/weight/best.onnx' | |
187 | + classes_txt_file = 'yoloseg/config/classes.txt' | |
188 | + image_path = 'yoloseg/img3.jpg' | |
189 | + | |
190 | + model_input_shape = (640, 640) | |
191 | + inference_engine = Inference( | |
192 | + onnx_model_path=model_path, | |
193 | + model_input_shape=model_input_shape, | |
194 | + classes_txt_file=classes_txt_file, | |
195 | + run_with_cuda=True | |
196 | + ) | |
197 | + | |
198 | + # Load an image | |
199 | + img = cv2.imread(image_path) | |
200 | + if img is None: | |
201 | + print("Error loading image") | |
202 | + return | |
203 | + img = cv2.resize(img, model_input_shape) | |
204 | + # Run inference | |
205 | + t1 = time.time() | |
206 | + detections, mask_maps = inference_engine.run_inference(img) | |
207 | + t2 = time.time() | |
208 | + | |
209 | + print(t2-t1) | |
210 | + | |
211 | + # Display results | |
212 | + for detection in detections: | |
213 | + x, y, w, h = detection['box'] | |
214 | + class_name = detection['class_name'] | |
215 | + confidence = detection['confidence'] | |
216 | + cv2.rectangle(img, (x, y), (x+w, y+h), detection['color'], 2) | |
217 | + label = f"{class_name}: {confidence:.2f}" | |
218 | + cv2.putText(img, label, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, detection['color'], 2) | |
219 | + | |
220 | + # Show the image | |
221 | + # cv2.imshow('Detections', img) | |
222 | + # cv2.waitKey(0) | |
223 | + # cv2.destroyAllWindows() | |
224 | + | |
225 | + # If you also want to display segmentation maps, you would need additional handling here | |
226 | + # Example for displaying first mask if available: | |
227 | + if mask_maps is not None: | |
228 | + | |
229 | + seg_image = overlay_mask(img, mask_maps[0], color=(0, 255, 0), alpha=0.3) | |
230 | + cv2.imshow("segmentation", seg_image) | |
231 | + cv2.waitKey(0) | |
232 | + cv2.destroyAllWindows() | |
233 | + | |
234 | +if __name__ == "__main__": | |
235 | + main()(파일 끝에 줄바꿈 문자 없음) |
+++ yoloseg/utils.py
... | ... | @@ -0,0 +1,57 @@ |
1 | +import numpy as np | |
2 | +import cv2 | |
3 | + | |
4 | + | |
5 | +def sigmoid(x): | |
6 | + return 1 / (1 + np.exp(-x)) | |
7 | + | |
8 | + | |
9 | +def xywh2xyxy(x): | |
10 | + # x has shape [n, 4] where each row is (center_x, center_y, width, height) | |
11 | + y = np.zeros_like(x) | |
12 | + y[:, 0:2] = x[:, 0:2] - x[:, 2:4] / 2 # calculate min_x, min_y | |
13 | + y[:, 2:4] = x[:, 0:2] + x[:, 2:4] / 2 # calculate max_x, max_y | |
14 | + return y | |
15 | + | |
16 | +def iou(box, boxes): | |
17 | + # Compute xmin, ymin, xmax, ymax for both boxes | |
18 | + xmin = np.maximum(box[0], boxes[:, 0]) | |
19 | + ymin = np.maximum(box[1], boxes[:, 1]) | |
20 | + xmax = np.minimum(box[2], boxes[:, 2]) | |
21 | + ymax = np.minimum(box[3], boxes[:, 3]) | |
22 | + | |
23 | + # Compute intersection area | |
24 | + intersection_area = np.maximum(0, xmax - xmin) * np.maximum(0, ymax - ymin) | |
25 | + | |
26 | + # Compute union area | |
27 | + box_area = (box[2] - box[0]) * (box[3] - box[1]) | |
28 | + boxes_area = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1]) | |
29 | + union_area = box_area + boxes_area - intersection_area | |
30 | + | |
31 | + # Compute IoU | |
32 | + iou = intersection_area / union_area | |
33 | + | |
34 | + return iou | |
35 | + | |
36 | + | |
37 | +def fast_nms(boxes, scores, iou_threshold): | |
38 | + sorted_indices = np.argsort(scores)[::-1] | |
39 | + | |
40 | + selected_indices = [] | |
41 | + while sorted_indices.size > 0: | |
42 | + box_id = sorted_indices[0] | |
43 | + selected_indices.append(box_id) | |
44 | + | |
45 | + if sorted_indices.size == 1: | |
46 | + break | |
47 | + | |
48 | + remaining_boxes = boxes[sorted_indices[1:]] | |
49 | + current_box = boxes[box_id].reshape(1, -1) | |
50 | + | |
51 | + ious = np.array([iou(current_box[0], remaining_box) for remaining_box in remaining_boxes]) | |
52 | + keep_indices = np.where(ious < iou_threshold)[0] | |
53 | + | |
54 | + sorted_indices = sorted_indices[keep_indices + 1] | |
55 | + | |
56 | + return selected_indices | |
57 | + |
+++ yoloseg/weight/best.onnx
This file is too big to display. |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?