mingyang91 commited on
Commit
684e6f5
·
verified ·
1 Parent(s): 0821896

add crack split.py

Browse files
.idea/workspace.xml CHANGED
@@ -4,10 +4,12 @@
4
  <option name="autoReloadType" value="SELECTIVE" />
5
  </component>
6
  <component name="ChangeListManager">
7
- <list default="true" id="d7806539-b6d6-42e7-bb45-1565f5d54891" name="Changes" comment="fix runtime error in coco evaluator">
 
8
  <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
9
  <change beforePath="$PROJECT_DIR$/demo.py" beforeDir="false" afterPath="$PROJECT_DIR$/demo.py" afterDir="false" />
10
- <change beforePath="$PROJECT_DIR$/yolo_dataset.py" beforeDir="false" afterPath="$PROJECT_DIR$/yolo_dataset.py" afterDir="false" />
 
11
  </list>
12
  <option name="SHOW_DIALOG" value="false" />
13
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -56,37 +58,41 @@
56
  <option name="hideEmptyMiddlePackages" value="true" />
57
  <option name="showLibraryContents" value="true" />
58
  </component>
59
- <component name="PropertiesComponent">{
60
- &quot;keyToString&quot;: {
61
- &quot;Python.demo.executor&quot;: &quot;Debug&quot;,
62
- &quot;Python.evaluator.executor&quot;: &quot;Debug&quot;,
63
- &quot;Python.extract.executor&quot;: &quot;Run&quot;,
64
- &quot;Python.streamlit.executor&quot;: &quot;Debug&quot;,
65
- &quot;Python.yolo_dataset.executor&quot;: &quot;Run&quot;,
66
- &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
67
- &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
68
- &quot;git-widget-placeholder&quot;: &quot;main&quot;,
69
- &quot;last_opened_file_path&quot;: &quot;/Users/famer.me/PycharmProjects/detector/tests&quot;,
70
- &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
71
- &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
72
- &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
73
- &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
74
- &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
75
- &quot;settings.editor.selected.configurable&quot;: &quot;settings.qodana&quot;,
76
- &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
 
77
  }
78
- }</component>
79
  <component name="RecentsManager">
80
  <key name="CopyFile.RECENT_KEYS">
 
 
81
  <recent name="$PROJECT_DIR$/tests" />
82
  <recent name="$PROJECT_DIR$" />
83
  </key>
84
  <key name="MoveFile.RECENT_KEYS">
 
85
  <recent name="$PROJECT_DIR$/tests" />
86
  <recent name="$PROJECT_DIR$" />
87
  </key>
88
  </component>
89
- <component name="RunManager" selected="Python.streamlit">
90
  <configuration name="demo" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
91
  <module name="detector" />
92
  <option name="ENV_FILES" value="" />
@@ -157,6 +163,29 @@
157
  <option name="INPUT_FILE" value="" />
158
  <method v="2" />
159
  </configuration>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  <configuration name="yolo_dataset" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
161
  <module name="detector" />
162
  <option name="ENV_FILES" value="" />
@@ -199,12 +228,14 @@
199
  <list>
200
  <item itemvalue="FastAPI.detector" />
201
  <item itemvalue="Python.streamlit" />
 
202
  <item itemvalue="Python.demo" />
203
  <item itemvalue="Python.evaluator" />
204
  <item itemvalue="Python.yolo_dataset" />
205
  </list>
206
  <recent_temporary>
207
  <list>
 
208
  <item itemvalue="Python.demo" />
209
  <item itemvalue="Python.evaluator" />
210
  <item itemvalue="Python.yolo_dataset" />
@@ -239,7 +270,8 @@
239
  <workItem from="1706274709532" duration="27000" />
240
  <workItem from="1706445222599" duration="1814000" />
241
  <workItem from="1706449264770" duration="4974000" />
242
- <workItem from="1706517295469" duration="1726000" />
 
243
  </task>
244
  <task id="LOCAL-00001" summary="init commit">
245
  <option name="closed" value="true" />
@@ -473,7 +505,23 @@
473
  <option name="project" value="LOCAL" />
474
  <updated>1706517885858</updated>
475
  </task>
476
- <option name="localTasksCounter" value="30" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  <servers />
478
  </component>
479
  <component name="TypeScriptGeneratedFilesManager">
@@ -518,7 +566,9 @@
518
  <MESSAGE value="IoU evaluator" />
519
  <MESSAGE value="Update UI" />
520
  <MESSAGE value="fix runtime error in coco evaluator" />
521
- <option name="LAST_COMMIT_MESSAGE" value="fix runtime error in coco evaluator" />
 
 
522
  </component>
523
  <component name="XDebuggerManager">
524
  <breakpoint-manager>
@@ -545,7 +595,8 @@
545
  <SUITE FILE_PATH="coverage/detector$yolo_dataset.coverage" NAME="yolo_dataset Coverage Results" MODIFIED="1705852113469" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
546
  <SUITE FILE_PATH="coverage/detector$evaluator.coverage" NAME="evaluator Coverage Results" MODIFIED="1706107083258" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
547
  <SUITE FILE_PATH="coverage/detector$demo.coverage" NAME="demo Coverage Results" MODIFIED="1706108414052" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
548
- <SUITE FILE_PATH="coverage/detector$streamlit.coverage" NAME="streamlit Coverage Results" MODIFIED="1706518941359" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
 
