drkareemkamal commited on
Commit
7861a07
1 Parent(s): fb264ce

Upload 11 files

Browse files
Dockerfile ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ FROM nvcr.io/nvidia/tritonserver:23.02-py3
2
+
3
+ # Install dependencies
4
+ RUN pip install opencv-python && \
5
+ apt update && \
6
+ apt install -y libgl1 && \
7
+ rm -rf /var/lib/apt/lists/*
8
+
9
+ CMD ["tritonserver", "--model-repository=/models" ]
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
README.md CHANGED
@@ -1,14 +1,65 @@
1
- ---
2
- title: Helmet Detection
3
- emoji: 👀
4
- colorFrom: pink
5
- colorTo: indigo
6
- sdk: streamlit
7
- sdk_version: 1.39.0
8
- app_file: app.py
9
- pinned: false
10
- license: mit
11
- short_description: helmet detection using YOLOv8 and Nvidia Tritone inference s
12
- ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Overview
2
+ This repository provides an ensemble model to combine a YoloV8 model exported from the [Ultralytics](https://github.com/ultralytics/ultralytics) repository with NMS post-processing. The NMS post-processing code contained in [models/postprocess/1/model.py](models/postprocess/1/model.py) is adapted from the [Ultralytics ONNX Example](https://github.com/ultralytics/ultralytics/blob/4b866c97180842b546fe117610869d3c8d69d8ae/examples/YOLOv8-OpenCV-ONNX-Python/main.py).
3
+
4
+
5
+ For more information about Triton's Ensemble Models, see their documentation on [Architecture.md](https://github.com/triton-inference-server/server/blob/main/docs/user_guide/architecture.md) and some of their [preprocessing examples](https://github.com/triton-inference-server/python_backend/tree/main/examples/preprocessing).
6
+
7
+ # Directory Structure
8
+ ```
9
+ models/
10
+ yolov8_onnx/
11
+ 1/
12
+ model.onnx
13
+ config.pbtxt
14
+
15
+ postprocess/
16
+ 1/
17
+ model.py
18
+ config.pbtxt
19
+
20
+ yolov8_ensemble/
21
+ 1/
22
+ <Empty Directory>
23
+ config.pbtxt
24
+ README.md
25
+ main.py
26
+ ```
27
+
28
+
29
+ # Quick Start
30
+ 1. Install [Ultralytics](https://github.com/ultralytics/ultralytics) and TritonClient
31
+ ```
32
+ pip install ultralytics==8.0.51 tritonclient[all]==2.31.0
33
+ ```
34
+
35
+ 2. Export a model to ONNX format:
36
+ ```
37
+ yolo export model=yolov8n.pt format=onnx dynamic=True opset=16
38
+ ```
39
+
40
+ 3. Rename the model file to `model.onnx` and place it under the `/models/yolov8_onnx/1` directory (see directory structure above).
41
+
42
+ 4. (Optional): Update the Score and NMS threshold in [models/postprocess/1/model.py](models/postprocess/1/model.py#L59)
43
+
44
+ 5. (Optional): Update the [models/yolov8_ensemble/config.pbtxt](models/yolov8_ensemble/config.pbtxt) file if your input resolution has changed.
45
+
46
+ 6. Build the Docker Container for Triton Inference:
47
+ ```
48
+ DOCKER_NAME="yolov8-triton"
49
+ docker build -t $DOCKER_NAME .
50
+ ```
51
+
52
+ 6. Run Triton Inference Server:
53
+ ```
54
+ DOCKER_NAME="yolov8-triton"
55
+ docker run --gpus all \
56
+ -it --rm \
57
+ --net=host \
58
+ -v ./models:/models \
59
+ $DOCKER_NAME
60
+ ```
61
+
62
+ 7. Run the script with `python main.py`. The overlay image will be written to `output.jpg`.
63
+
64
+
65
+
main.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+ import tritonclient.grpc as grpcclient
4
+ import sys
5
+ import argparse
6
+
7
+ class_names =['Helmet',"No_helmet","person"]
8
+
9
+ def get_triton_client(url: str = 'localhost:8001'):
10
+ try:
11
+ keepalive_options = grpcclient.KeepAliveOptions(
12
+ keepalive_time_ms=2**31 - 1,
13
+ keepalive_timeout_ms=20000,
14
+ keepalive_permit_without_calls=False,
15
+ http2_max_pings_without_data=2
16
+ )
17
+ triton_client = grpcclient.InferenceServerClient(
18
+ url=url,
19
+ verbose=False,
20
+ keepalive_options=keepalive_options)
21
+ except Exception as e:
22
+ print("channel creation failed: " + str(e))
23
+ sys.exit()
24
+ return triton_client
25
+
26
+
27
+ def draw_bounding_box(img, class_id, confidence, x, y, x_plus_w, y_plus_h):
28
+ label = f'{class_names[class_id]}: {confidence:.2f}'
29
+ color = (255, 0, )
30
+ cv2.rectangle(img, (x, y), (x_plus_w, y_plus_h), color, 2)
31
+ cv2.putText(img, label, (x - 10, y - 10),
32
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
33
+
34
+
35
+ def read_image(image_path: str, expected_image_shape) -> np.ndarray:
36
+ expected_width = expected_image_shape[0]
37
+ expected_height = expected_image_shape[1]
38
+ expected_length = min((expected_height, expected_width))
39
+ original_image: np.ndarray = cv2.imread(image_path)
40
+ [height, width, _] = original_image.shape
41
+ length = max((height, width))
42
+ image = np.zeros((length, length, 3), np.uint8)
43
+ image[0:height, 0:width] = original_image
44
+ scale = length / expected_length
45
+
46
+ input_image = cv2.resize(image, (expected_width, expected_height))
47
+ input_image = (input_image / 255.0).astype(np.float32)
48
+
49
+ # Channel first
50
+ input_image = input_image.transpose(2, 0, 1)
51
+
52
+ # Expand dimensions
53
+ input_image = np.expand_dims(input_image, axis=0)
54
+ return original_image, input_image, scale
55
+
56
+
57
+ def run_inference(model_name: str, input_image: np.ndarray,
58
+ triton_client: grpcclient.InferenceServerClient):
59
+ inputs = []
60
+ outputs = []
61
+ inputs.append(grpcclient.InferInput('images', input_image.shape, "FP32"))
62
+ # Initialize the data
63
+ inputs[0].set_data_from_numpy(input_image)
64
+
65
+ outputs.append(grpcclient.InferRequestedOutput('num_detections'))
66
+ outputs.append(grpcclient.InferRequestedOutput('detection_boxes'))
67
+ outputs.append(grpcclient.InferRequestedOutput('detection_scores'))
68
+ outputs.append(grpcclient.InferRequestedOutput('detection_classes'))
69
+
70
+ # Test with outputs
71
+ results = triton_client.infer(model_name=model_name,
72
+ inputs=inputs,
73
+ outputs=outputs)
74
+
75
+ num_detections = results.as_numpy('num_detections')
76
+ detection_boxes = results.as_numpy('detection_boxes')
77
+ detection_scores = results.as_numpy('detection_scores')
78
+ detection_classes = results.as_numpy('detection_classes')
79
+ return num_detections, detection_boxes, detection_scores, detection_classes
80
+
81
+
82
+ def main(image_path, model_name, url):
83
+ triton_client = get_triton_client(url)
84
+ expected_image_shape = triton_client.get_model_metadata(model_name).inputs[0].shape[-2:]
85
+ original_image, input_image, scale = read_image(image_path, expected_image_shape)
86
+ num_detections, detection_boxes, detection_scores, detection_classes = run_inference(
87
+ model_name, input_image, triton_client)
88
+ print(detection_classes)
89
+ print(detection_boxes)
90
+ for index in range(num_detections[0]):
91
+ box = detection_boxes[index]
92
+
93
+ draw_bounding_box(original_image,
94
+ detection_classes[index],
95
+ detection_scores[index],
96
+ round(box[0] * scale),
97
+ round(box[1] * scale),
98
+ round((box[0] + box[2]) * scale),
99
+ round((box[1] + box[3]) * scale))
100
+
101
+ cv2.imwrite('output.jpg', original_image)
102
+
103
+
104
+ if __name__ == '__main__':
105
+ parser = argparse.ArgumentParser()
106
+ parser.add_argument('--image_path', type=str, default='./assets/Image (47).png')
107
+ parser.add_argument('--model_name', type=str, default='yolov8_ensemble')
108
+ parser.add_argument('--url', type=str, default='172.17.0.1:8001')
109
+ args = parser.parse_args()
110
+ main(args.image_path, args.model_name, args.url)
models/postprocess/1/__pycache__/model.cpython-38.pyc ADDED
Binary file (2.98 kB). View file
 
models/postprocess/1/model (Copy).py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import json
3
+ import triton_python_backend_utils as pb_utils
4
+ import cv2
5
+
6
+
7
+ class TritonPythonModel:
8
+ """Your Python model must use the same class name. Every Python model
9
+ that is created must have "TritonPythonModel" as the class name.
10
+ """
11
+
12
+ def initialize(self, args):
13
+ """`initialize` is called only once when the model is being loaded.
14
+ Implementing `initialize` function is optional. This function allows
15
+ the model to intialize any state associated with this model.
16
+ Parameters
17
+ ----------
18
+ args : dict
19
+ Both keys and values are strings. The dictionary keys and values are:
20
+ * model_config: A JSON string containing the model configuration
21
+ * model_instance_kind: A string containing model instance kind
22
+ * model_instance_device_id: A string containing model instance device ID
23
+ * model_repository: Model repository path
24
+ * model_version: Model version
25
+ * model_name: Model name
26
+ """
27
+
28
+ # You must parse model_config. JSON string is not parsed here
29
+ self.model_config = model_config = json.loads(args['model_config'])
30
+
31
+ # Get OUTPUT0 configuration
32
+ num_detections_config = pb_utils.get_output_config_by_name(
33
+ model_config, "num_detections")
34
+ detection_boxes_config = pb_utils.get_output_config_by_name(
35
+ model_config, "detection_boxes")
36
+
37
+ detection_scores_config = pb_utils.get_output_config_by_name(
38
+ model_config, "detection_scores")
39
+
40
+ detection_classes_config = pb_utils.get_output_config_by_name(
41
+ model_config, "detection_classes")
42
+
43
+ # Convert Triton types to numpy types
44
+ self.num_detections_dtype = pb_utils.triton_string_to_numpy(
45
+ num_detections_config['data_type'])
46
+
47
+ # Convert Triton types to numpy types
48
+ self.detection_boxes_dtype = pb_utils.triton_string_to_numpy(
49
+ detection_boxes_config['data_type'])
50
+
51
+ # Convert Triton types to numpy types
52
+ self.detection_scores_dtype = pb_utils.triton_string_to_numpy(
53
+ detection_scores_config['data_type'])
54
+
55
+ # Convert Triton types to numpy types
56
+ self.detection_classes_dtype = pb_utils.triton_string_to_numpy(
57
+ detection_classes_config['data_type'])
58
+
59
+ self.score_threshold = 0.25
60
+ self.nms_threshold = 0.45
61
+
62
+ def execute(self, requests):
63
+ """`execute` MUST be implemented in every Python model. `execute`
64
+ function receives a list of pb_utils.InferenceRequest as the only
65
+ argument. This function is called when an inference request is made
66
+ for this model. Depending on the batching configuration (e.g. Dynamic
67
+ Batching) used, `requests` may contain multiple requests. Every
68
+ Python model, must create one pb_utils.InferenceResponse for every
69
+ pb_utils.InferenceRequest in `requests`. If there is an error, you can
70
+ set the error argument when creating a pb_utils.InferenceResponse
71
+ Parameters
72
+ ----------
73
+ requests : list
74
+ A list of pb_utils.InferenceRequest
75
+ Returns
76
+ -------
77
+ list
78
+ A list of pb_utils.InferenceResponse. The length of this list must
79
+ be the same as `requests`
80
+ """
81
+
82
+ num_detections_dtype = self.num_detections_dtype
83
+ detection_boxes_dtype = self.detection_boxes_dtype
84
+ detection_scores_dtype = self.detection_scores_dtype
85
+ detection_classes_dtype = self.detection_classes_dtype
86
+
87
+ responses = []
88
+
89
+ # Every Python backend must iterate over everyone of the requests
90
+ # and create a pb_utils.InferenceResponse for each of them.
91
+ for request in requests:
92
+ # Get INPUT0
93
+ in_0 = pb_utils.get_input_tensor_by_name(request, "INPUT_0")
94
+
95
+ # Get the output arrays from the results
96
+ outputs = in_0.as_numpy()
97
+
98
+ outputs = np.array([cv2.transpose(outputs[0])])
99
+ rows = outputs.shape[1]
100
+
101
+ boxes = []
102
+ scores = []
103
+ class_ids = []
104
+ for i in range(rows):
105
+ classes_scores = outputs[0][i][4:]
106
+ (minScore, maxScore, minClassLoc, (x, maxClassIndex)
107
+ ) = cv2.minMaxLoc(classes_scores)
108
+ if maxScore >= self.score_threshold:
109
+ box = [outputs[0][i][0] -
110
+ (0.5 *
111
+ outputs[0][i][2]), outputs[0][i][1] -
112
+ (0.5 *
113
+ outputs[0][i][3]), outputs[0][i][2], outputs[0][i][3]]
114
+ boxes.append(box)
115
+ scores.append(maxScore)
116
+ class_ids.append(maxClassIndex)
117
+
118
+ result_boxes = cv2.dnn.NMSBoxes(boxes, scores,
119
+ self.score_threshold,
120
+ self.nms_threshold,
121
+ 0.5)
122
+
123
+ num_detections = 0
124
+ output_boxes = []
125
+ output_scores = []
126
+ output_classids = []
127
+ for i in range(len(result_boxes)):
128
+ index = result_boxes[i]
129
+ box = boxes[index]
130
+ detection = {
131
+ 'class_id': class_ids[index],
132
+ 'confidence': scores[index],
133
+ 'box': box}
134
+ output_boxes.append(box)
135
+ output_scores.append(scores[index])
136
+ output_classids.append(class_ids[index])
137
+
138
+ num_detections += 1
139
+
140
+ num_detections = np.array(num_detections)
141
+ num_detections = pb_utils.Tensor(
142
+ "num_detections", num_detections.astype(num_detections_dtype))
143
+
144
+ detection_boxes = np.array(output_boxes)
145
+ detection_boxes = pb_utils.Tensor(
146
+ "detection_boxes", detection_boxes.astype(detection_boxes_dtype))
147
+
148
+ detection_scores = np.array(output_scores)
149
+ detection_scores = pb_utils.Tensor(
150
+ "detection_scores", detection_scores.astype(detection_scores_dtype))
151
+ detection_classes = np.array(output_classids)
152
+ detection_classes = pb_utils.Tensor(
153
+ "detection_classes",
154
+ detection_classes.astype(detection_classes_dtype))
155
+
156
+ inference_response = pb_utils.InferenceResponse(
157
+ output_tensors=[
158
+ num_detections,
159
+ detection_boxes,
160
+ detection_scores,
161
+ detection_classes])
162
+ responses.append(inference_response)
163
+
164
+ return responses
165
+
166
+ def finalize(self):
167
+ """`finalize` is called only once when the model is being unloaded.
168
+ Implementing `finalize` function is OPTIONAL. This function allows
169
+ the model to perform any necessary clean ups before exit.
170
+ """
171
+ pass
models/postprocess/1/model.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import json
3
+ import triton_python_backend_utils as pb_utils
4
+ import cv2
5
+
6
+
7
+ class TritonPythonModel:
8
+ """Your Python model must use the same class name. Every Python model
9
+ that is created must have "TritonPythonModel" as the class name.
10
+ """
11
+
12
+ def initialize(self, args):
13
+ """`initialize` is called only once when the model is being loaded."""
14
+ self.model_config = model_config = json.loads(args['model_config'])
15
+
16
+ # Get OUTPUT0 configuration for Triton output layers
17
+ num_detections_config = pb_utils.get_output_config_by_name(
18
+ model_config, "num_detections")
19
+ detection_boxes_config = pb_utils.get_output_config_by_name(
20
+ model_config, "detection_boxes")
21
+ detection_scores_config = pb_utils.get_output_config_by_name(
22
+ model_config, "detection_scores")
23
+ detection_classes_config = pb_utils.get_output_config_by_name(
24
+ model_config, "detection_classes")
25
+
26
+ # Convert Triton types to numpy types
27
+ self.num_detections_dtype = pb_utils.triton_string_to_numpy(
28
+ num_detections_config['data_type'])
29
+ self.detection_boxes_dtype = pb_utils.triton_string_to_numpy(
30
+ detection_boxes_config['data_type'])
31
+ self.detection_scores_dtype = pb_utils.triton_string_to_numpy(
32
+ detection_scores_config['data_type'])
33
+ self.detection_classes_dtype = pb_utils.triton_string_to_numpy(
34
+ detection_classes_config['data_type'])
35
+
36
+ # Thresholds for detection filtering
37
+ self.score_threshold = 0.25 # Confidence threshold
38
+ self.nms_threshold = 0.45 # NMS threshold to suppress overlaps
39
+
40
+ def execute(self, requests):
41
+ """The function is executed when inference requests are made."""
42
+
43
+ num_detections_dtype = self.num_detections_dtype
44
+ detection_boxes_dtype = self.detection_boxes_dtype
45
+ detection_scores_dtype = self.detection_scores_dtype
46
+ detection_classes_dtype = self.detection_classes_dtype
47
+
48
+ responses = []
49
+
50
+ # Process each inference request
51
+ for request in requests:
52
+ # Get INPUT0 - input tensor for the model
53
+ in_0 = pb_utils.get_input_tensor_by_name(request, "INPUT_0")
54
+
55
+ # Get the output arrays from the results (assuming batch size of 1)
56
+ outputs = in_0.as_numpy()
57
+ outputs = np.array([cv2.transpose(outputs[0])]) # Transpose to match expected format
58
+ rows = outputs.shape[1]
59
+
60
+ boxes = []
61
+ scores = []
62
+ class_ids = []
63
+
64
+ # Iterate over each detection
65
+ for i in range(rows):
66
+ # Extract class scores and determine the best class and its score
67
+ classes_scores = outputs[0][i][4:]
68
+ (minScore, maxScore, minClassLoc, (x, maxClassIndex)
69
+ ) = cv2.minMaxLoc(classes_scores)
70
+
71
+ if maxScore >= self.score_threshold: # Filter out low confidence predictions
72
+ # YOLO format: (x_center, y_center, width, height)
73
+ box = [
74
+ outputs[0][i][0] - (0.5 * outputs[0][i][2]), # x_min
75
+ outputs[0][i][1] - (0.5 * outputs[0][i][3]), # y_min
76
+ outputs[0][i][2], # width
77
+ outputs[0][i][3] # height
78
+ ]
79
+ boxes.append(box)
80
+ scores.append(maxScore)
81
+ class_ids.append(maxClassIndex) # Store the predicted class ID
82
+
83
+ # Apply Non-Maximum Suppression (NMS) to remove redundant boxes
84
+ result_boxes = cv2.dnn.NMSBoxes(boxes, scores, self.score_threshold, self.nms_threshold)
85
+
86
+ num_detections = 0
87
+ output_boxes = []
88
+ output_scores = []
89
+ output_classids = []
90
+
91
+ # Process the final set of boxes after NMS
92
+ for i in range(len(result_boxes)):
93
+ index = result_boxes[i]
94
+ box = boxes[index]
95
+ detection = {
96
+ 'class_id': class_ids[index], # Store as integer
97
+ 'confidence': scores[index], # Confidence score
98
+ 'box': box # Bounding box
99
+ }
100
+ output_boxes.append(box)
101
+ output_scores.append(scores[index])
102
+ output_classids.append(class_ids[index])
103
+
104
+ num_detections += 1
105
+
106
+ # Create output tensors for Triton
107
+ num_detections = np.array([num_detections], dtype=num_detections_dtype)
108
+ detection_boxes = np.array(output_boxes, dtype=detection_boxes_dtype)
109
+ detection_scores = np.array(output_scores, dtype=detection_scores_dtype)
110
+ detection_classes = np.array(output_classids, dtype=detection_classes_dtype)
111
+
112
+ # Create the inference response
113
+ inference_response = pb_utils.InferenceResponse(
114
+ output_tensors=[
115
+ pb_utils.Tensor("num_detections", num_detections),
116
+ pb_utils.Tensor("detection_boxes", detection_boxes),
117
+ pb_utils.Tensor("detection_scores", detection_scores),
118
+ pb_utils.Tensor("detection_classes", detection_classes)
119
+ ]
120
+ )
121
+ responses.append(inference_response)
122
+
123
+ return responses
124
+
125
+ def finalize(self):
126
+ """Clean-up function when the model is unloaded."""
127
+ pass
128
+
models/postprocess/config.pbtxt ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "postprocess"
2
+ backend: "python"
3
+ max_batch_size: 0
4
+ input [
5
+ {
6
+ name: "INPUT_0"
7
+ data_type: TYPE_FP32
8
+ dims: [-1, -1, -1]
9
+ }
10
+ ]
11
+
12
+ output [
13
+ {
14
+ name: "num_detections"
15
+ data_type: TYPE_INT32
16
+ dims: [1 ]
17
+ },
18
+ {
19
+ name: "detection_boxes"
20
+ data_type: TYPE_FP32
21
+ dims: [1000,4 ]
22
+ },
23
+ {
24
+ name: "detection_scores"
25
+ data_type: TYPE_FP32
26
+ dims: [1000]
27
+ },
28
+ {
29
+ name: "detection_classes"
30
+ data_type: TYPE_INT32
31
+ dims: [1000 ]
32
+ }
33
+ ]
34
+
35
+ instance_group [{ kind: KIND_CPU }]
models/yolov8_ensemble/config.pbtxt ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: "yolov8_ensemble"
2
+ platform: "ensemble"
3
+ max_batch_size: 0
4
+ input [
5
+ {
6
+ name: "images"
7
+ data_type: TYPE_FP32
8
+ dims: [ 1,3,640,640 ]
9
+ }
10
+ ]
11
+ output [
12
+ {
13
+ name: "num_detections"
14
+ data_type: TYPE_INT32
15
+ dims: [1 ]
16
+ },
17
+ {
18
+ name: "detection_boxes"
19
+ data_type: TYPE_FP32
20
+ dims: [1000,4 ]
21
+ },
22
+ {
23
+ name: "detection_scores"
24
+ data_type: TYPE_FP32
25
+ dims: [1000]
26
+ },
27
+ {
28
+ name: "detection_classes"
29
+ data_type: TYPE_INT32
30
+ dims: [1000 ]
31
+ }
32
+ ]
33
+ ensemble_scheduling {
34
+ step [
35
+ {
36
+ model_name: "yolov8_onnx"
37
+ model_version: -1
38
+ input_map {
39
+ key: "images"
40
+ value: "images"
41
+ }
42
+ output_map {
43
+ key: "output0"
44
+ value: "output0"
45
+ }
46
+ },
47
+ {
48
+ model_name: "postprocess"
49
+ model_version: -1
50
+ input_map {
51
+ key: "INPUT_0"
52
+ value: "output0"
53
+ }
54
+ output_map {
55
+ key: "num_detections"
56
+ value: "num_detections"
57
+ },
58
+ output_map {
59
+ key: "detection_boxes"
60
+ value: "detection_boxes"
61
+ },
62
+ output_map {
63
+ key: "detection_scores"
64
+ value: "detection_scores"
65
+ },
66
+ output_map {
67
+ key: "detection_classes"
68
+ value: "detection_classes"
69
+ }
70
+ }
71
+ ]
72
+ }
models/yolov8_onnx/1/model.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4d145b55c3390438e06bd6510b8eab37b80c508e31a3c4b2783b529a9761a368
3
+ size 174679472
models/yolov8_onnx/config.pbtxt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ platform: "onnxruntime_onnx"
2
+ max_batch_size: 8
3
+ input [
4
+ {
5
+ name: "images"
6
+ data_type: TYPE_FP32
7
+ dims: [ 3,640,640 ]
8
+ }
9
+ ]
10
+ output [
11
+ {
12
+ name: "output0"
13
+ data_type: TYPE_FP32
14
+ dims: [ -1, -1]
15
+ }
16
+ ]