549
  <SUITE FILE_PATH="coverage/detector$extract.coverage" NAME="yolo_dataset Coverage Results" MODIFIED="1705764465837" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
550
  </component>
551
  </project>
 
4
  <option name="autoReloadType" value="SELECTIVE" />
5
  </component>
6
  <component name="ChangeListManager">
7
+ <list default="true" id="d7806539-b6d6-42e7-bb45-1565f5d54891" name="Changes" comment="add crack split.py">
8
+ <change afterPath="$PROJECT_DIR$/models/tools/draw.py" afterDir="false" />
9
  <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
10
  <change beforePath="$PROJECT_DIR$/demo.py" beforeDir="false" afterPath="$PROJECT_DIR$/demo.py" afterDir="false" />
11
+ <change beforePath="$PROJECT_DIR$/models/yolo_crack.py" beforeDir="false" afterPath="$PROJECT_DIR$/models/yolo_crack.py" afterDir="false" />
12
+ <change beforePath="$PROJECT_DIR$/yolo_model.py" beforeDir="false" afterPath="$PROJECT_DIR$/yolo_model.py" afterDir="false" />
13
  </list>
14
  <option name="SHOW_DIALOG" value="false" />
15
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
 
58
  <option name="hideEmptyMiddlePackages" value="true" />
59
  <option name="showLibraryContents" value="true" />
60
  </component>
61
+ <component name="PropertiesComponent"><![CDATA[{
62
+ "keyToString": {
63
+ "Python.demo.executor": "Debug",
64
+ "Python.evaluator.executor": "Debug",
65
+ "Python.extract.executor": "Run",
66
+ "Python.streamlit.executor": "Run",
67
+ "Python.yolo_crack.executor": "Run",
68
+ "Python.yolo_dataset.executor": "Run",
69
+ "RunOnceActivity.OpenProjectViewOnStart": "true",
70
+ "RunOnceActivity.ShowReadmeOnStart": "true",
71
+ "git-widget-placeholder": "main",
72
+ "last_opened_file_path": "/Users/famer.me/PycharmProjects/detector/tools",
73
+ "node.js.detected.package.eslint": "true",
74
+ "node.js.detected.package.tslint": "true",
75
+ "node.js.selected.package.eslint": "(autodetect)",
76
+ "node.js.selected.package.tslint": "(autodetect)",
77
+ "nodejs_package_manager_path": "npm",
78
+ "settings.editor.selected.configurable": "settings.qodana",
79
+ "vue.rearranger.settings.migration": "true"
80
  }
81
+ }]]></component>
82
  <component name="RecentsManager">
83
  <key name="CopyFile.RECENT_KEYS">
84
+ <recent name="$PROJECT_DIR$/tools" />
85
+ <recent name="$PROJECT_DIR$/datasets" />
86
  <recent name="$PROJECT_DIR$/tests" />
87
  <recent name="$PROJECT_DIR$" />
88
  </key>
89
  <key name="MoveFile.RECENT_KEYS">
90
+ <recent name="$PROJECT_DIR$/models" />
91
  <recent name="$PROJECT_DIR$/tests" />
92
  <recent name="$PROJECT_DIR$" />
93
  </key>
94
  </component>
95
+ <component name="RunManager" selected="Python.yolo_crack">
96
  <configuration name="demo" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
97
  <module name="detector" />
98
  <option name="ENV_FILES" value="" />
 
163
  <option name="INPUT_FILE" value="" />
164
  <method v="2" />
165
  </configuration>
166
+ <configuration name="yolo_crack" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
167
+ <module name="detector" />
168
+ <option name="ENV_FILES" value="" />
169
+ <option name="INTERPRETER_OPTIONS" value="" />
170
+ <option name="PARENT_ENVS" value="true" />
171
+ <envs>
172
+ <env name="PYTHONUNBUFFERED" value="1" />
173
+ </envs>
174
+ <option name="SDK_HOME" value="" />
175
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
176
+ <option name="IS_MODULE_SDK" value="true" />
177
+ <option name="ADD_CONTENT_ROOTS" value="true" />
178
+ <option name="ADD_SOURCE_ROOTS" value="true" />
179
+ <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
180
+ <option name="SCRIPT_NAME" value="$PROJECT_DIR$/models/yolo_crack.py" />
181
+ <option name="PARAMETERS" value="" />
182
+ <option name="SHOW_COMMAND_LINE" value="false" />
183
+ <option name="EMULATE_TERMINAL" value="false" />
184
+ <option name="MODULE_MODE" value="false" />
185
+ <option name="REDIRECT_INPUT" value="false" />
186
+ <option name="INPUT_FILE" value="" />
187
+ <method v="2" />
188
+ </configuration>
189
  <configuration name="yolo_dataset" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
190
  <module name="detector" />
191
  <option name="ENV_FILES" value="" />
 
228
  <list>
229
  <item itemvalue="FastAPI.detector" />
230
  <item itemvalue="Python.streamlit" />
231
+ <item itemvalue="Python.yolo_crack" />
232
  <item itemvalue="Python.demo" />
233
  <item itemvalue="Python.evaluator" />
234
  <item itemvalue="Python.yolo_dataset" />
235
  </list>
236
  <recent_temporary>
237
  <list>
238
+ <item itemvalue="Python.yolo_crack" />
239
  <item itemvalue="Python.demo" />
240
  <item itemvalue="Python.evaluator" />
241
  <item itemvalue="Python.yolo_dataset" />
 
270
  <workItem from="1706274709532" duration="27000" />
271
  <workItem from="1706445222599" duration="1814000" />
272
  <workItem from="1706449264770" duration="4974000" />
273
+ <workItem from="1706517295469" duration="2595000" />
274
+ <workItem from="1706855634459" duration="14109000" />
275
  </task>
276
  <task id="LOCAL-00001" summary="init commit">
277
  <option name="closed" value="true" />
 
505
  <option name="project" value="LOCAL" />
506
  <updated>1706517885858</updated>
507
  </task>
508
+ <task id="LOCAL-00030" summary="filter out macos cache files">
509
+ <option name="closed" value="true" />
510
+ <created>1706519049182</created>
511
+ <option name="number" value="00030" />
512
+ <option name="presentableId" value="LOCAL-00030" />
513
+ <option name="project" value="LOCAL" />
514
+ <updated>1706519049183</updated>
515
+ </task>
516
+ <task id="LOCAL-00031" summary="add crack split.py">
517
+ <option name="closed" value="true" />
518
+ <created>1706866629031</created>
519
+ <option name="number" value="00031" />
520
+ <option name="presentableId" value="LOCAL-00031" />
521
+ <option name="project" value="LOCAL" />
522
+ <updated>1706866629031</updated>
523
+ </task>
524
+ <option name="localTasksCounter" value="32" />
525
  <servers />
526
  </component>
527
  <component name="TypeScriptGeneratedFilesManager">
 
566
  <MESSAGE value="IoU evaluator" />
567
  <MESSAGE value="Update UI" />
568
  <MESSAGE value="fix runtime error in coco evaluator" />
569
+ <MESSAGE value="filter out macos cache files" />
570
+ <MESSAGE value="add crack split.py" />
571
+ <option name="LAST_COMMIT_MESSAGE" value="add crack split.py" />
572
  </component>
573
  <component name="XDebuggerManager">
574
  <breakpoint-manager>
 
595
  <SUITE FILE_PATH="coverage/detector$yolo_dataset.coverage" NAME="yolo_dataset Coverage Results" MODIFIED="1705852113469" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
596
  <SUITE FILE_PATH="coverage/detector$evaluator.coverage" NAME="evaluator Coverage Results" MODIFIED="1706107083258" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
597
  <SUITE FILE_PATH="coverage/detector$demo.coverage" NAME="demo Coverage Results" MODIFIED="1706108414052" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
598
+ <SUITE FILE_PATH="coverage/detector$streamlit.coverage" NAME="streamlit Coverage Results" MODIFIED="1706949799318" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
599
+ <SUITE FILE_PATH="coverage/detector$yolo_crack.coverage" NAME="yolo_crack Coverage Results" MODIFIED="1706950034213" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
600
  <SUITE FILE_PATH="coverage/detector$extract.coverage" NAME="yolo_dataset Coverage Results" MODIFIED="1705764465837" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="true" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
601
  </component>
602
  </project>
demo.py CHANGED
@@ -4,6 +4,7 @@ import time
4
  from functools import wraps
5
  from io import StringIO
6
  from zipfile import ZipFile
 
7
 
8
  import streamlit as st
9
  from PIL import Image
@@ -11,9 +12,10 @@ from PIL import Image
11
  import evaluator
12
  from yolo_dataset import YoloDataset
13
  from yolo_model import YoloModel
 
14
 
15
  fire_and_smoke = YoloModel("SHOU-ISD/fire-and-smoke", "yolov8n.pt")
16
- crack = YoloModel("SHOU-ISD/yolo-cracks", "best.pt")
17
  coco = YoloModel("ultralyticsplus/yolov8s", "yolov8s.pt")
18
 
19
 
@@ -71,15 +73,15 @@ def detect(model: YoloModel):
71
 
72
  if buffer:
73
  # Object Detecting
74
- with st.spinner('Wait for it...'):
75
  # Slider for changing confidence
76
  confidence = st.slider('Confidence Threshold', 0, 100, 30)
77
 
78
  # Calculating time for detection
79
  t1 = time.time()
80
- im = Image.open(buffer)
81
- # im.save("saved_images/image.jpg")
82
- res_img = model.preview_detect(im, confidence / 100.0)
83
  t2 = time.time()
84
 
85
  # Displaying the image
 
4
  from functools import wraps
5
  from io import StringIO
6
  from zipfile import ZipFile
7
+ from tempfile import mktemp
8
 
9
  import streamlit as st
10
  from PIL import Image
 
12
  import evaluator
13
  from yolo_dataset import YoloDataset
14
  from yolo_model import YoloModel
15
+ from models.yolo_crack import YoloModel as CrackModel
16
 
17
  fire_and_smoke = YoloModel("SHOU-ISD/fire-and-smoke", "yolov8n.pt")
18
+ crack = CrackModel("SHOU-ISD/yolo-cracks", "last4.pt", "SHOU-ISD/yolo-cracks", "best.pt")
19
  coco = YoloModel("ultralyticsplus/yolov8s", "yolov8s.pt")
20
 
21
 
 
73
 
74
  if buffer:
75
  # Object Detecting
76
+ with (st.spinner('Wait for it...')):
77
  # Slider for changing confidence
78
  confidence = st.slider('Confidence Threshold', 0, 100, 30)
79
 
80
  # Calculating time for detection
81
  t1 = time.time()
82
+ filename = mktemp(suffix=buffer.name)
83
+ Image.open(buffer).save(filename)
84
+ res_img = model.preview_detect(filename, confidence / 100.0)
85
  t2 = time.time()
86
 
87
  # Displaying the image
models/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .yolo_crack import *
models/tools/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from .split import *
models/tools/draw.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from PIL import ImageDraw
3
+
4
+
5
+ def plot_one_box(x, img, color=None, label=None, line_thickness=None):
6
+ """
7
+ Helper Functions for Plotting BBoxes
8
+ :param x:
9
+ :param img:
10
+ :param color:
11
+ :param label:
12
+ :param line_thickness:
13
+ :return:
14
+ """
15
+ width, height = img.size
16
+ tl = line_thickness or round(0.002 * (width + height) / 2) + 1 # line/font thickness
17
+ color = color or (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
18
+ c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
19
+ img_draw = ImageDraw.Draw(img)
20
+ img_draw.rectangle((c1[0], c1[1], c2[0], c2[1]), outline=color, width=tl)
21
+ if label:
22
+ tf = max(tl - 1, 1) # font thickness
23
+ x1, y1, x2, y2 = img_draw.textbbox(c1, label, stroke_width=tf)
24
+ img_draw.rectangle((x1, y1, x2, y2), fill=color)
25
+ img_draw.text((x1, y1), label, fill=(255, 255, 255))
26
+
27
+
28
+ def add_bboxes(pil_img, result, confidence=0.6):
29
+ """
30
+ Plotting Bounding Box on img
31
+ :param pil_img:
32
+ :param result:
33
+ :param confidence:
34
+ :return:
35
+ """
36
+ for box in result.boxes:
37
+ [cl] = box.cls.tolist()
38
+ [conf] = box.conf.tolist()
39
+ if conf < confidence:
40
+ continue
41
+ [rect] = box.xyxy.tolist()
42
+ text = f'{result.names[cl]}: {conf: 0.2f}'
43
+ plot_one_box(x=rect, img=pil_img, label=text)
44
+
45
+ return pil_img
46
+
47
+
48
+ def add_bboxes2(pil_img, result, confidence=0.6):
49
+ """
50
+ Plotting Bounding Box on img
51
+ :param pil_img:
52
+ :param result:
53
+ :param confidence:
54
+ :return:
55
+ """
56
+ for box in result['boxes']:
57
+ cl = box['cls']
58
+ conf = box['conf']
59
+ if conf < confidence:
60
+ continue
61
+ rect = box['xyxy']
62
+ text = f'{cl}: {conf: 0.2f}'
63
+ plot_one_box(x=rect, img=pil_img, label=text)
64
+
65
+ return pil_img
models/tools/split.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import copy
3
+ from typing import Optional
4
+ from PIL import Image
5
+
6
+ bbox = [float, float, float, float]
7
+
8
+ annotation = {
9
+ "id": int,
10
+ "image_id": int,
11
+ "category_id": int,
12
+ "bbox": bbox,
13
+ "ignore": int,
14
+ "iscrowd": int,
15
+ "area": float,
16
+ }
17
+
18
+ small_image = {
19
+ "image": Image,
20
+ "area": bbox
21
+ }
22
+
23
+
24
+ def split_image(image: Image,
25
+ hint_size_min: tuple[int, int],
26
+ hint_size_max: tuple[int, int],
27
+ overlap: float = 0.1) -> list[small_image]:
28
+ """
29
+ Given an image and a hint size, split the image into a list of images.
30
+ New images are overlapped with other images by the overlap ratio.
31
+ :param image: The image to split. typically a large image. 1kx1k ~ 10kx10k
32
+ :param hint_size_min: The minimum size of the output image.
33
+ :param hint_size_max: The maximum size of the output image.
34
+ :param overlap: The overlap ratio of the output image.
35
+ :return: A list of images.
36
+ """
37
+ Wi, Hi = image.size
38
+ Wmin, Hmin = hint_size_min
39
+ Wmax, Hmax = hint_size_max
40
+ assert Wmin <= Wmax <= Wi
41
+ assert Hmin <= Hmax <= Hi
42
+ w_search = search(Wi, Wmin, Wmax, overlap)
43
+ h_search = search(Hi, Hmin, Hmax, overlap)
44
+ if w_search is None or h_search is None:
45
+ raise ValueError('The image is too small to split.')
46
+ w_count, output_width, last_output_width, width_overlap = w_search
47
+ h_count, output_height, last_output_height, height_overlap = h_search
48
+ images = []
49
+ for h_index in range(h_count):
50
+ h = h_index * (output_height - height_overlap)
51
+ for w_index in range(w_count):
52
+ w = w_index * (output_width - width_overlap)
53
+ small = {
54
+ "image": image.crop((w, h, w + output_width, h + output_height)),
55
+ "area": (w, h, output_width, output_height)
56
+ }
57
+ images.append(small)
58
+ if last_output_width > 0:
59
+ w = Wi - output_width
60
+ small = {
61
+ "image": image.crop((w, h, w + output_width, h + output_height)),
62
+ "area": (w, h, output_width, output_height)
63
+ }
64
+ images.append(small)
65
+ return images
66
+
67
+
68
+ def search(input: int,
69
+ output_min: int,
70
+ output_max: int,
71
+ overlap: float) -> Optional[tuple[int, int, int, int]]:
72
+ """
73
+ example 1:
74
+ input: 8000, output: 1000, overlap: 0.1
75
+ 8000 // (1000 - 100) = 8
76
+ 8000 % (1000 - 100) = 800
77
+ count = 8, output = 1000, last_output = 800, overlap_pixels = 100
78
+
79
+ example 2:
80
+ input: 7200, output: 800, overlap: 0.1
81
+ 7200 // (800 - 80) = 10
82
+ 7200 % (800 - 80) = 0
83
+ count = 10, output = 800, last_output = 0, overlap_pixels = 80
84
+
85
+ :param input: The length of the input image.
86
+ :param output_min: The minimum length of the output image.
87
+ :param output_max: The maximum length of the output image.
88
+ :param overlap: The overlap ratio of the output image.
89
+ :return: A tuple of (count, output, last_output, overlap_pixels).
90
+ """
91
+
92
+ for output in range(output_max, output_min - 1, -1):
93
+ overlap_pixels = int(output * overlap)
94
+ last_output = input % (output - overlap_pixels)
95
+ if last_output == 0 or output_min <= last_output <= output_max:
96
+ count = input // (output - overlap_pixels)
97
+ return count, output, last_output, overlap_pixels
98
+
99
+ return None
100
+
101
+
102
+ def box_intersected(box1: bbox, box2: bbox) -> bool:
103
+ """
104
+ Check if two boxes are intersected.
105
+ :param box1: The first box.
106
+ :param box2: The second box.
107
+ :return: True if the two boxes are intersected.
108
+ """
109
+ x1, y1, w1, h1 = box1
110
+ x2, y2, w2, h2 = box2
111
+ return x1 < x2 + w2 and x2 < x1 + w1 and y1 < y2 + h2 and y2 < y1 + h1
112
+
113
+
114
+ def fit_in_area(annotations: list[annotation], in_area: bbox) -> list[annotation]:
115
+ result = []
116
+ for old in annotations:
117
+ ann = copy.deepcopy(old)
118
+ result.append(ann)
119
+ x, y, w, h = ann["bbox"]
120
+ if x < in_area[0]:
121
+ ann["bbox"][0] = 0
122
+ else:
123
+ ann["bbox"][0] -= in_area[0]
124
+ if y < in_area[1]:
125
+ ann["bbox"][1] = 0
126
+ else:
127
+ ann["bbox"][1] -= in_area[1]
128
+ if x + w > in_area[0] + in_area[2]:
129
+ ann["bbox"][2] = in_area[2] - ann["bbox"][0]
130
+ if y + h > in_area[1] + in_area[3]:
131
+ ann["bbox"][3] = in_area[3] - ann["bbox"][1]
132
+ return result
133
+
134
+
135
+ small_image_with_labels = {
136
+ "image": Image,
137
+ "area": bbox,
138
+ "labels": list[annotation]
139
+ }
140
+
141
+
142
+ def split_image_with_labels(image: Image,
143
+ labels: list[annotation],
144
+ hint_size_min: tuple[int, int],
145
+ hint_size_max: tuple[int, int],
146
+ overlap: float = 0.1) -> list[small_image]:
147
+ small_imgs = split_image(image, hint_size_min, hint_size_max, overlap)
148
+ result = []
149
+ for small_img in small_imgs:
150
+ small_labels = [ann for ann in labels if box_intersected(ann["bbox"], small_img["area"])]
151
+ small_labels = fit_in_area(small_labels, small_img["area"])
152
+ result.append({
153
+ "image": small_img["image"],
154
+ "area": small_img["area"],
155
+ "labels": small_labels
156
+ })
157
+ return result
158
+
159
+
160
+ def main():
161
+ image = Image.open('../datasets/Das3300161.jpg')
162
+ small_imgs = split_image(image, (800, 800), (1000, 1000), 0.1)
163
+ labels = json.load(open('../datasets/result.json'))
164
+ annotations = list(filter(lambda ann: ann["image_id"] == 28, labels["annotations"]))
165
+ for small_img in small_imgs:
166
+ small_labels = [ann for ann in annotations if box_intersected(ann["bbox"], small_img["area"])]
167
+ small_labels = fit_in_area(small_labels, small_img["area"])
168
+ # save small_labels to json
169
+ json.dump(small_labels, open('datasets/' + str(small_img["area"]) + '.json', 'w'))
170
+ # save small_image["image"] to file
171
+ small_img["image"].save('datasets/' + str(small_img["area"]) + '.jpg')
172
+
173
+
174
+ if __name__ == '__main__':
175
+ main()
models/yolo_crack.py ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from models.tools import split
2
+ from PIL import Image
3
+ from huggingface_hub import hf_hub_download
4
+ from ultralytics import YOLO
5
+
6
+ import torch
7
+ import cv2
8
+ import numpy as np
9
+ import math
10
+
11
+ from models.tools.draw import add_bboxes2
12
+
13
+
14
+ class YoloModel:
15
+ def __init__(self, seg_repo_name: str, seg_file_name: str, det_repo_name: str, det_file_name: str):
16
+ seg_weight_file = YoloModel.download_weight_file(seg_repo_name, seg_file_name)
17
+ det_weight_file = YoloModel.download_weight_file(det_repo_name, det_file_name)
18
+ self.seg_model = YOLO(seg_weight_file)
19
+ self.det_model = YOLO(det_weight_file)
20
+
21
+ @staticmethod
22
+ def download_weight_file(repo_name: str, file_name: str):
23
+ return hf_hub_download(repo_name, file_name)
24
+
25
+ def preview_detect(self, im, confidence):
26
+ results = self.detect(im)
27
+ res_img = Image.open(im)
28
+ res = {
29
+ 'boxes': [
30
+ {
31
+ 'xyxy': [x1, y1, x2, y2],
32
+ 'cls': cls,
33
+ 'conf': conf
34
+ } for x1, y1, x2, y2, conf, cls in results
35
+ ]
36
+ }
37
+ res_img = add_bboxes2(res_img, res, confidence)
38
+ return res_img
39
+
40
+ def detect(self, source):
41
+ pred_bbox_list = [] # 初始化该图像bbox列表
42
+ threshold = 50 # 暂定bbox merge 阈值为50, 后期可根据用户需求做自适应调整
43
+ strategy = "distance" # 暂定bbox merge 策略为distance
44
+
45
+ seg_img_list = self._seg_ori_img(source) # 对该图像进行路面分割
46
+ assert len(seg_img_list) == 1, "seg_img_list out of range"
47
+ road_img = Image.fromarray(cv2.cvtColor(seg_img_list[0], cv2.COLOR_BGR2RGB))
48
+ small_imgs = split.split_image(road_img, (640, 640), (1080, 1080), 0.1) # 对路面图像进行小图分割
49
+ num = 0
50
+ for small_img in small_imgs:
51
+ num += 1
52
+ results = self.det_model(source=small_img["image"])
53
+ for result in results:
54
+ temp_bbox_list = result.boxes.xyxy # 获取检测结果中的bbox坐标(此处使用xyxy格式)
55
+ w_bias = small_img["area"][0]
56
+ h_bias = small_img["area"][1]
57
+ temp_bbox_list = self._bbox_map(temp_bbox_list, w_bias, h_bias) # 将bbox坐标映射到原始大图坐标系中
58
+ temp_bbox_cls = result.boxes.cls # 获取检测结果中的class
59
+ temp_bbox_conf = result.boxes.conf # 获取检测结果中的confidence
60
+ assert len(temp_bbox_list) == len(temp_bbox_cls) == len(
61
+ temp_bbox_conf), 'different number of matrix size'
62
+ for i in range(len(temp_bbox_list)): # 整合bbox、conf和class到一个数组中
63
+ temp_bbox_list[i].append(temp_bbox_conf[i])
64
+ temp_bbox_list[i].append(temp_bbox_cls[i])
65
+ pred_bbox_list += temp_bbox_list # 将单张大图分割后的全体小图得到的检测结果(bbox、conf、class)整合到一个list
66
+ pred_bbox_list = self._merge_box(pred_bbox_list, threshold, strategy=strategy) # 调用指定算法,对bbox进行分析合并
67
+
68
+ return pred_bbox_list
69
+
70
+ def _seg_ori_img(self, source):
71
+ """
72
+ 分割原始图像中的沥青路面区域
73
+ :param source: 图像路径
74
+ :return: 分割得到的沥青路面图像(尺寸与原始图像一致,非路面区域用白色填充)
75
+ """
76
+ ori_img = cv2.imread(source)
77
+ ori_size = ori_img.shape
78
+ results = self.seg_model(source=source)
79
+ seg_img_list = []
80
+
81
+ for result in results:
82
+ if result.masks is not None and len(result.masks) > 0: # 检测到路面时
83
+ masks_data = result.masks.data
84
+ obj_masks = masks_data[:]
85
+ road_mask = torch.any(obj_masks, dim=0).int() * 255
86
+ mask = road_mask.cpu().numpy()
87
+ Mask = mask.astype(np.uint8)
88
+ mask_res = cv2.resize(Mask, (ori_size[1], ori_size[0]), interpolation=cv2.INTER_CUBIC)
89
+
90
+ else: # 检测不到路面时保存纯黑色图像
91
+ mask_res = np.zeros((ori_size[0], ori_size[1], 3), dtype=np.uint8)
92
+
93
+ mask_region = mask_res == 0
94
+ ori_img[mask_region] = 255 # 判断条件置0掩码为黑,置255背景为白
95
+ seg_img_list.append(ori_img)
96
+
97
+ return seg_img_list
98
+
99
+ def _bbox_map(self, bbox_list, w, h):
100
+ """
101
+ 将小图中的bbox坐标映射到原始图像中
102
+ :param bbox_list: 小图中的bbox数组
103
+ :param w: 小图在原始图像中的偏置w
104
+ :param h: 小图在原始图像中的偏置h
105
+ :return: 该bbox数组在原始图像中的坐标
106
+ """
107
+ if isinstance(bbox_list, torch.Tensor):
108
+ bbox_list = bbox_list.tolist()
109
+ for bbox in bbox_list:
110
+ bbox[0] += w
111
+ bbox[1] += h
112
+ bbox[2] += w
113
+ bbox[3] += h
114
+
115
+ return bbox_list
116
+
117
+ def _xywh2xyxy(self, box_list):
118
+ """
119
+ YOLO标签,xywh转xyxy
120
+ :param box_list: bbox数组(xywh)
121
+ :return: bbox数组(xyxy)
122
+ """
123
+ new_box_list = []
124
+ for box in box_list:
125
+ x1 = box[0] - box[2] / 2
126
+ y1 = box[1] - box[3] / 2
127
+ x2 = box[0] + box[2] / 2
128
+ y2 = box[1] + box[3] / 2
129
+ new_box_list.append([x1, y1, x2, y2])
130
+
131
+ return new_box_list
132
+
133
+ def _xyxy2xywh(self, box_list):
134
+ """
135
+ YOLO标签,xyxy转xywh
136
+ :param box_list: bbox数组(xyxy)
137
+ :return: bbox数组(xywh)
138
+ """
139
+ new_box_list = []
140
+ for box in box_list:
141
+ x1 = (box[0] + box[2]) / 2
142
+ y1 = (box[1] + box[3]) / 2
143
+ w = (box[2] - box[0])
144
+ h = (box[3] - box[1])
145
+ new_box_list.append([x1, y1, w, h])
146
+
147
+ return new_box_list
148
+
149
+ def _nor2std(self, box_list, img_w, img_h):
150
+ """
151
+ YOLO标签,标准化坐标映射到原始图像
152
+ :param box_list: bbox数组(nor)
153
+ :param img_w: 原始图像宽度
154
+ :param img_h: 原始图像高度
155
+ :return: bbox数组(在原始图像中的坐标)
156
+ """
157
+ for box in box_list:
158
+ box[0] *= img_w
159
+ box[1] *= img_h
160
+ box[2] *= img_w
161
+ box[3] *= img_h
162
+
163
+ def _std2nor(self, box_list, img_w, img_h):
164
+ """
165
+ YOLO标签,原始图像坐标转标准化坐标
166
+ :param box_list: bbox数组(std)
167
+ :param img_w: 原始图像宽度
168
+ :param img_h: 原始图像高度
169
+ :return: bbox数组(标准化坐标)
170
+ """
171
+ for box in box_list:
172
+ box[0] /= img_w
173
+ box[1] /= img_h
174
+ box[2] /= img_w
175
+ box[3] /= img_h
176
+
177
+ def _judge_merge_by_center_distance(self, center_box1, center_box2, distance_threshold):
178
+ """
179
+ 根据bbox中心坐标间距,判断是否进行bbox合并
180
+ :param center_box1: box1的中心坐标
181
+ :param center_box2: box2的中心坐标
182
+ :param distance_threshold: 间距阈值
183
+ :return: 若间距小于阈值,进行合并(Ture);反之则忽略(False)
184
+ """
185
+ x1 = center_box1[0]
186
+ x2 = center_box2[0]
187
+ y1 = center_box1[1]
188
+ y2 = center_box2[1]
189
+ distance = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
190
+ if distance < distance_threshold:
191
+ return True
192
+ else:
193
+ return False
194
+
195
+ def _judge_merge_by_overlap_area(self, std_box1, std_box2, overlap_threshold):
196
+ """
197
+ 根据bbox交叉面积,判断是否进行bbox合并
198
+ :param std_box1: box1的标准坐标
199
+ :param std_box2: box2的标准坐标
200
+ :param overlap_threshold: 交叉面积阈值
201
+ :return: 若交叉面积大于阈值,进行合并(True);反之则忽略(False)
202
+ """
203
+ x1 = max(std_box1[0], std_box2[0])
204
+ y1 = max(std_box1[1], std_box2[1])
205
+ x2 = min(std_box1[2], std_box2[2])
206
+ y2 = min(std_box1[3], std_box2[3])
207
+ width = max(0, x2 - x1)
208
+ height = max(0, y2 - y1)
209
+ area = width * height
210
+ if area < overlap_threshold:
211
+ return False
212
+ else:
213
+ return True
214
+
215
+ def _basic_merge(self, box1, box2):
216
+ """
217
+ 合并两个box,生成新的box坐标
218
+ :param box1: box1坐标(std)
219
+ :param box2: box2坐标(std)
220
+ :return: 新box坐标(std)
221
+ """
222
+ x11 = box1[0]
223
+ y11 = box1[1]
224
+ x12 = box1[2]
225
+ y12 = box1[3]
226
+ x21 = box2[0]
227
+ y21 = box2[1]
228
+ x22 = box2[2]
229
+ y22 = box2[3]
230
+ new_x1 = min(x11, x12, x21, x22)
231
+ new_y1 = min(y11, y12, y21, y22)
232
+ new_x2 = max(x11, x12, x21, x22)
233
+ new_y2 = max(y11, y12, y21, y22)
234
+ assert len(box1) == len(box2), 'box1 and box2 has different size'
235
+ if len(box1) == 6: # 此时,box中带有conf和class,其结构为[x1, y1, x2, y2, conf, class]
236
+ avg_conf = (box1[4] + box2[4]) / 2
237
+ clas = box1[5]
238
+ new_box = [new_x1, new_y1, new_x2, new_y2, avg_conf, clas]
239
+ else:
240
+ new_box = [new_x1, new_y1, new_x2, new_y2]
241
+
242
+ return new_box
243
+
244
+ def _update_list(self, bbox_list, del_index):
245
+ """
246
+ 更新bbox数组,删除特定的bbox元素(已经被合并到其他box中的bbox)
247
+ :param bbox_list: bbox数组
248
+ :param del_index: 待删除bbox元素的rank
249
+ :return: 更新后的bbox数组
250
+ """
251
+ assert len(bbox_list) > del_index >= 0, 'del_index out of boundary'
252
+ bbox_list[del_index] = bbox_list[-1:][0]
253
+ bbox_list.pop()
254
+ return bbox_list
255
+
256
+ def _merge_box(self, std_bbox_list, threshold, strategy='overlap'):
257
+ """
258
+ bbox合并算法,根据选定的合并策略及阈值,进行bbox合并
259
+ :param std_bbox_list: std_bbox_list可有两种格式:(Array[N, 4] -> [x1, y1, x2, y2]; Array[N, 6] -> [x1, y1, x2, y2, conf, class])
260
+ :param threshold: 阈值
261
+ :param strategy: 合并策略(distance/overlap)
262
+ """
263
+ if isinstance(std_bbox_list, torch.Tensor):
264
+ std_bbox_list = std_bbox_list.tolist()
265
+ center_bbox_list = self._xyxy2xywh(std_bbox_list)
266
+ i = 0
267
+ while i < len(std_bbox_list):
268
+ j = i + 1
269
+ while j < len(std_bbox_list):
270
+ if strategy == 'overlap':
271
+ assert i < len(std_bbox_list) and j < len(std_bbox_list), f'len={len(std_bbox_list)}, j={j}, i={i}'
272
+ if self._judge_merge_by_overlap_area(std_bbox_list[i], std_bbox_list[j], threshold):
273
+ std_bbox_list[i] = self._basic_merge(std_bbox_list[i], std_bbox_list[j])
274
+ self._update_list(std_bbox_list, j)
275
+ self._update_list(center_bbox_list, j)
276
+ continue
277
+ else:
278
+ if self._judge_merge_by_center_distance(center_bbox_list[i], center_bbox_list[j], threshold):
279
+ std_bbox_list[i] = self._basic_merge(std_bbox_list[i], std_bbox_list[j])
280
+ self._update_list(std_bbox_list, j)
281
+ self._update_list(center_bbox_list, j)
282
+ continue
283
+ j += 1
284
+ i += 1
285
+
286
+ return std_bbox_list
287
+
288
+
289
+ def main():
290
+ model = YoloModel("SHOU-ISD/yolo-cracks", "last4.pt", "SHOU-ISD/yolo-cracks", "best.pt")
291
+ model.preview_detect('./datasets/Das1100209.jpg', 0.4).show()
292
+
293
+
294
+ if __name__ == '__main__':
295
+ main()
yolo_model.py CHANGED
@@ -1,50 +1,8 @@
1
- import random
2
- from PIL import ImageDraw, Image
3
  from huggingface_hub import hf_hub_download
4
  from ultralytics import YOLO
5
 
6
-
7
- def plot_one_box(x, img, color=None, label=None, line_thickness=None):
8
- """
9
- Helper Functions for Plotting BBoxes
10
- :param x:
11
- :param img:
12
- :param color:
13
- :param label:
14
- :param line_thickness:
15
- :return:
16
- """
17
- width, height = img.size
18
- tl = line_thickness or round(0.002 * (width + height) / 2) + 1 # line/font thickness
19
- color = color or (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
20
- c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
21
- img_draw = ImageDraw.Draw(img)
22
- img_draw.rectangle((c1[0], c1[1], c2[0], c2[1]), outline=color, width=tl)
23
- if label:
24
- tf = max(tl - 1, 1) # font thickness
25
- x1, y1, x2, y2 = img_draw.textbbox(c1, label, stroke_width=tf)
26
- img_draw.rectangle((x1, y1, x2, y2), fill=color)
27
- img_draw.text((x1, y1), label, fill=(255, 255, 255))
28
-
29
-
30
- def add_bboxes(pil_img, result, confidence=0.6):
31
- """
32
- Plotting Bounding Box on img
33
- :param pil_img:
34
- :param result:
35
- :param confidence:
36
- :return:
37
- """
38
- for box in result.boxes:
39
- [cl] = box.cls.tolist()
40
- [conf] = box.conf.tolist()
41
- if conf < confidence:
42
- continue
43
- [rect] = box.xyxy.tolist()
44
- text = f'{result.names[cl]}: {conf: 0.2f}'
45
- plot_one_box(x=rect, img=pil_img, label=text)
46
-
47
- return pil_img
48
 
49
 
50
  class YoloModel:
@@ -59,9 +17,10 @@ class YoloModel:
59
  def detect(self, im):
60
  return self.model(source=im)
61
 
62
- def preview_detect(self, im, confidence):
63
- results = self.model(source=im)
64
- res_img = im
 
65
  for result in results:
66
  res_img = add_bboxes(res_img, result, confidence)
67
  return res_img
 
1
+ from PIL import Image
 
2
  from huggingface_hub import hf_hub_download
3
  from ultralytics import YOLO
4
 
5
+ from models.tools.draw import add_bboxes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
 
8
  class YoloModel:
 
17
  def detect(self, im):
18
  return self.model(source=im)
19
 
20
+ def preview_detect(self, filename, confidence):
21
+ image = Image.open(filename)
22
+ results = self.model(source=image)
23
+ res_img = image
24
  for result in results:
25
  res_img = add_bboxes(res_img, result, confidence)
26
  return res_img