├── .gitignore
├── LICENSE
├── README.md
├── UIFunctions.py
├── assets
└── main-page.png
├── config
├── fold.json
├── ip.json
└── setting.json
├── custom_grips.py
├── home.ui
├── home_ui.py
├── img
├── ASE.png
├── IOU.png
├── RTSP.png
├── begin.png
├── box_down.png
├── box_up.png
├── cam.png
├── check_no.png
├── check_yes.png
├── classify.png
├── conf.png
├── delay.png
├── detect.png
├── file.png
├── home.png
├── logo.png
├── menu.png
├── model.png
├── pause.png
├── pose.png
├── resize.png
├── save.png
├── segment.png
├── set.png
├── stop.png
└── track.png
├── main.py
├── models
├── classify
│ └── yolov8n-cls.pt
├── detect
│ └── yolov8n.pt
├── pose
│ └── yolov8n-pose.pt
└── segment
│ └── yolov8n-seg.pt
├── resources.qrc
├── resources_rc.py
├── ui
├── CustomMessageBox.py
├── home.py
├── resources_rc.py
├── rtsp_dialog.ui
└── rtsp_dialog_ui.py
└── utils
├── capnums.py
├── rtsp_dialog.py
└── rtsp_win.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 | *.cache
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # Profiling
85 | *.pclprof
86 |
87 | # pyenv
88 | .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98 | __pypackages__/
99 |
100 | # Celery stuff
101 | celerybeat-schedule
102 | celerybeat.pid
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | .idea
111 | env/
112 | venv/
113 | ENV/
114 | env.bak/
115 | venv.bak/
116 |
117 | # Spyder project settings
118 | .spyderproject
119 | .spyproject
120 |
121 | # VSCode project settings
122 | .vscode/
123 |
124 | # Rope project settings
125 | .ropeproject
126 |
127 | # mkdocs documentation
128 | /site
129 | mkdocs_github_authors.yaml
130 |
131 | # mypy
132 | .mypy_cache/
133 | .dmypy.json
134 | dmypy.json
135 |
136 | # Pyre type checker
137 | .pyre/
138 |
139 | # datasets and projects
140 | # datasets/
141 | runs/
142 | wandb/
143 | # tests/
144 | .DS_Store
145 |
146 | # Neural Network weights -----------------------------------------------------------------------------------------------
147 | weights/
148 | # *.weights
149 | # *.pt
150 | *.pb
151 | # *.onnx
152 | # *.engine
153 | *.mlmodel
154 | *.mlpackage
155 | *.torchscript
156 | *.tflite
157 | *.h5
158 | *_saved_model/
159 | *_web_model/
160 | *_openvino_model/
161 | *_paddle_model/
162 | pnnx*
163 |
164 | # Autogenerated files for tests
165 | # /ultralytics/assets/
166 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # YOLOv8-PySide --- 基于 ultralytics 8.1.0 发行版优化
2 | [](https://doi.org/10.5281/zenodo.11063548)
3 | ## 页面效果
4 |
5 | 
6 |
7 | ## 如何使用
8 | - `python>=3.8`
9 | - `pip install ultralytics==8.1.0` or `git clone --branch v8.1.0 --single-branch https://github.com/ultralytics/ultralytics.git` `pip install -e .`
10 | - `pip install pyside6 chardet`
11 | - `pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 torchaudio==0.12.1 --extra-index-url https://download.pytorch.org/whl/cu113`
12 | - `python main.py`
13 |
14 |
15 | ## 项目功能
16 | - ✅ 图片推理
17 | - ✅ 视频推理
18 | - ✅ 摄像头推理
19 | - ✅ RTSP 推流
20 | - ✅ 分类任务推理
21 | - ✅ 检测任务推理
22 | - ✅ 分割任务推理
23 | - ✅ 关键点任务推理
24 | - ❌ 追踪任务推理
25 | - ❌ 旋转框任务推理
26 | - ✅ Pytroch (.pt) 格式模型推理
27 | - ✅ ONNX (.onnx) 格式模型推理
28 | - ✅ TensorRT (.engine) 格式模型推理
29 | - ✅ 模型选择
30 | - ✅ 置信度/阈值调整
31 | - ✅ 延迟调整
32 | - ✅ 保存推理结果
33 |
34 | ## 注意事项
35 | - 跟踪功能未集成。
36 | - 旋转框检测未集成。
37 | - 打包成功可能无法运行。
38 | - 如果想使用自己的模型,您需要先使用 `ultralytics` 来训练 yolov8 模型,然后将训练好的 `.pt/.onnx/.engine` 文件放入 `models/*` 文件夹。
39 | - 如果模型是改进的,请将你整个项目文件导入。
40 | - 如果选择保存结果,结果会保存在 `./run` 路径中。
41 | - UI 设计文件是 `home.ui`,如果修改它,您需要使用 `pyside6-uic home.ui > ui/home.py` 命令来重新生成 `.py` 文件。
42 | - 资源文件是 `resources.qrc`,如果您修改了默认图标,需要使用 `pyside6-rcc resources.qrc > ui/resources_rc.py` 命令来重新生成 `.py` 文件。
43 |
44 | ## Star History
45 |
46 | [](https://star-history.com/#WangQvQ/Ultralytics-PySide6&Timeline)
47 |
48 | ## Cite
49 | ```
50 | @software{wangqvq_2024_11063548,
51 | author = {WangQvQ},
52 | title = {WangQvQ/Ultralytics-PySide6: v1.0},
53 | month = apr,
54 | year = 2024,
55 | publisher = {Zenodo},
56 | version = {v1.0},
57 | doi = {10.5281/zenodo.11063548},
58 | url = {https://doi.org/10.5281/zenodo.11063548}
59 | }
60 | ```
61 |
62 | ## References
63 |
64 | - [作者CSDN主页](https://yolov5.blog.csdn.net)
65 | - [PyQt5-YOLOv5](https://github.com/Javacr/PyQt5-YOLOv5)
66 | - [ultralytics](https://github.com/ultralytics/ultralytics)
67 | - [PySide6-YOLOv8](https://github.com/Jai-wei/YOLOv8-PySide6-GUI/tree/main)
68 | - [YOLOv8-GUI-PySide6](https://github.com/SuPoTing/YOLOv8-GUI-PySide6)
69 |
--------------------------------------------------------------------------------
/UIFunctions.py:
--------------------------------------------------------------------------------
1 | from main import *
2 | from custom_grips import CustomGrip
3 | from PySide6.QtCore import QPropertyAnimation, QEasingCurve, QEvent, QTimer
4 | from PySide6.QtCore import *
5 | from PySide6.QtGui import *
6 | from PySide6.QtWidgets import *
7 | import time
8 |
9 | GLOBAL_STATE = False # max min flag
10 | GLOBAL_TITLE_BAR = True
11 |
12 |
13 | class UIFuncitons(MainWindow):
14 | # 展开左侧菜单
15 | def toggleMenu(self, enable):
16 | if enable:
17 | standard = 68 # 左侧菜单的标准宽度
18 | maxExtend = 180 # 左侧菜单展开时的最大宽度
19 | width = self.LeftMenuBg.width() # 现在的菜单宽度
20 |
21 | if width == 68: # 如果菜单目前是缩起来的
22 | widthExtended = maxExtend # 展开后的宽度
23 | else:
24 | widthExtended = standard # 收缩后的宽度
25 |
26 | # 动画效果
27 | self.animation = QPropertyAnimation(self.LeftMenuBg, b"minimumWidth")
28 | self.animation.setDuration(500) # 动画时间(毫秒)
29 | self.animation.setStartValue(width) # 动画的起始宽度
30 | self.animation.setEndValue(widthExtended) # 动画的结束宽度
31 | self.animation.setEasingCurve(QEasingCurve.InOutQuint) # 动画的缓动曲线
32 | self.animation.start() # 开始执行动画
33 |
34 | # 展开右侧的设定选单
35 | def settingBox(self, enable):
36 | if enable:
37 | # 获取宽度
38 | widthRightBox = self.prm_page.width() # 右侧设定选单的宽度
39 | widthLeftBox = self.LeftMenuBg.width() # 左侧菜单的宽度
40 | maxExtend = 220 # 设定选单展开时的最大宽度
41 | standard = 0
42 |
43 | # 设定最大宽度
44 | if widthRightBox == 0: # 如果右侧设定选单目前是收缩的
45 | widthExtended = maxExtend # 展开后的宽度
46 | else:
47 | widthExtended = standard # 收缩后的宽度
48 |
49 | # 设定左侧菜单的动画
50 | self.left_box = QPropertyAnimation(self.LeftMenuBg, b"minimumWidth")
51 | self.left_box.setDuration(500) # 动画时间(毫秒)
52 | self.left_box.setStartValue(widthLeftBox) # 动画的起始宽度
53 | self.left_box.setEndValue(68) # 动画的结束宽度(收缩的宽度)
54 | self.left_box.setEasingCurve(QEasingCurve.InOutQuart) # 动画的缓动曲线
55 |
56 | # 设定右侧设定选单的动画
57 | self.right_box = QPropertyAnimation(self.prm_page, b"minimumWidth")
58 | self.right_box.setDuration(500) # 动画时间(毫秒)
59 | self.right_box.setStartValue(widthRightBox) # 动画的起始宽度
60 | self.right_box.setEndValue(widthExtended) # 动画的结束宽度
61 | self.right_box.setEasingCurve(QEasingCurve.InOutQuart) # 动画的缓动曲线
62 |
63 | # 创建一个平行动画组
64 | self.group = QParallelAnimationGroup()
65 | self.group.addAnimation(self.left_box)
66 | self.group.addAnimation(self.right_box)
67 | self.group.start() # 开始执行动画
68 |
69 | # 展开右侧的设定选单
70 | def cam_settingBox(self, enable):
71 | if enable:
72 | # 获取宽度
73 | widthRightBox = self.prm_page_cam.width() # 右侧设定选单的宽度
74 | widthLeftBox = self.LeftMenuBg.width() # 左侧菜单的宽度
75 | maxExtend = 220 # 设定选单展开时的最大宽度
76 | standard = 0
77 |
78 | # 设定最大宽度
79 | if widthRightBox == 0: # 如果右侧设定选单目前是收缩的
80 | widthExtended = maxExtend # 展开后的宽度
81 | else:
82 | widthExtended = standard # 收缩后的宽度
83 |
84 | # 设定左侧菜单的动画
85 | self.left_box = QPropertyAnimation(self.LeftMenuBg, b"minimumWidth")
86 | self.left_box.setDuration(500) # 动画时间(毫秒)
87 | self.left_box.setStartValue(widthLeftBox) # 动画的起始宽度
88 | self.left_box.setEndValue(68) # 动画的结束宽度(收缩的宽度)
89 | self.left_box.setEasingCurve(QEasingCurve.InOutQuart) # 动画的缓动曲线
90 |
91 | # 设定右侧设定选单的动画
92 | self.right_box = QPropertyAnimation(self.prm_page_cam, b"minimumWidth")
93 | self.right_box.setDuration(500) # 动画时间(毫秒)
94 | self.right_box.setStartValue(widthRightBox) # 动画的起始宽度
95 | self.right_box.setEndValue(widthExtended) # 动画的结束宽度
96 | self.right_box.setEasingCurve(QEasingCurve.InOutQuart) # 动画的缓动曲线
97 |
98 | # 创建一个平行动画组
99 | self.group = QParallelAnimationGroup()
100 | self.group.addAnimation(self.left_box)
101 | self.group.addAnimation(self.right_box)
102 | self.group.start() # 开始执行动画
103 |
104 | # 最大化/还原视窗
105 | def maximize_restore(self):
106 | global GLOBAL_STATE # 使用全局变数
107 | status = GLOBAL_STATE # 取得全局变数的值
108 | if status == False: # 如果视窗不是最大化状态
109 | GLOBAL_STATE = True # 设置全局变数为 True(最大化状态)
110 | self.showMaximized() # 最大化视窗
111 | self.max_sf.setToolTip("Restore") # 更改最大化按钮的提示文本
112 | self.frame_size_grip.hide() # 隐藏视窗大小调整按钮
113 | self.left_grip.hide() # 隐藏四边调整的按钮
114 | self.right_grip.hide()
115 | self.top_grip.hide()
116 | self.bottom_grip.hide()
117 | else:
118 | GLOBAL_STATE = False # 设置全局变数为 False(非最大化状态)
119 | self.showNormal() # 还原视窗(最小化)
120 | self.resize(self.width() + 1, self.height() + 1) # 修复最小化后的视窗大小
121 | self.max_sf.setToolTip("Maximize") # 更改最大化按钮的提示文本
122 | self.frame_size_grip.show() # 显示视窗大小调整按钮
123 | self.left_grip.show() # 显示四边调整的按钮
124 | self.right_grip.show() # 显示四边调整的按钮
125 | self.top_grip.show() # 显示四边调整的按钮
126 | self.bottom_grip.show() # 显示四边调整的按钮
127 |
128 | # 视窗控制的定义
129 | def uiDefinitions(self):
130 | # 双击标题栏最大化/还原
131 | def dobleClickMaximizeRestore(event):
132 | if event.type() == QEvent.MouseButtonDblClick:
133 | QTimer.singleShot(250, lambda: UIFuncitons.maximize_restore(self))
134 |
135 | self.top.mouseDoubleClickEvent = dobleClickMaximizeRestore
136 |
137 | # 移动视窗 / 最大化 / 还原
138 | def moveWindow(event):
139 | if GLOBAL_STATE: # 如果视窗已最大化,则切换到还原状态
140 | UIFuncitons.maximize_restore(self)
141 | if event.buttons() == Qt.LeftButton: # 移动视窗
142 | self.move(self.pos() + event.globalPos() - self.dragPos)
143 | self.dragPos = event.globalPos()
144 |
145 | self.top.mouseMoveEvent = moveWindow
146 |
147 | # 自定义拉伸按钮
148 | self.left_grip = CustomGrip(self, Qt.LeftEdge, True)
149 | self.right_grip = CustomGrip(self, Qt.RightEdge, True)
150 | self.top_grip = CustomGrip(self, Qt.TopEdge, True)
151 | self.bottom_grip = CustomGrip(self, Qt.BottomEdge, True)
152 |
153 | # 最小化视窗
154 | self.min_sf.clicked.connect(lambda: self.showMinimized())
155 | # 最大化/还原视窗
156 | self.max_sf.clicked.connect(lambda: UIFuncitons.maximize_restore(self))
157 | # 关闭应用程式
158 | self.close_button.clicked.connect(self.close)
159 |
160 | # 控制视窗四边的拉伸
161 | def resize_grips(self):
162 | # 设置左侧拉伸按钮的位置和大小
163 | self.left_grip.setGeometry(0, 10, 10, self.height())
164 | # 设置右侧拉伸按钮的位置和大小
165 | self.right_grip.setGeometry(self.width() - 10, 10, 10, self.height())
166 | # 设置上侧拉伸按钮的位置和大小
167 | self.top_grip.setGeometry(0, 0, self.width(), 10)
168 | # 设置下侧拉伸按钮的位置和大小
169 | self.bottom_grip.setGeometry(0, self.height() - 10, self.width(), 10)
170 |
171 | # 显示模组以添加阴影效果
172 | def shadow_style(self, widget, Color):
173 | shadow = QGraphicsDropShadowEffect(self) # 创建阴影效果对象
174 | shadow.setOffset(8, 8) # 设定阴影的偏移量
175 | shadow.setBlurRadius(38) # 设定阴影的模糊半径
176 | shadow.setColor(Color) # 设定阴影的颜色
177 | widget.setGraphicsEffect(shadow) # 将阴影效果应用到指定的小部件
178 |
--------------------------------------------------------------------------------
/assets/main-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/assets/main-page.png
--------------------------------------------------------------------------------
/config/fold.json:
--------------------------------------------------------------------------------
1 | {
2 | "open_fold": "C:/Users/wangqi/Desktop"
3 | }
--------------------------------------------------------------------------------
/config/ip.json:
--------------------------------------------------------------------------------
1 | {
2 | "ip": "rtsp://admin:admin888@192.168.1.2:555"
3 | }
--------------------------------------------------------------------------------
/config/setting.json:
--------------------------------------------------------------------------------
1 | {
2 | "iou": 0.45,
3 | "conf": 0.25,
4 | "rate": 10,
5 | "save_res": 0,
6 | "save_txt": 0,
7 | "save_res_cam": 0,
8 | "save_txt_cam": 0
9 | }
--------------------------------------------------------------------------------
/custom_grips.py:
--------------------------------------------------------------------------------
1 | from PySide6.QtCore import *
2 | from PySide6.QtGui import *
3 | from PySide6.QtWidgets import *
4 |
5 | # After the interface hides the window, use this file to achieve borderless stretching of the window
6 | class CustomGrip(QWidget):
7 | def __init__(self, parent, position, disable_color=False):
8 | # SETUP UI
9 | QWidget.__init__(self)
10 | self.parent = parent
11 | self.setParent(parent)
12 | self.wi = Widgets()
13 |
14 | # SHOW TOP GRIP
15 | if position == Qt.TopEdge:
16 | self.wi.top(self)
17 | self.setGeometry(0, 0, self.parent.width(), 10)
18 | self.setMaximumHeight(10)
19 |
20 | # GRIPS
21 | top_left = QSizeGrip(self.wi.top_left)
22 | top_right = QSizeGrip(self.wi.top_right)
23 |
24 | # RESIZE TOP
25 | def resize_top(event):
26 | delta = event.pos()
27 | height = max(
28 | self.parent.minimumHeight(), self.parent.height() - delta.y()
29 | )
30 | geo = self.parent.geometry()
31 | geo.setTop(geo.bottom() - height)
32 | self.parent.setGeometry(geo)
33 | event.accept()
34 |
35 | self.wi.top.mouseMoveEvent = resize_top
36 |
37 | # ENABLE COLOR
38 | if disable_color:
39 | self.wi.top_left.setStyleSheet("background: transparent")
40 | self.wi.top_right.setStyleSheet("background: transparent")
41 | self.wi.top.setStyleSheet("background: transparent")
42 |
43 | # SHOW BOTTOM GRIP
44 | elif position == Qt.BottomEdge:
45 | self.wi.bottom(self)
46 | self.setGeometry(0, self.parent.height() - 10, self.parent.width(), 10)
47 | self.setMaximumHeight(10)
48 |
49 | # GRIPS
50 | self.bottom_left = QSizeGrip(self.wi.bottom_left)
51 | self.bottom_right = QSizeGrip(self.wi.bottom_right)
52 |
53 | # RESIZE BOTTOM
54 | def resize_bottom(event):
55 | delta = event.pos()
56 | height = max(
57 | self.parent.minimumHeight(), self.parent.height() + delta.y()
58 | )
59 | self.parent.resize(self.parent.width(), height)
60 | event.accept()
61 |
62 | self.wi.bottom.mouseMoveEvent = resize_bottom
63 |
64 | # ENABLE COLOR
65 | if disable_color:
66 | self.wi.bottom_left.setStyleSheet("background: transparent")
67 | self.wi.bottom_right.setStyleSheet("background: transparent")
68 | self.wi.bottom.setStyleSheet("background: transparent")
69 |
70 | # SHOW LEFT GRIP
71 | elif position == Qt.LeftEdge:
72 | self.wi.left(self)
73 | self.setGeometry(0, 10, 10, self.parent.height())
74 | self.setMaximumWidth(10)
75 |
76 | # RESIZE LEFT
77 | def resize_left(event):
78 | delta = event.pos()
79 | width = max(self.parent.minimumWidth(), self.parent.width() - delta.x())
80 | geo = self.parent.geometry()
81 | geo.setLeft(geo.right() - width)
82 | self.parent.setGeometry(geo)
83 | event.accept()
84 |
85 | self.wi.leftgrip.mouseMoveEvent = resize_left
86 |
87 | # ENABLE COLOR
88 | if disable_color:
89 | self.wi.leftgrip.setStyleSheet("background: transparent")
90 |
91 | # RESIZE RIGHT
92 | elif position == Qt.RightEdge:
93 | self.wi.right(self)
94 | self.setGeometry(self.parent.width() - 10, 10, 10, self.parent.height())
95 | self.setMaximumWidth(10)
96 |
97 | def resize_right(event):
98 | delta = event.pos()
99 | width = max(self.parent.minimumWidth(), self.parent.width() + delta.x())
100 | self.parent.resize(width, self.parent.height())
101 | event.accept()
102 |
103 | self.wi.rightgrip.mouseMoveEvent = resize_right
104 |
105 | # ENABLE COLOR
106 | if disable_color:
107 | self.wi.rightgrip.setStyleSheet("background: transparent")
108 |
109 | def mouseReleaseEvent(self, event):
110 | self.mousePos = None
111 |
112 | def resizeEvent(self, event):
113 | if hasattr(self.wi, "container_top"):
114 | self.wi.container_top.setGeometry(0, 0, self.width(), 10)
115 |
116 | elif hasattr(self.wi, "container_bottom"):
117 | self.wi.container_bottom.setGeometry(0, 0, self.width(), 10)
118 |
119 | elif hasattr(self.wi, "leftgrip"):
120 | self.wi.leftgrip.setGeometry(0, 0, 10, self.height() - 20)
121 |
122 | elif hasattr(self.wi, "rightgrip"):
123 | self.wi.rightgrip.setGeometry(0, 0, 10, self.height() - 20)
124 |
125 |
126 | class Widgets(object):
127 | def top(self, Form):
128 | if not Form.objectName():
129 | Form.setObjectName(u"Form")
130 | self.container_top = QFrame(Form)
131 | self.container_top.setObjectName(u"container_top")
132 | self.container_top.setGeometry(QRect(0, 0, 500, 10))
133 | self.container_top.setMinimumSize(QSize(0, 10))
134 | self.container_top.setMaximumSize(QSize(16777215, 10))
135 | self.container_top.setFrameShape(QFrame.NoFrame)
136 | self.container_top.setFrameShadow(QFrame.Raised)
137 | self.top_layout = QHBoxLayout(self.container_top)
138 | self.top_layout.setSpacing(0)
139 | self.top_layout.setObjectName(u"top_layout")
140 | self.top_layout.setContentsMargins(0, 0, 0, 0)
141 | self.top_left = QFrame(self.container_top)
142 | self.top_left.setObjectName(u"top_left")
143 | self.top_left.setMinimumSize(QSize(10, 10))
144 | self.top_left.setMaximumSize(QSize(10, 10))
145 | self.top_left.setCursor(QCursor(Qt.SizeFDiagCursor))
146 | self.top_left.setStyleSheet(u"background-color: rgb(33, 37, 43);")
147 | self.top_left.setFrameShape(QFrame.NoFrame)
148 | self.top_left.setFrameShadow(QFrame.Raised)
149 | self.top_layout.addWidget(self.top_left)
150 | self.top = QFrame(self.container_top)
151 | self.top.setObjectName(u"top")
152 | self.top.setCursor(QCursor(Qt.SizeVerCursor))
153 | self.top.setStyleSheet(u"background-color: rgb(85, 255, 255);")
154 | self.top.setFrameShape(QFrame.NoFrame)
155 | self.top.setFrameShadow(QFrame.Raised)
156 | self.top_layout.addWidget(self.top)
157 | self.top_right = QFrame(self.container_top)
158 | self.top_right.setObjectName(u"top_right")
159 | self.top_right.setMinimumSize(QSize(10, 10))
160 | self.top_right.setMaximumSize(QSize(10, 10))
161 | self.top_right.setCursor(QCursor(Qt.SizeBDiagCursor))
162 | self.top_right.setStyleSheet(u"background-color: rgb(33, 37, 43);")
163 | self.top_right.setFrameShape(QFrame.NoFrame)
164 | self.top_right.setFrameShadow(QFrame.Raised)
165 | self.top_layout.addWidget(self.top_right)
166 |
167 | def bottom(self, Form):
168 | if not Form.objectName():
169 | Form.setObjectName(u"Form")
170 | self.container_bottom = QFrame(Form)
171 | self.container_bottom.setObjectName(u"container_bottom")
172 | self.container_bottom.setGeometry(QRect(0, 0, 500, 10))
173 | self.container_bottom.setMinimumSize(QSize(0, 10))
174 | self.container_bottom.setMaximumSize(QSize(16777215, 10))
175 | self.container_bottom.setFrameShape(QFrame.NoFrame)
176 | self.container_bottom.setFrameShadow(QFrame.Raised)
177 | self.bottom_layout = QHBoxLayout(self.container_bottom)
178 | self.bottom_layout.setSpacing(0)
179 | self.bottom_layout.setObjectName(u"bottom_layout")
180 | self.bottom_layout.setContentsMargins(0, 0, 0, 0)
181 | self.bottom_left = QFrame(self.container_bottom)
182 | self.bottom_left.setObjectName(u"bottom_left")
183 | self.bottom_left.setMinimumSize(QSize(10, 10))
184 | self.bottom_left.setMaximumSize(QSize(10, 10))
185 | self.bottom_left.setCursor(QCursor(Qt.SizeBDiagCursor))
186 | self.bottom_left.setStyleSheet(u"background-color: rgb(33, 37, 43);")
187 | self.bottom_left.setFrameShape(QFrame.NoFrame)
188 | self.bottom_left.setFrameShadow(QFrame.Raised)
189 | self.bottom_layout.addWidget(self.bottom_left)
190 | self.bottom = QFrame(self.container_bottom)
191 | self.bottom.setObjectName(u"bottom")
192 | self.bottom.setCursor(QCursor(Qt.SizeVerCursor))
193 | self.bottom.setStyleSheet(u"background-color: rgb(85, 170, 0);")
194 | self.bottom.setFrameShape(QFrame.NoFrame)
195 | self.bottom.setFrameShadow(QFrame.Raised)
196 | self.bottom_layout.addWidget(self.bottom)
197 | self.bottom_right = QFrame(self.container_bottom)
198 | self.bottom_right.setObjectName(u"bottom_right")
199 | self.bottom_right.setMinimumSize(QSize(10, 10))
200 | self.bottom_right.setMaximumSize(QSize(10, 10))
201 | self.bottom_right.setCursor(QCursor(Qt.SizeFDiagCursor))
202 | self.bottom_right.setStyleSheet(u"background-color: rgb(33, 37, 43);")
203 | self.bottom_right.setFrameShape(QFrame.NoFrame)
204 | self.bottom_right.setFrameShadow(QFrame.Raised)
205 | self.bottom_layout.addWidget(self.bottom_right)
206 |
207 | def left(self, Form):
208 | if not Form.objectName():
209 | Form.setObjectName(u"Form")
210 | self.leftgrip = QFrame(Form)
211 | self.leftgrip.setObjectName(u"left")
212 | self.leftgrip.setGeometry(QRect(0, 10, 10, 480))
213 | self.leftgrip.setMinimumSize(QSize(10, 0))
214 | self.leftgrip.setCursor(QCursor(Qt.SizeHorCursor))
215 | self.leftgrip.setStyleSheet(u"background-color: rgb(255, 121, 198);")
216 | self.leftgrip.setFrameShape(QFrame.NoFrame)
217 | self.leftgrip.setFrameShadow(QFrame.Raised)
218 |
219 | def right(self, Form):
220 | if not Form.objectName():
221 | Form.setObjectName(u"Form")
222 | Form.resize(500, 500)
223 | self.rightgrip = QFrame(Form)
224 | self.rightgrip.setObjectName(u"right")
225 | self.rightgrip.setGeometry(QRect(0, 0, 10, 500))
226 | self.rightgrip.setMinimumSize(QSize(10, 0))
227 | self.rightgrip.setCursor(QCursor(Qt.SizeHorCursor))
228 | self.rightgrip.setStyleSheet(u"background-color: rgb(255, 0, 127);")
229 | self.rightgrip.setFrameShape(QFrame.NoFrame)
230 | self.rightgrip.setFrameShadow(QFrame.Raised)
231 |
--------------------------------------------------------------------------------
/img/ASE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/ASE.png
--------------------------------------------------------------------------------
/img/IOU.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/IOU.png
--------------------------------------------------------------------------------
/img/RTSP.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/RTSP.png
--------------------------------------------------------------------------------
/img/begin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/begin.png
--------------------------------------------------------------------------------
/img/box_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/box_down.png
--------------------------------------------------------------------------------
/img/box_up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/box_up.png
--------------------------------------------------------------------------------
/img/cam.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/cam.png
--------------------------------------------------------------------------------
/img/check_no.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/check_no.png
--------------------------------------------------------------------------------
/img/check_yes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/check_yes.png
--------------------------------------------------------------------------------
/img/classify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/classify.png
--------------------------------------------------------------------------------
/img/conf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/conf.png
--------------------------------------------------------------------------------
/img/delay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/delay.png
--------------------------------------------------------------------------------
/img/detect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/detect.png
--------------------------------------------------------------------------------
/img/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/file.png
--------------------------------------------------------------------------------
/img/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/home.png
--------------------------------------------------------------------------------
/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/logo.png
--------------------------------------------------------------------------------
/img/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/menu.png
--------------------------------------------------------------------------------
/img/model.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/model.png
--------------------------------------------------------------------------------
/img/pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/pause.png
--------------------------------------------------------------------------------
/img/pose.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/pose.png
--------------------------------------------------------------------------------
/img/resize.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/resize.png
--------------------------------------------------------------------------------
/img/save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/save.png
--------------------------------------------------------------------------------
/img/segment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/segment.png
--------------------------------------------------------------------------------
/img/set.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/set.png
--------------------------------------------------------------------------------
/img/stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/stop.png
--------------------------------------------------------------------------------
/img/track.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/img/track.png
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | from ultralytics.engine.predictor import BasePredictor
2 | from ultralytics.models.yolo.detect.predict import DetectionPredictor
3 | from ultralytics.engine.results import Results
4 | from ultralytics.utils import DEFAULT_CFG, LOGGER, SETTINGS, callbacks, ops
5 | from ultralytics.utils.plotting import Annotator, colors, save_one_box
6 | from ultralytics.utils.torch_utils import smart_inference_mode
7 | from ultralytics.utils.files import increment_path
8 | from ultralytics.utils.checks import check_imgsz, check_imshow, check_yaml
9 | from ultralytics.data import load_inference_source
10 | from ultralytics.data.augment import LetterBox, classify_transforms
11 | from ultralytics.cfg import get_cfg, get_save_dir
12 | from ultralytics.trackers import track
13 | from ultralytics import YOLO
14 |
15 | from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog, QMenu
16 | from PySide6.QtGui import QImage, QPixmap, QColor
17 | from PySide6.QtCore import QTimer, QThread, Signal, QObject, QPoint, Qt
18 | from ui.CustomMessageBox import MessageBox
19 | from ui.home import Ui_MainWindow
20 | from UIFunctions import *
21 |
22 | from collections import defaultdict
23 | from pathlib import Path
24 | from utils.capnums import Camera
25 | from utils.rtsp_win import Window
26 |
27 | import numpy as np
28 | import threading
29 | import traceback
30 | import time
31 | import json
32 | import torch
33 | import sys
34 | import cv2
35 | import os
36 |
37 |
38 | class YoloPredictor(BasePredictor, QObject):
39 | # 信号定义,用于与其他部分进行通信
40 | yolo2main_pre_img = Signal(np.ndarray) # 原始图像信号
41 | yolo2main_res_img = Signal(np.ndarray) # 测试结果信号
42 | yolo2main_status_msg = Signal(str) # 检测/暂停/停止/测试完成/错误报告信号
43 | yolo2main_fps = Signal(str) # 帧率信号
44 | yolo2main_labels = Signal(dict) # 检测目标结果(每个类别的数量)
45 | yolo2main_progress = Signal(int) # 完整度信号
46 | yolo2main_class_num = Signal(int) # 检测到的类别数量
47 | yolo2main_target_num = Signal(int) # 检测到的目标数量
48 |
49 | def __init__(self, cfg=DEFAULT_CFG, overrides=None, _callbacks=None):
50 | # 调用父类的初始化方法
51 | super(YoloPredictor, self).__init__()
52 | # 初始化 PyQt 的 QObject
53 | QObject.__init__(self)
54 |
55 | # 解析配置文件
56 | self.args = get_cfg(cfg, overrides)
57 | # 设置模型保存目录
58 | self.save_dir = get_save_dir(self.args)
59 | # 初始化一个标志,标记模型是否已经完成预热(warmup)
60 | self.done_warmup = False
61 | # 检查是否要显示图像
62 | if self.args.show:
63 | self.args.show = check_imshow(warn=True)
64 |
65 | # GUI 相关的属性
66 | self.used_model_name = None # 要使用的检测模型的名称
67 | self.new_model_name = None # 实时更改的模型名称
68 | self.source = "" # 输入源
69 | self.stop_dtc = False # 终止检测的标志
70 | self.continue_dtc = True # 暂停检测的标志
71 | self.save_res = False # 保存测试结果的标志
72 | self.save_txt = False # 保存标签(txt)文件的标志
73 | self.save_res_cam = False # 保存webcam测试结果的标志
74 | self.save_txt_cam = False # 保存webcam标签(txt)文件的标志
75 | self.iou_thres = 0.45 # IoU 阈值
76 | self.conf_thres = 0.25 # 置信度阈值
77 | self.speed_thres = 0 # 延迟,毫秒
78 | self.labels_dict = {} # 返回检测结果的字典
79 | self.progress_value = 0 # 进度条的值
80 | self.task = ""
81 |
82 | # 如果设置已完成,可以使用以下属性
83 | self.model = None
84 | self.data = self.args.data # data_dict
85 | self.imgsz = None
86 | self.device = None
87 | self.dataset = None
88 | self.vid_path, self.vid_writer, self.vid_frame = None, None, None
89 | self.plotted_img = None
90 | self.data_path = None
91 | self.source_type = None
92 | self.batch = None
93 | self.results = None
94 | self.transforms = None
95 | self.callbacks = _callbacks or callbacks.get_default_callbacks()
96 | self.txt_path = None
97 | self._lock = threading.Lock() # for automatic thread-safe inference
98 | callbacks.add_integration_callbacks(self)
99 |
100 | # main for detect
101 | @smart_inference_mode()
102 | def run(self, *args, **kwargs):
103 | print(str(self.save_txt) + "sssssssssss")
104 | print(str(self.save_txt_cam) + "sssssssssss")
105 | try:
106 | if self.args.verbose:
107 | LOGGER.info("")
108 | # Setup model
109 | self.yolo2main_status_msg.emit("模型载入中...")
110 | if not self.model:
111 | self.setup_model(self.new_model_name)
112 | self.used_model_name = self.new_model_name
113 |
114 | with self._lock: # for thread-safe inference
115 | # Setup source every time predict is called
116 | self.setup_source(
117 | self.source if self.source is not None else self.args.source
118 | )
119 |
120 | # 检查保存路径/标签
121 | if (
122 | self.save_res
123 | or self.save_txt
124 | or self.save_res_cam
125 | or self.save_txt_cam
126 | ):
127 | (
128 | self.save_dir / "labels"
129 | if (self.save_txt or self.save_txt_cam)
130 | else self.save_dir
131 | ).mkdir(parents=True, exist_ok=True)
132 |
133 | # 模型预热
134 | if not self.done_warmup:
135 | self.model.warmup(
136 | imgsz=(
137 | (
138 | 1
139 | if self.model.pt or self.model.triton
140 | else self.dataset.bs
141 | ),
142 | 3,
143 | *self.imgsz,
144 | )
145 | )
146 | self.done_warmup = True
147 |
148 | self.seen, self.windows, self.batch, profilers = (
149 | 0,
150 | [],
151 | None,
152 | (ops.Profile(), ops.Profile(), ops.Profile()),
153 | )
154 | # 开始检测
155 | count = 0 # frame count
156 | start_time = time.time() # 用于计算帧率
157 | # batch = iter(self.dataset)
158 | for batch in self.dataset:
159 | # while True:
160 | # 终止检测标志检测
161 | if self.stop_dtc:
162 | if isinstance(self.vid_writer[-1], cv2.VideoWriter):
163 | self.vid_writer[-1].release() # 释放最后的视讯写入器
164 | self.yolo2main_status_msg.emit("检测终止")
165 | break
166 |
167 | # 在中途更改模型
168 | if self.used_model_name != self.new_model_name:
169 | # self.yolo2main_status_msg.emit('Change Model...')
170 | self.setup_model(self.new_model_name)
171 | self.used_model_name = self.new_model_name
172 |
173 | # 暂停开关
174 | if self.continue_dtc:
175 | # time.sleep(0.001)
176 | self.yolo2main_status_msg.emit("检测中...")
177 | # batch = next(self.dataset) # 获取下一个数据
178 |
179 | self.batch = batch
180 | path, im0s, vid_cap, s = batch
181 | visualize = (
182 | increment_path(self.save_dir / Path(path).stem, mkdir=True)
183 | if self.args.visualize
184 | else False
185 | )
186 |
187 | # 计算完成度和帧率(待优化)
188 | count += 1 # 帧计数 +1
189 | if vid_cap:
190 | all_count = vid_cap.get(cv2.CAP_PROP_FRAME_COUNT) # 总帧数
191 | else:
192 | all_count = 1
193 | self.progress_value = int(
194 | count / all_count * 1000
195 | ) # 进度条(0~1000)
196 | if count % 5 == 0 and count >= 5: # 每5帧计算一次帧率
197 | self.yolo2main_fps.emit(
198 | str(int(5 / (time.time() - start_time)))
199 | )
200 | start_time = time.time()
201 | # Preprocess
202 | with profilers[0]:
203 | if self.task == "Classify":
204 | im = self.classify_preprocess(im0s)
205 | else:
206 | im = self.preprocess(im0s)
207 | # elif self.task == 'Detect' or self.task == 'Pose' or self.task == 'Segment':
208 | # im = self.preprocess(im0s)
209 |
210 | # Inference
211 | with profilers[1]:
212 | preds = self.inference(im, *args, **kwargs)
213 | # if self.args.embed:
214 | # yield from [preds] if isinstance(preds, torch.Tensor) else preds # yield embedding tensors
215 | # continue
216 |
217 | # Postprocess
218 | with profilers[2]:
219 | if self.task == "Classify":
220 | self.results = self.classify_postprocess(
221 | preds, im, im0s
222 | )
223 | elif self.task == "Detect":
224 | self.results = self.postprocess(preds, im, im0s)
225 | elif self.task == "Pose":
226 | self.results = self.pose_postprocess(preds, im, im0s)
227 | elif self.task == "Segment":
228 | self.results = self.segment_postprocess(preds, im, im0s)
229 |
230 | elif self.task == "Track":
231 | model = YOLO(self.used_model_name)
232 | self.results = model.track(
233 | source=self.source, tracker="bytetrack.yaml"
234 | )
235 | print(self.results)
236 | # pass
237 | self.run_callbacks("on_predict_postprocess_end")
238 | # Visualize, save, write results
239 | n = len(im0s)
240 | for i in range(n):
241 | self.seen += 1
242 | self.results[i].speed = {
243 | "preprocess": profilers[0].dt * 1e3 / n,
244 | "inference": profilers[1].dt * 1e3 / n,
245 | "postprocess": profilers[2].dt * 1e3 / n,
246 | }
247 | p, im0 = (
248 | path[i],
249 | (None if self.source_type.tensor else im0s[i].copy()),
250 | )
251 | p = Path(p)
252 | label_str = self.write_results(
253 | i, self.results, (p, im, im0)
254 | )
255 |
256 | # 标签和数字字典
257 | class_nums = 0
258 | target_nums = 0
259 | self.labels_dict = {}
260 | if "no detections" in label_str:
261 | im0 = im0
262 | pass
263 | else:
264 | im0 = self.plotted_img
265 | for ii in label_str.split(",")[:-1]:
266 | nums, label_name = ii.split("~")
267 | if ":" in nums:
268 | _, nums = nums.split(":")
269 | self.labels_dict[label_name] = int(nums)
270 | target_nums += int(nums)
271 | class_nums += 1
272 | if self.save_res:
273 | self.save_preds(vid_cap, i, str(self.save_dir / p.name))
274 |
275 | # 发送测试结果
276 | self.yolo2main_res_img.emit(im0) # 检测后
277 | self.yolo2main_pre_img.emit(
278 | im0s if isinstance(im0s, np.ndarray) else im0s[0]
279 | ) # 检测前
280 | # self.yolo2main_labels.emit(self.labels_dict) # webcam 需要更改 def write_results
281 | self.yolo2main_class_num.emit(class_nums)
282 | self.yolo2main_target_num.emit(target_nums)
283 |
284 | if self.speed_thres != 0:
285 | time.sleep(self.speed_thres / 1000) # 延迟,毫秒
286 |
287 | self.yolo2main_progress.emit(self.progress_value) # 进度条
288 |
289 | # 检测完成
290 | if not self.source_type.webcam and count + 1 >= all_count:
291 | if isinstance(self.vid_writer[-1], cv2.VideoWriter):
292 | self.vid_writer[-1].release() # 释放最后的视频写入器
293 | self.yolo2main_status_msg.emit("检测完成")
294 | break
295 |
296 | except Exception as e:
297 | pass
298 | traceback.print_exc()
299 | print(f"Error: {e}")
300 | self.yolo2main_status_msg.emit("%s" % e)
301 |
302 | def inference(self, img, *args, **kwargs):
303 | """Runs inference on a given image using the specified model and arguments."""
304 | visualize = (
305 | increment_path(self.save_dir / Path(self.batch[0][0]).stem, mkdir=True)
306 | if self.args.visualize and (not self.source_type.tensor)
307 | else False
308 | )
309 | return self.model(
310 | img,
311 | augment=self.args.augment,
312 | visualize=visualize,
313 | embed=self.args.embed,
314 | *args,
315 | **kwargs,
316 | )
317 |
318 | def get_annotator(self, img):
319 | return Annotator(
320 | img, line_width=self.args.line_thickness, example=str(self.model.names)
321 | )
322 |
323 | def classify_preprocess(self, img):
324 | """Converts input image to model-compatible data type."""
325 | if not isinstance(img, torch.Tensor):
326 | img = torch.stack([self.transforms(im) for im in img], dim=0)
327 | img = (img if isinstance(img, torch.Tensor) else torch.from_numpy(img)).to(
328 | self.model.device
329 | )
330 | return img.half() if self.model.fp16 else img.float() # uint8 to fp16/32
331 |
332 | def classify_postprocess(self, preds, img, orig_imgs):
333 | """Post-processes predictions to return Results objects."""
334 | if not isinstance(
335 | orig_imgs, list
336 | ): # input images are a torch.Tensor, not a list
337 | orig_imgs = ops.convert_torch2numpy_batch(orig_imgs)
338 |
339 | results = []
340 | for i, pred in enumerate(preds):
341 | orig_img = orig_imgs[i]
342 | img_path = self.batch[0][i]
343 | results.append(
344 | Results(
345 | orig_img=orig_img, path=img_path, names=self.model.names, probs=pred
346 | )
347 | )
348 | return results
349 |
350 | def preprocess(self, img):
351 | not_tensor = not isinstance(img, torch.Tensor)
352 | if not_tensor:
353 | img = np.stack(self.pre_transform(img))
354 | img = img[..., ::-1].transpose(
355 | (0, 3, 1, 2)
356 | ) # BGR to RGB, BHWC to BCHW, (n, 3, h, w)
357 | img = np.ascontiguousarray(img) # contiguous
358 | img = torch.from_numpy(img)
359 |
360 | img = img.to(self.device)
361 | img = img.half() if self.model.fp16 else img.float() # uint8 to fp16/32
362 | if not_tensor:
363 | img /= 255 # 0 - 255 to 0.0 - 1.0
364 | return img
365 |
366 | def postprocess(self, preds, img, orig_img):
367 | ### important
368 | preds = ops.non_max_suppression(
369 | preds,
370 | self.conf_thres,
371 | self.iou_thres,
372 | agnostic=self.args.agnostic_nms,
373 | max_det=self.args.max_det,
374 | classes=self.args.classes,
375 | )
376 |
377 | results = []
378 | for i, pred in enumerate(preds):
379 | orig_img = orig_img[i] if isinstance(orig_img, list) else orig_img
380 | shape = orig_img.shape
381 | pred[:, :4] = ops.scale_boxes(img.shape[2:], pred[:, :4], shape).round()
382 | path, _, _, _ = self.batch
383 | img_path = path[i] if isinstance(path, list) else path
384 | results.append(
385 | Results(
386 | orig_img=orig_img, path=img_path, names=self.model.names, boxes=pred
387 | )
388 | )
389 | return results
390 |
391 | def pose_postprocess(self, preds, img, orig_imgs):
392 | """Return detection results for a given input image or list of images."""
393 | preds = ops.non_max_suppression(
394 | preds,
395 | self.conf_thres,
396 | self.iou_thres,
397 | agnostic=self.args.agnostic_nms,
398 | max_det=self.args.max_det,
399 | classes=self.args.classes,
400 | nc=len(self.model.names),
401 | )
402 |
403 | if not isinstance(
404 | orig_imgs, list
405 | ): # input images are a torch.Tensor, not a list
406 | orig_imgs = ops.convert_torch2numpy_batch(orig_imgs)
407 |
408 | results = []
409 | for i, pred in enumerate(preds):
410 | orig_img = orig_imgs[i]
411 | pred[:, :4] = ops.scale_boxes(
412 | img.shape[2:], pred[:, :4], orig_img.shape
413 | ).round()
414 | pred_kpts = (
415 | pred[:, 6:].view(len(pred), *self.model.kpt_shape)
416 | if len(pred)
417 | else pred[:, 6:]
418 | )
419 | pred_kpts = ops.scale_coords(img.shape[2:], pred_kpts, orig_img.shape)
420 | img_path = self.batch[0][i]
421 | results.append(
422 | Results(
423 | orig_img,
424 | path=img_path,
425 | names=self.model.names,
426 | boxes=pred[:, :6],
427 | keypoints=pred_kpts,
428 | )
429 | )
430 | return results
431 |
432 | def segment_postprocess(self, preds, img, orig_imgs):
433 | """Applies non-max suppression and processes detections for each image in an input batch."""
434 | p = ops.non_max_suppression(
435 | preds[0],
436 | self.conf_thres,
437 | self.iou_thres,
438 | agnostic=self.args.agnostic_nms,
439 | max_det=self.args.max_det,
440 | nc=len(self.model.names),
441 | classes=self.args.classes,
442 | )
443 |
444 | if not isinstance(
445 | orig_imgs, list
446 | ): # input images are a torch.Tensor, not a list
447 | orig_imgs = ops.convert_torch2numpy_batch(orig_imgs)
448 |
449 | results = []
450 | proto = (
451 | preds[1][-1] if len(preds[1]) == 3 else preds[1]
452 | ) # second output is len 3 if pt, but only 1 if exported
453 | for i, pred in enumerate(p):
454 | orig_img = orig_imgs[i]
455 | img_path = self.batch[0][i]
456 | if not len(pred): # save empty boxes
457 | masks = None
458 | elif self.args.retina_masks:
459 | pred[:, :4] = ops.scale_boxes(
460 | img.shape[2:], pred[:, :4], orig_img.shape
461 | )
462 | masks = ops.process_mask_native(
463 | proto[i], pred[:, 6:], pred[:, :4], orig_img.shape[:2]
464 | ) # HWC
465 | else:
466 | masks = ops.process_mask(
467 | proto[i], pred[:, 6:], pred[:, :4], img.shape[2:], upsample=True
468 | ) # HWC
469 | pred[:, :4] = ops.scale_boxes(
470 | img.shape[2:], pred[:, :4], orig_img.shape
471 | )
472 | results.append(
473 | Results(
474 | orig_img,
475 | path=img_path,
476 | names=self.model.names,
477 | boxes=pred[:, :6],
478 | masks=masks,
479 | )
480 | )
481 | return results
482 |
483 | def pre_transform(self, img):
484 | same_shapes = all(x.shape == img[0].shape for x in img)
485 | letterbox = LetterBox(
486 | self.imgsz, auto=same_shapes and self.model.pt, stride=self.model.stride
487 | )
488 | return [letterbox(image=x) for x in img]
489 |
490 | def setup_source(self, source):
491 | self.imgsz = check_imgsz(
492 | self.args.imgsz, stride=self.model.stride, min_dim=2
493 | ) # check image size
494 | self.transforms = (
495 | getattr(self.model.model, "transforms", classify_transforms(self.imgsz[0]))
496 | if self.task == "Classify"
497 | else None
498 | )
499 | self.dataset = load_inference_source(
500 | source=source,
501 | imgsz=self.imgsz,
502 | vid_stride=self.args.vid_stride,
503 | buffer=self.args.stream_buffer,
504 | )
505 | self.source_type = self.dataset.source_type
506 | if not getattr(self, "stream", True) and (
507 | self.dataset.mode == "stream" # streams
508 | or len(self.dataset) > 1000 # images
509 | or any(getattr(self.dataset, "video_flag", [False]))
510 | ): # videos
511 | LOGGER.warning(STREAM_WARNING)
512 | self.vid_path = [None] * self.dataset.bs
513 | self.vid_writer = [None] * self.dataset.bs
514 | self.vid_frame = [None] * self.dataset.bs
515 |
516 | def write_results(self, idx, results, batch):
517 | """Write inference results to a file or directory."""
518 | p, im, _ = batch
519 | log_string = ""
520 | if len(im.shape) == 3:
521 | im = im[None] # expand for batch dim
522 |
523 | if (
524 | self.source_type.webcam
525 | or self.source_type.from_img
526 | or self.source_type.tensor
527 | ): # batch_size >= 1
528 | log_string += f"{idx}: "
529 | frame = self.dataset.count
530 | else:
531 | frame = getattr(self.dataset, "frame", 0)
532 | self.data_path = p
533 | self.txt_path = str(self.save_dir / "labels" / p.stem) + (
534 | "" if self.dataset.mode == "image" else f"_{frame}"
535 | )
536 | # log_string += '%gx%g ' % im.shape[2:] # print string
537 |
538 | result = results[idx]
539 |
540 | if self.task == "Classify":
541 | prob = results[idx].probs
542 | # for c in prob.top5:
543 | # print(c)
544 | else:
545 | det = results[idx].boxes
546 | if len(det) == 0:
547 | return f"{log_string}(no detections), " # if no, send this~~
548 |
549 | for c in det.cls.unique():
550 | n = (det.cls == c).sum() # detections per class
551 | log_string += f"{n}~{self.model.names[int(c)]},"
552 |
553 | if (
554 | self.save_res or self.save_res_cam or self.args.save or self.args.show
555 | ): # Add bbox to image
556 | plot_args = {
557 | "line_width": self.args.line_width,
558 | "boxes": self.args.show_boxes,
559 | "conf": self.args.show_conf,
560 | "labels": self.args.show_labels,
561 | }
562 | if not self.args.retina_masks:
563 | plot_args["im_gpu"] = im[idx]
564 | self.plotted_img = result.plot(**plot_args)
565 | # Write
566 | # if self.save_res_cam:
567 | # result.save(str(self.save_dir / p.name))
568 | if self.save_txt or self.save_txt_cam:
569 | result.save_txt(f"{self.txt_path}.txt", save_conf=self.args.save_conf)
570 | if self.args.save_crop:
571 | result.save_crop(
572 | save_dir=self.save_dir / "crops",
573 | file_name=self.data_path.stem
574 | + ("" if self.dataset.mode == "image" else f"_{frame}"),
575 | )
576 |
577 | return log_string
578 |
579 |
580 | class MainWindow(QMainWindow, Ui_MainWindow):
581 | main2yolo_begin_sgl = Signal() # 主视窗向 YOLO 实例发送执行信号
582 |
583 | def __init__(self, parent=None):
584 | super(MainWindow, self).__init__(parent)
585 |
586 | # 基本介面设置
587 | self.setupUi(self)
588 | self.setAttribute(Qt.WA_TranslucentBackground) # 圆角透明
589 | self.setWindowFlags(Qt.FramelessWindowHint) # 设置窗口标志: 隐藏窗口边框
590 | UIFuncitons.uiDefinitions(self) # 自定义介面定义
591 |
592 | # 初始页面
593 | self.task = ""
594 | self.PageIndex = 1
595 | self.content.setCurrentIndex(self.PageIndex)
596 | self.pushButton_detect.clicked.connect(self.button_detect)
597 | self.pushButton_pose.clicked.connect(self.button_pose)
598 | self.pushButton_classify.clicked.connect(self.button_classify)
599 | self.pushButton_segment.clicked.connect(self.button_segment)
600 | # self.pushButton_track.setEnabled(False)
601 |
602 | self.src_file_button.setEnabled(False)
603 | self.src_cam_button.setEnabled(False)
604 | self.src_rtsp_button.setEnabled(False)
605 | ####################################image or video####################################
606 | # 显示模块阴影
607 | UIFuncitons.shadow_style(self, self.Class_QF, QColor(162, 129, 247))
608 | UIFuncitons.shadow_style(self, self.Target_QF, QColor(251, 157, 139))
609 | UIFuncitons.shadow_style(self, self.Fps_QF, QColor(170, 128, 213))
610 | UIFuncitons.shadow_style(self, self.Model_QF, QColor(64, 186, 193))
611 |
612 | # YOLO-v8 线程
613 | self.yolo_predict = YoloPredictor() # 创建 YOLO 实例
614 | self.select_model = self.model_box.currentText() # 默认模型
615 |
616 | self.yolo_thread = QThread() # 创建 YOLO 线程
617 | self.yolo_predict.yolo2main_pre_img.connect(
618 | lambda x: self.show_image(x, self.pre_video)
619 | )
620 | self.yolo_predict.yolo2main_res_img.connect(
621 | lambda x: self.show_image(x, self.res_video)
622 | )
623 | self.yolo_predict.yolo2main_status_msg.connect(lambda x: self.show_status(x))
624 | self.yolo_predict.yolo2main_fps.connect(lambda x: self.fps_label.setText(x))
625 | self.yolo_predict.yolo2main_class_num.connect(
626 | lambda x: self.Class_num.setText(str(x))
627 | )
628 | self.yolo_predict.yolo2main_target_num.connect(
629 | lambda x: self.Target_num.setText(str(x))
630 | )
631 | self.yolo_predict.yolo2main_progress.connect(
632 | lambda x: self.progress_bar.setValue(x)
633 | )
634 | self.main2yolo_begin_sgl.connect(self.yolo_predict.run)
635 | self.yolo_predict.moveToThread(self.yolo_thread)
636 |
637 | self.Qtimer_ModelBox = QTimer(self) # 定时器: 每 2 秒监控模型文件的变化
638 | self.Qtimer_ModelBox.timeout.connect(self.ModelBoxRefre)
639 | self.Qtimer_ModelBox.start(2000)
640 |
641 | # 模型参数
642 | self.model_box.currentTextChanged.connect(self.change_model)
643 | self.iou_spinbox.valueChanged.connect(
644 | lambda x: self.change_val(x, "iou_spinbox")
645 | ) # iou 文本框
646 | self.iou_slider.valueChanged.connect(
647 | lambda x: self.change_val(x, "iou_slider")
648 | ) # iou 滚动条
649 | self.conf_spinbox.valueChanged.connect(
650 | lambda x: self.change_val(x, "conf_spinbox")
651 | ) # conf 文本框
652 | self.conf_slider.valueChanged.connect(
653 | lambda x: self.change_val(x, "conf_slider")
654 | ) # conf 滚动条
655 | self.speed_spinbox.valueChanged.connect(
656 | lambda x: self.change_val(x, "speed_spinbox")
657 | ) # speed 文本框
658 | self.speed_slider.valueChanged.connect(
659 | lambda x: self.change_val(x, "speed_slider")
660 | ) # speed 滚动条
661 |
662 | # 提示窗口初始化
663 | self.Class_num.setText("--")
664 | self.Target_num.setText("--")
665 | self.fps_label.setText("--")
666 | self.Model_name.setText(self.select_model)
667 |
668 | # 选择检测来源
669 | self.src_file_button.clicked.connect(self.open_src_file) # 选择本地文件
670 | self.src_rtsp_button.clicked.connect(
671 | self.show_status("The function has not yet been implemented.")
672 | ) # 选择 RTSP
673 |
674 | # 开始测试按钮
675 | self.run_button.clicked.connect(self.run_or_continue) # 暂停/开始
676 | self.stop_button.clicked.connect(self.stop) # 终止
677 |
678 | # 其他功能按钮
679 | self.save_res_button.toggled.connect(self.is_save_res) # 保存图片选项
680 | self.save_txt_button.toggled.connect(self.is_save_txt) # 保存标签选项
681 | ####################################image or video####################################
682 |
683 | ####################################camera####################################
684 | self.cam_data = np.array([])
685 | # 显示cam模块阴影
686 | UIFuncitons.shadow_style(self, self.Class_QF_cam, QColor(162, 129, 247))
687 | UIFuncitons.shadow_style(self, self.Target_QF_cam, QColor(251, 157, 139))
688 | UIFuncitons.shadow_style(self, self.Fps_QF_cam, QColor(170, 128, 213))
689 | UIFuncitons.shadow_style(self, self.Model_QF_cam, QColor(64, 186, 193))
690 |
691 | # YOLO-v8-cam线程
692 | self.yolo_predict_cam = YoloPredictor() # 创建 YOLO 实例
693 | self.select_model_cam = self.model_box_cam.currentText() # 默认模型
694 |
695 | self.yolo_thread_cam = QThread() # 创建 YOLO 线程
696 | self.yolo_predict_cam.yolo2main_pre_img.connect(
697 | lambda c: self.cam_show_image(c, self.pre_cam)
698 | )
699 | self.yolo_predict_cam.yolo2main_res_img.connect(
700 | lambda c: self.cam_show_image(c, self.res_cam)
701 | )
702 | self.yolo_predict_cam.yolo2main_status_msg.connect(
703 | lambda c: self.cam_show_status(c)
704 | )
705 | self.yolo_predict_cam.yolo2main_fps.connect(
706 | lambda c: self.fps_label_cam.setText(c)
707 | )
708 | self.yolo_predict_cam.yolo2main_class_num.connect(
709 | lambda c: self.Class_num_cam.setText(str(c))
710 | )
711 | self.yolo_predict_cam.yolo2main_target_num.connect(
712 | lambda c: self.Target_num_cam.setText(str(c))
713 | )
714 | # self.yolo_predict_cam.yolo2main_progress.connect(lambda c: self.progress_bar_cam.setValue(c))
715 | self.yolo_predict_cam.yolo2main_progress.connect(
716 | self.progress_bar_cam.setValue(0)
717 | )
718 | self.main2yolo_begin_sgl.connect(self.yolo_predict_cam.run)
719 | self.yolo_predict_cam.moveToThread(self.yolo_thread_cam)
720 |
721 | self.Qtimer_ModelBox_cam = QTimer(self) # 定时器: 每 2 秒监控模型文件的变化
722 | self.Qtimer_ModelBox_cam.timeout.connect(self.ModelBoxRefre)
723 | self.Qtimer_ModelBox_cam.start(2000)
724 |
725 | # cam模型参数
726 | self.model_box_cam.currentTextChanged.connect(self.cam_change_model)
727 | self.iou_spinbox_cam.valueChanged.connect(
728 | lambda c: self.cam_change_val(c, "iou_spinbox_cam")
729 | ) # iou 文本框
730 | self.iou_slider_cam.valueChanged.connect(
731 | lambda c: self.cam_change_val(c, "iou_slider_cam")
732 | ) # iou 滚动条
733 | self.conf_spinbox_cam.valueChanged.connect(
734 | lambda c: self.cam_change_val(c, "conf_spinbox_cam")
735 | ) # conf 文本框
736 | self.conf_slider_cam.valueChanged.connect(
737 | lambda c: self.cam_change_val(c, "conf_slider_cam")
738 | ) # conf 滚动条
739 | self.speed_spinbox_cam.valueChanged.connect(
740 | lambda c: self.cam_change_val(c, "speed_spinbox_cam")
741 | ) # speed 文本框
742 | self.speed_slider_cam.valueChanged.connect(
743 | lambda c: self.cam_change_val(c, "speed_slider_cam")
744 | ) # speed 滚动条
745 |
746 | # 提示窗口初始化
747 | self.Class_num_cam.setText("--")
748 | self.Target_num_cam.setText("--")
749 | self.fps_label_cam.setText("--")
750 | self.Model_name_cam.setText(self.select_model_cam)
751 |
752 | # 选择检测来源
753 | self.src_cam_button.clicked.connect(self.cam_button) # 选择摄像机
754 |
755 | # 开始测试按钮
756 | self.run_button_cam.clicked.connect(self.cam_run_or_continue) # 暂停/开始
757 | self.stop_button_cam.clicked.connect(self.cam_stop) # 终止
758 |
759 | # 其他功能按钮
760 | self.save_res_button_cam.toggled.connect(self.cam_is_save_res) # 保存图片选项
761 | self.save_txt_button_cam.toggled.connect(self.cam_is_save_txt) # 保存标签选项
762 | ####################################camera####################################
763 |
764 | self.ToggleBotton.clicked.connect(
765 | lambda: UIFuncitons.toggleMenu(self, True)
766 | ) # 左侧导航按钮
767 |
768 | # 初始化
769 | self.load_config()
770 |
771 | def button_classify(self): # 触发button_detect后的事件
772 | self.task = "Classify"
773 | self.yolo_predict.task = self.task
774 | self.yolo_predict_cam.task = self.task
775 |
776 | self.content.setCurrentIndex(0)
777 | self.src_file_button.setEnabled(True)
778 | self.src_cam_button.setEnabled(True)
779 | self.src_rtsp_button.setEnabled(True)
780 | self.settings_button.clicked.connect(
781 | lambda: UIFuncitons.settingBox(self, True)
782 | ) # 右上方设置按钮
783 |
784 | # 读取模型文件夹
785 | self.pt_list = os.listdir("./models/classify/")
786 | self.pt_list = [
787 | file for file in self.pt_list if file.endswith((".pt", "onnx", "engine"))
788 | ]
789 | self.pt_list.sort(
790 | key=lambda x: os.path.getsize("./models/classify/" + x)
791 | ) # 按文件大小排序
792 | self.model_box.clear()
793 | self.model_box.addItems(self.pt_list)
794 | self.yolo_predict.new_model_name = "./models/classify/%s" % self.select_model
795 | self.yolo_predict_cam.new_model_name = (
796 | "./models/classify/%s" % self.select_model_cam
797 | )
798 |
799 | # 读取cam模型文件夹
800 | self.pt_list_cam = os.listdir("./models/classify/")
801 | self.pt_list_cam = [
802 | file
803 | for file in self.pt_list_cam
804 | if file.endswith((".pt", "onnx", "engine"))
805 | ]
806 | self.pt_list_cam.sort(
807 | key=lambda x: os.path.getsize("./models/classify/" + x)
808 | ) # 按文件大小排序
809 | self.model_box_cam.clear()
810 | self.model_box_cam.addItems(self.pt_list_cam)
811 | self.show_status("目前页面:image or video检测页面,Mode:Classify")
812 |
813 | def button_detect(self): # 触发button_detect后的事件
814 | self.task = "Detect"
815 | self.yolo_predict.task = self.task
816 | self.yolo_predict_cam.task = self.task
817 | self.yolo_predict.new_model_name = "./models/detect/%s" % self.select_model
818 | self.yolo_predict_cam.new_model_name = (
819 | "./models/detect/%s" % self.select_model_cam
820 | )
821 | self.content.setCurrentIndex(0)
822 | self.src_file_button.setEnabled(True)
823 | self.src_cam_button.setEnabled(True)
824 | self.src_rtsp_button.setEnabled(True)
825 | self.settings_button.clicked.connect(
826 | lambda: UIFuncitons.settingBox(self, True)
827 | ) # 右上方设置按钮
828 |
829 | # 读取模型文件夹
830 | self.pt_list = os.listdir("./models/detect/")
831 | self.pt_list = [
832 | file for file in self.pt_list if file.endswith((".pt", "onnx", "engine"))
833 | ]
834 | self.pt_list.sort(
835 | key=lambda x: os.path.getsize("./models/detect/" + x)
836 | ) # 按文件大小排序
837 | self.model_box.clear()
838 | self.model_box.addItems(self.pt_list)
839 | self.yolo_predict.new_model_name = "./models/detect/%s" % self.select_model
840 | self.yolo_predict_cam.new_model_name = (
841 | "./models/detect/%s" % self.select_model_cam
842 | )
843 |
844 | # 读取cam模型文件夹
845 | self.pt_list_cam = os.listdir("./models/detect/")
846 | self.pt_list_cam = [
847 | file
848 | for file in self.pt_list_cam
849 | if file.endswith((".pt", "onnx", "engine"))
850 | ]
851 | self.pt_list_cam.sort(
852 | key=lambda x: os.path.getsize("./models/detect/" + x)
853 | ) # 按文件大小排序
854 | self.model_box_cam.clear()
855 | self.model_box_cam.addItems(self.pt_list_cam)
856 | self.show_status("目前页面:image or video检测页面,Mode:Detect")
857 |
858 | def button_pose(self): # 触发button_detect后的事件
859 | self.task = "Pose"
860 | self.yolo_predict.task = self.task
861 | self.yolo_predict_cam.task = self.task
862 | self.yolo_predict.new_model_name = "./models/pose/%s" % self.select_model
863 | self.yolo_predict_cam.new_model_name = (
864 | "./models/pose/%s" % self.select_model_cam
865 | )
866 | self.content.setCurrentIndex(0)
867 | self.src_file_button.setEnabled(True)
868 | self.src_cam_button.setEnabled(True)
869 | self.src_rtsp_button.setEnabled(True)
870 | self.settings_button.clicked.connect(
871 | lambda: UIFuncitons.settingBox(self, True)
872 | ) # 右上方设置按钮
873 |
874 | # 读取模型文件夹
875 | self.pt_list = os.listdir("./models/pose/")
876 | self.pt_list = [
877 | file for file in self.pt_list if file.endswith((".pt", "onnx", "engine"))
878 | ]
879 | self.pt_list.sort(
880 | key=lambda x: os.path.getsize("./models/pose/" + x)
881 | ) # 按文件大小排序
882 | self.model_box.clear()
883 | self.model_box.addItems(self.pt_list)
884 | self.yolo_predict.new_model_name = "./models/pose/%s" % self.select_model
885 | self.yolo_predict_cam.new_model_name = (
886 | "./models/pose/%s" % self.select_model_cam
887 | )
888 |
889 | # 读取cam模型文件夹
890 | self.pt_list_cam = os.listdir("./models/pose/")
891 | self.pt_list_cam = [
892 | file
893 | for file in self.pt_list_cam
894 | if file.endswith((".pt", "onnx", "engine"))
895 | ]
896 | self.pt_list_cam.sort(
897 | key=lambda x: os.path.getsize("./models/pose/" + x)
898 | ) # 按文件大小排序
899 | self.model_box_cam.clear()
900 | self.model_box_cam.addItems(self.pt_list_cam)
901 | self.show_status("目前页面:image or video检测页面,Mode:Pose")
902 |
903 | def button_segment(self): # 触发button_detect后的事件
904 | self.task = "Segment"
905 | self.yolo_predict.task = self.task
906 | self.yolo_predict_cam.task = self.task
907 | self.yolo_predict.new_model_name = "./models/segment/%s" % self.select_model
908 | self.yolo_predict_cam.new_model_name = (
909 | "./models/segment/%s" % self.select_model_cam
910 | )
911 | self.content.setCurrentIndex(0)
912 | self.src_file_button.setEnabled(True)
913 | self.src_cam_button.setEnabled(False)
914 | self.src_rtsp_button.setEnabled(True)
915 | self.settings_button.clicked.connect(
916 | lambda: UIFuncitons.settingBox(self, True)
917 | ) # 右上方设置按钮
918 |
919 | # 读取模型文件夹
920 | self.pt_list = os.listdir("./models/segment/")
921 | self.pt_list = [
922 | file for file in self.pt_list if file.endswith((".pt", "onnx", "engine"))
923 | ]
924 | self.pt_list.sort(
925 | key=lambda x: os.path.getsize("./models/segment/" + x)
926 | ) # 按文件大小排序
927 | self.model_box.clear()
928 | self.model_box.addItems(self.pt_list)
929 | self.yolo_predict.new_model_name = "./models/segment/%s" % self.select_model
930 | self.yolo_predict_cam.new_model_name = (
931 | "./models/segment/%s" % self.select_model_cam
932 | )
933 |
934 | # 读取cam模型文件夹
935 | self.pt_list_cam = os.listdir("./models/segment/")
936 | self.pt_list_cam = [
937 | file
938 | for file in self.pt_list_cam
939 | if file.endswith((".pt", "onnx", "engine"))
940 | ]
941 | self.pt_list_cam.sort(
942 | key=lambda x: os.path.getsize("./models/segment/" + x)
943 | ) # 按文件大小排序
944 | self.model_box_cam.clear()
945 | self.model_box_cam.addItems(self.pt_list_cam)
946 | self.show_status("目前页面:image or video检测页面,Mode:Segment")
947 |
948 | def button_track(self): # 触发button_detect后的事件
949 | self.task = "Track"
950 | self.yolo_predict.task = self.task
951 | self.yolo_predict_cam.task = self.task
952 | self.yolo_predict.new_model_name = "./models/track/%s" % self.select_model
953 | self.yolo_predict_cam.new_model_name = (
954 | "./models/track/%s" % self.select_model_cam
955 | )
956 | self.content.setCurrentIndex(0)
957 | self.src_file_button.setEnabled(True)
958 | self.src_cam_button.setEnabled(True)
959 | self.src_rtsp_button.setEnabled(True)
960 | self.settings_button.clicked.connect(
961 | lambda: UIFuncitons.settingBox(self, True)
962 | ) # 右上方设置按钮
963 |
964 | # 读取模型文件夹
965 | self.pt_list = os.listdir("./models/track/")
966 | self.pt_list = [
967 | file for file in self.pt_list if file.endswith((".pt", "onnx", "engine"))
968 | ]
969 | self.pt_list.sort(
970 | key=lambda x: os.path.getsize("./models/track/" + x)
971 | ) # 按文件大小排序
972 | self.model_box.clear()
973 | self.model_box.addItems(self.pt_list)
974 | self.yolo_predict.new_model_name = "./models/track/%s" % self.select_model
975 | self.yolo_predict_cam.new_model_name = (
976 | "./models/track/%s" % self.select_model_cam
977 | )
978 |
979 | # 读取cam模型文件夹
980 | self.pt_list_cam = os.listdir("./models/track/")
981 | self.pt_list_cam = [
982 | file
983 | for file in self.pt_list_cam
984 | if file.endswith((".pt", "onnx", "engine"))
985 | ]
986 | self.pt_list_cam.sort(
987 | key=lambda x: os.path.getsize("./models/track/" + x)
988 | ) # 按文件大小排序
989 | self.model_box_cam.clear()
990 | self.model_box_cam.addItems(self.pt_list_cam)
991 | self.show_status("目前页面:image or video检测页面,Mode:Track")
992 |
993 | ####################################image or video####################################
994 | # 选择本地档案
995 | def open_src_file(self):
996 | if self.task == "Classify":
997 | self.show_status("目前页面:image or video检测页面,Mode:Classify")
998 | if self.task == "Detect":
999 | self.show_status("目前页面:image or video检测页面,Mode:Detect")
1000 | if self.task == "Pose":
1001 | self.show_status("目前页面:image or video检测页面,Mode:Pose")
1002 | if self.task == "Segment":
1003 | self.show_status("目前页面:image or video检测页面,Mode:Segment")
1004 | if self.task == "Track":
1005 | self.show_status("目前页面:image or video检测页面,Mode:Track")
1006 |
1007 | # 结束cam线程,节省资源
1008 | if self.yolo_thread_cam.isRunning():
1009 | self.yolo_thread_cam.quit() # 结束线程
1010 | self.cam_stop()
1011 | # 0:image/video page
1012 | # 1:home page
1013 | # 2:camera page
1014 | if self.PageIndex != 0:
1015 | self.PageIndex = 0
1016 | self.content.setCurrentIndex(self.PageIndex)
1017 | self.settings_button.clicked.connect(
1018 | lambda: UIFuncitons.settingBox(self, True)
1019 | ) # 右上方设置按钮
1020 |
1021 | if self.PageIndex == 0:
1022 | # 设置配置档路径
1023 | config_file = "config/fold.json"
1024 |
1025 | # 读取配置档内容
1026 | config = json.load(open(config_file, "r", encoding="utf-8"))
1027 |
1028 | # 获取上次打开的资料夹路径
1029 | open_fold = config["open_fold"]
1030 |
1031 | # 如果上次打开的资料夹不存在,则使用当前工作目录
1032 | if not os.path.exists(open_fold):
1033 | open_fold = os.getcwd()
1034 |
1035 | # 通过文件对话框让用户选择图片或影片档案
1036 | if self.task == "Track":
1037 | name, _ = QFileDialog.getOpenFileName(
1038 | self, "Video", open_fold, "Pic File(*.mp4 *.mkv *.avi *.flv)"
1039 | )
1040 | else:
1041 | name, _ = QFileDialog.getOpenFileName(
1042 | self,
1043 | "Video/image",
1044 | open_fold,
1045 | "Pic File(*.mp4 *.mkv *.avi *.flv *.jpg *.png)",
1046 | )
1047 |
1048 | # 如果用户选择了档案
1049 | if name:
1050 | # 将所选档案的路径设置为 yolo_predict 的 source
1051 | self.yolo_predict.source = name
1052 |
1053 | # 显示档案载入状态
1054 | self.show_status("载入档案:{}".format(os.path.basename(name)))
1055 |
1056 | # 更新配置档中的上次打开的资料夹路径
1057 | config["open_fold"] = os.path.dirname(name)
1058 |
1059 | # 将更新后的配置档写回到档案中
1060 | config_json = json.dumps(config, ensure_ascii=False, indent=2)
1061 | with open(config_file, "w", encoding="utf-8") as f:
1062 | f.write(config_json)
1063 |
1064 | # 停止检测
1065 | self.stop()
1066 |
1067 | # 主视窗显示原始图片和检测结果
1068 | @staticmethod
1069 | def show_image(img_src, label):
1070 | try:
1071 | # 获取原始图片的高度、宽度和通道数
1072 | ih, iw, _ = img_src.shape
1073 |
1074 | # 获取标签(label)的宽度和高度
1075 | w = label.geometry().width()
1076 | h = label.geometry().height()
1077 |
1078 | # 保持原始数据比例
1079 | if iw / w > ih / h:
1080 | scal = w / iw
1081 | nw = w
1082 | nh = int(scal * ih)
1083 | img_src_ = cv2.resize(img_src, (nw, nh))
1084 | else:
1085 | scal = h / ih
1086 | nw = int(scal * iw)
1087 | nh = h
1088 | img_src_ = cv2.resize(img_src, (nw, nh))
1089 |
1090 | # 将图片转换为RGB格式
1091 | frame = cv2.cvtColor(img_src_, cv2.COLOR_BGR2RGB)
1092 |
1093 | # 将图片数据转换为Qt的图片对象
1094 | img = QImage(
1095 | frame.data,
1096 | frame.shape[1],
1097 | frame.shape[0],
1098 | frame.shape[2] * frame.shape[1],
1099 | QImage.Format_RGB888,
1100 | )
1101 |
1102 | # 将图片显示在标签(label)上
1103 | label.setPixmap(QPixmap.fromImage(img))
1104 |
1105 | except Exception as e:
1106 | # 处理异常,印出错误信息
1107 | print(repr(e))
1108 |
1109 | # 控制开始/暂停检测
1110 | def run_or_continue(self):
1111 | # 检查 YOLO 预测的来源是否为空
1112 | if self.yolo_predict.source == "":
1113 | self.show_status("开始侦测前请选择图片或影片来源...")
1114 | self.run_button.setChecked(False)
1115 | else:
1116 | # 设置 YOLO 预测的停止标志为 False
1117 | self.yolo_predict.stop_dtc = False
1118 |
1119 | # 如果开始按钮被勾选
1120 | if self.run_button.isChecked():
1121 | self.run_button.setChecked(True) # 启动按钮
1122 | self.save_txt_button.setEnabled(False) # 启动检测后禁止勾选保存
1123 | self.save_res_button.setEnabled(False)
1124 | self.show_status("检测中...")
1125 | self.yolo_predict.continue_dtc = True # 控制 YOLO 是否暂停
1126 | if not self.yolo_thread.isRunning():
1127 | self.yolo_thread.start()
1128 | self.main2yolo_begin_sgl.emit()
1129 |
1130 | # 如果开始按钮未被勾选,表示暂停检测
1131 | else:
1132 | self.yolo_predict.continue_dtc = False
1133 | self.show_status("检测暂停...")
1134 | self.run_button.setChecked(False) # 停止按钮
1135 |
1136 | # 显示底部状态栏信息
1137 | def show_status(self, msg):
1138 | # 设置状态栏文字
1139 | self.status_bar.setText(msg)
1140 |
1141 | # 根据不同的状态信息执行相应的操作
1142 | if msg == "Detection completed" or msg == "检测完成":
1143 | # 启用保存结果和保存文本的按钮
1144 | self.save_res_button.setEnabled(True)
1145 | self.save_txt_button.setEnabled(True)
1146 |
1147 | # 将检测开关按钮设置为未勾选状态
1148 | self.run_button.setChecked(False)
1149 |
1150 | # 将进度条的值设置为0
1151 | self.progress_bar.setValue(0)
1152 |
1153 | # 如果 YOLO 线程正在运行,则终止该线程
1154 | if self.yolo_thread.isRunning():
1155 | self.yolo_thread.quit() # 结束处理
1156 |
1157 | elif msg == "Detection terminated!" or msg == "检测终止":
1158 | # 启用保存结果和保存文本的按钮
1159 | self.save_res_button.setEnabled(True)
1160 | self.save_txt_button.setEnabled(True)
1161 |
1162 | # 将检测开关按钮设置为未勾选状态
1163 | self.run_button.setChecked(False)
1164 |
1165 | # 将进度条的值设置为0
1166 | self.progress_bar.setValue(0)
1167 |
1168 | # 如果 YOLO 线程正在运行,则终止该线程
1169 | if self.yolo_thread.isRunning():
1170 | self.yolo_thread.quit() # 结束处理
1171 |
1172 | # 清空影像显示
1173 | self.pre_video.clear() # 清除原始图像
1174 | self.res_video.clear() # 清除检测结果图像
1175 | self.Class_num.setText("--") # 显示的类别数目
1176 | self.Target_num.setText("--") # 显示的目标数目
1177 | self.fps_label.setText("--") # 显示的帧率信息
1178 |
1179 | # 保存测试结果按钮 -- 图片/视频
1180 | def is_save_res(self):
1181 | if self.save_res_button.checkState() == Qt.CheckState.Unchecked:
1182 | # 显示消息,提示运行图片结果不会保存
1183 | self.show_status("NOTE: Run image results are not saved.")
1184 |
1185 | # 将 YOLO 实例的保存结果的标志设置为 False
1186 | self.yolo_predict.save_res = False
1187 | elif self.save_res_button.checkState() == Qt.CheckState.Checked:
1188 | # 显示消息,提示运行图片结果将会保存
1189 | self.show_status("NOTE: Run image results will be saved.")
1190 |
1191 | # 将 YOLO 实例的保存结果的标志设置为 True
1192 | self.yolo_predict.save_res = True
1193 |
1194 | # 保存测试结果按钮 -- 标签(txt)
1195 | def is_save_txt(self):
1196 | if self.save_txt_button.checkState() == Qt.CheckState.Unchecked:
1197 | # 显示消息,提示标签结果不会保存
1198 | self.show_status("NOTE: Labels results are not saved.")
1199 |
1200 | # 将 YOLO 实例的保存标签的标志设置为 False
1201 | self.yolo_predict.save_txt = False
1202 | elif self.save_txt_button.checkState() == Qt.CheckState.Checked:
1203 | # 显示消息,提示标签结果将会保存
1204 | self.show_status("NOTE: Labels results will be saved.")
1205 |
1206 | # 将 YOLO 实例的保存标签的标志设置为 True
1207 | self.yolo_predict.save_txt = True
1208 |
1209 | # 终止按钮及相关状态处理
1210 | def stop(self):
1211 | # 如果 YOLO 线程正在运行,则终止线程
1212 | if self.yolo_thread.isRunning():
1213 | self.yolo_thread.quit() # 结束线程
1214 |
1215 | # 设置 YOLO 实例的终止标志为 True
1216 | self.yolo_predict.stop_dtc = True
1217 |
1218 | # 恢复开始按钮的状态
1219 | self.run_button.setChecked(False)
1220 |
1221 | # 启用保存按钮的使用权限
1222 | if self.task == "Classify":
1223 | self.save_res_button.setEnabled(False)
1224 | self.save_txt_button.setEnabled(False)
1225 | else:
1226 | self.save_res_button.setEnabled(True)
1227 | self.save_txt_button.setEnabled(True)
1228 |
1229 | # 清空预测结果显示区域的影象
1230 | self.pre_video.clear()
1231 |
1232 | # 清空检测结果显示区域的影象
1233 | self.res_video.clear()
1234 |
1235 | # 将进度条的值设置为0
1236 | self.progress_bar.setValue(0)
1237 |
1238 | # 重置类别数量、目标数量和fps标签
1239 | self.Class_num.setText("--")
1240 | self.Target_num.setText("--")
1241 | self.fps_label.setText("--")
1242 |
1243 | # 更改检测参数
1244 | def change_val(self, x, flag):
1245 | if flag == "iou_spinbox":
1246 | # 如果是 iou_spinbox 的值发生变化,则改变 iou_slider 的值
1247 | self.iou_slider.setValue(int(x * 100))
1248 |
1249 | elif flag == "iou_slider":
1250 | # 如果是 iou_slider 的值发生变化,则改变 iou_spinbox 的值
1251 | self.iou_spinbox.setValue(x / 100)
1252 | # 显示消息,提示 IOU 阈值变化
1253 | self.show_status("IOU Threshold: %s" % str(x / 100))
1254 | # 设置 YOLO 实例的 IOU 阈值
1255 | self.yolo_predict.iou_thres = x / 100
1256 |
1257 | elif flag == "conf_spinbox":
1258 | # 如果是 conf_spinbox 的值发生变化,则改变 conf_slider 的值
1259 | self.conf_slider.setValue(int(x * 100))
1260 |
1261 | elif flag == "conf_slider":
1262 | # 如果是 conf_slider 的值发生变化,则改变 conf_spinbox 的值
1263 | self.conf_spinbox.setValue(x / 100)
1264 | # 显示消息,提示 Confidence 阈值变化
1265 | self.show_status("Conf Threshold: %s" % str(x / 100))
1266 | # 设置 YOLO 实例的 Confidence 阈值
1267 | self.yolo_predict.conf_thres = x / 100
1268 |
1269 | elif flag == "speed_spinbox":
1270 | # 如果是 speed_spinbox 的值发生变化,则改变 speed_slider 的值
1271 | self.speed_slider.setValue(x)
1272 |
1273 | elif flag == "speed_slider":
1274 | # 如果是 speed_slider 的值发生变化,则改变 speed_spinbox 的值
1275 | self.speed_spinbox.setValue(x)
1276 | # 显示消息,提示延迟时间变化
1277 | self.show_status("Delay: %s ms" % str(x))
1278 | # 设置 YOLO 实例的延迟时间阈值
1279 | self.yolo_predict.speed_thres = x # 毫秒
1280 |
1281 | # 更改模型
1282 | def change_model(self, x):
1283 | # 获取当前选择的模型名称
1284 | self.select_model = self.model_box.currentText()
1285 |
1286 | # 设置 YOLO 实例的新模型名称
1287 | if self.task == "Classify":
1288 | self.yolo_predict.new_model_name = (
1289 | "./models/classify/%s" % self.select_model
1290 | )
1291 | elif self.task == "Detect":
1292 | self.yolo_predict.new_model_name = "./models/detect/%s" % self.select_model
1293 | elif self.task == "Pose":
1294 | self.yolo_predict.new_model_name = "./models/pose/%s" % self.select_model
1295 | elif self.task == "Segment":
1296 | self.yolo_predict.new_model_name = "./models/segment/%s" % self.select_model
1297 | elif self.task == "Track":
1298 | self.yolo_predict.new_model_name = "./models/track/%s" % self.select_model
1299 | # 显示消息,提示模型已更改
1300 | self.show_status("Change Model:%s" % self.select_model)
1301 |
1302 | # 在界面上显示新的模型名称
1303 | self.Model_name.setText(self.select_model)
1304 |
1305 | ####################################image or video####################################
1306 |
1307 | ####################################camera####################################
1308 | def cam_button(self):
1309 | self.yolo_predict_cam.source = 0
1310 | self.show_status("目前页面:Webcam检测页面")
1311 | # 结束image or video线程,节省资源
1312 | if self.yolo_thread.isRunning():
1313 | self.yolo_thread.quit() # 结束线程
1314 | self.stop()
1315 |
1316 | if self.PageIndex != 2:
1317 | self.PageIndex = 2
1318 | self.content.setCurrentIndex(self.PageIndex)
1319 | self.settings_button.clicked.connect(
1320 | lambda: UIFuncitons.cam_settingBox(self, True)
1321 | ) # 右上方设置按钮
1322 |
1323 | # cam控制开始/暂停检测
1324 | def cam_run_or_continue(self):
1325 | if self.yolo_predict_cam.source == "":
1326 | self.show_status("并未检测到摄影机")
1327 | self.run_button_cam.setChecked(False)
1328 |
1329 | else:
1330 | # 设置 YOLO 预测的停止标志为 False
1331 | self.yolo_predict_cam.stop_dtc = False
1332 |
1333 | # 如果开始按钮被勾选
1334 | if self.run_button_cam.isChecked():
1335 | self.run_button_cam.setChecked(True) # 启动按钮
1336 | self.save_txt_button_cam.setEnabled(False) # 启动检测后禁止勾选保存
1337 | self.save_res_button_cam.setEnabled(False)
1338 | self.cam_show_status("检测中...")
1339 | self.yolo_predict_cam.continue_dtc = True # 控制 YOLO 是否暂停
1340 |
1341 | if not self.yolo_thread_cam.isRunning():
1342 | self.yolo_thread_cam.start()
1343 | self.main2yolo_begin_sgl.emit()
1344 |
1345 | # 如果开始按钮未被勾选,表示暂停检测
1346 | else:
1347 | self.yolo_predict_cam.continue_dtc = False
1348 | self.cam_show_status("检测暂停...")
1349 | self.run_button_cam.setChecked(False) # 停止按钮
1350 |
1351 | # cam主视窗显示原始图片和检测结果
1352 | @staticmethod
1353 | def cam_show_image(img_src, label):
1354 | try:
1355 | # 获取原始图片的高度、宽度和通道数
1356 | ih, iw, _ = img_src.shape
1357 |
1358 | # 获取标签(label)的宽度和高度
1359 | w = label.geometry().width()
1360 | h = label.geometry().height()
1361 |
1362 | # 保持原始数据比例
1363 | if iw / w > ih / h:
1364 | scal = w / iw
1365 | nw = w
1366 | nh = int(scal * ih)
1367 | img_src_ = cv2.resize(img_src, (nw, nh))
1368 | else:
1369 | scal = h / ih
1370 | nw = int(scal * iw)
1371 | nh = h
1372 | img_src_ = cv2.resize(img_src, (nw, nh))
1373 |
1374 | # 将图片转换为RGB格式
1375 | frame = cv2.cvtColor(img_src_, cv2.COLOR_BGR2RGB)
1376 |
1377 | # 将图片数据转换为Qt的图片对象
1378 | img = QImage(
1379 | frame.data,
1380 | frame.shape[1],
1381 | frame.shape[0],
1382 | frame.shape[2] * frame.shape[1],
1383 | QImage.Format_RGB888,
1384 | )
1385 |
1386 | # 将图片显示在标签(label)上
1387 | label.setPixmap(QPixmap.fromImage(img))
1388 |
1389 | except Exception as e:
1390 | # 处理异常,印出错误信息
1391 | traceback.print_exc()
1392 | print(f"Error: {e}")
1393 | self.cam_show_status("%s" % e)
1394 |
1395 | # 更改检测参数
1396 | def cam_change_val(self, c, flag):
1397 | if flag == "iou_spinbox_cam":
1398 | # 如果是 iou_spinbox 的值发生变化,则改变 iou_slider 的值
1399 | self.iou_slider_cam.setValue(int(c * 100))
1400 |
1401 | elif flag == "iou_slider_cam":
1402 | # 如果是 iou_slider 的值发生变化,则改变 iou_spinbox 的值
1403 | self.iou_spinbox_cam.setValue(c / 100)
1404 | # 显示消息,提示 IOU 阈值变化
1405 | self.cam_show_status("IOU Threshold: %s" % str(c / 100))
1406 | # 设置 YOLO 实例的 IOU 阈值
1407 | self.yolo_predict_cam.iou_thres = c / 100
1408 |
1409 | elif flag == "conf_spinbox_cam":
1410 | # 如果是 conf_spinbox 的值发生变化,则改变 conf_slider 的值
1411 | self.conf_slider_cam.setValue(int(c * 100))
1412 |
1413 | elif flag == "conf_slider_cam":
1414 | # 如果是 conf_slider 的值发生变化,则改变 conf_spinbox 的值
1415 | self.conf_spinbox_cam.setValue(c / 100)
1416 | # 显示消息,提示 Confidence 阈值变化
1417 | self.cam_show_status("Conf Threshold: %s" % str(c / 100))
1418 | # 设置 YOLO 实例的 Confidence 阈值
1419 | self.yolo_predict_cam.conf_thres = c / 100
1420 |
1421 | elif flag == "speed_spinbox_cam":
1422 | # 如果是 speed_spinbox 的值发生变化,则改变 speed_slider 的值
1423 | self.speed_slider_cam.setValue(c)
1424 |
1425 | elif flag == "speed_slider_cam":
1426 | # 如果是 speed_slider 的值发生变化,则改变 speed_spinbox 的值
1427 | self.speed_spinbox_cam.setValue(c)
1428 | # 显示消息,提示延迟时间变化
1429 | self.cam_show_status("Delay: %s ms" % str(c))
1430 | # 设置 YOLO 实例的延迟时间阈值
1431 | self.yolo_predict_cam.speed_thres = c # 毫秒
1432 |
1433 | # 更改模型
1434 | def cam_change_model(self, c):
1435 | # 获取当前选择的模型名称
1436 | self.select_model_cam = self.model_box_cam.currentText()
1437 |
1438 | # 设置 YOLO 实例的新模型名称
1439 | if self.task == "Classify":
1440 | self.yolo_predict_cam.new_model_name = (
1441 | "./models/classify/%s" % self.select_model_cam
1442 | )
1443 | elif self.task == "Detect":
1444 | self.yolo_predict_cam.new_model_name = (
1445 | "./models/detect/%s" % self.select_model_cam
1446 | )
1447 | elif self.task == "Pose":
1448 | self.yolo_predict_cam.new_model_name = (
1449 | "./models/pose/%s" % self.select_model_cam
1450 | )
1451 | elif self.task == "Segment":
1452 | self.yolo_predict_cam.new_model_name = (
1453 | "./models/segment/%s" % self.select_model_cam
1454 | )
1455 | elif self.task == "Track":
1456 | self.yolo_predict_cam.new_model_name = (
1457 | "./models/track/%s" % self.select_model_cam
1458 | )
1459 | # 显示消息,提示模型已更改
1460 | self.cam_show_status("Change Model:%s" % self.select_model_cam)
1461 |
1462 | # 在界面上显示新的模型名称
1463 | self.Model_name_cam.setText(self.select_model_cam)
1464 |
1465 | # 显示底部状态栏信息
1466 | def cam_show_status(self, msg):
1467 | # 设置状态栏文字
1468 | self.status_bar.setText(msg)
1469 |
1470 | # 根据不同的状态信息执行相应的操作
1471 | if msg == "Detection completed" or msg == "检测完成":
1472 | # 启用保存结果和保存文本的按钮
1473 | self.save_res_button_cam.setEnabled(True)
1474 | self.save_txt_button_cam.setEnabled(True)
1475 |
1476 | # 将检测开关按钮设置为未勾选状态
1477 | self.run_button_cam.setChecked(False)
1478 |
1479 | # 将进度条的值设置为0
1480 | self.progress_bar_cam.setValue(0)
1481 |
1482 | # 如果 YOLO 线程正在运行,则终止该线程
1483 | if self.yolo_thread_cam.isRunning():
1484 | self.yolo_thread_cam.quit() # 结束处理
1485 |
1486 | elif msg == "Detection terminated!" or msg == "检测终止":
1487 | # 启用保存结果和保存文本的按钮
1488 | self.save_res_button_cam.setEnabled(True)
1489 | self.save_txt_button_cam.setEnabled(True)
1490 |
1491 | # 将检测开关按钮设置为未勾选状态
1492 | self.run_button_cam.setChecked(False)
1493 |
1494 | # 将进度条的值设置为0
1495 | self.progress_bar_cam.setValue(0)
1496 |
1497 | # 如果 YOLO 线程正在运行,则终止该线程
1498 | if self.yolo_thread_cam.isRunning():
1499 | self.yolo_thread_cam.quit() # 结束处理
1500 |
1501 | # 清空影像显示
1502 | self.pre_cam.clear() # 清除原始图像
1503 | self.res_cam.clear() # 清除检测结果图像
1504 | self.Class_num_cam.setText("--") # 显示的类别数目
1505 | self.Target_num_cam.setText("--") # 显示的目标数目
1506 | self.fps_label_cam.setText("--") # 显示的帧率信息
1507 |
1508 | # 保存测试结果按钮 -- 图片/视频
1509 | def cam_is_save_res(self):
1510 | if self.save_res_button_cam.checkState() == Qt.CheckState.Unchecked:
1511 | # 显示消息,提示运行图片结果不会保存
1512 | self.show_status("NOTE:运行图片结果不会保存")
1513 |
1514 | # 将 YOLO 实例的保存结果的标志设置为 False
1515 | self.yolo_thread_cam.save_res = False
1516 | elif self.save_res_button_cam.checkState() == Qt.CheckState.Checked:
1517 | # 显示消息,提示运行图片结果将会保存
1518 | self.show_status("NOTE:运行图片结果将会保存")
1519 |
1520 | # 将 YOLO 实例的保存结果的标志设置为 True
1521 | self.yolo_thread_cam.save_res = True
1522 |
1523 | # 保存测试结果按钮 -- 标签(txt)
1524 | def cam_is_save_txt(self):
1525 | if self.save_txt_button_cam.checkState() == Qt.CheckState.Unchecked:
1526 | # 显示消息,提示标签结果不会保存
1527 | self.show_status("NOTE:Label结果不会保存")
1528 |
1529 | # 将 YOLO 实例的保存标签的标志设置为 False
1530 | self.yolo_thread_cam.save_txt_cam = False
1531 | elif self.save_txt_button_cam.checkState() == Qt.CheckState.Checked:
1532 | # 显示消息,提示标签结果将会保存
1533 | self.show_status("NOTE:Label结果将会保存")
1534 |
1535 | # 将 YOLO 实例的保存标签的标志设置为 True
1536 | self.yolo_thread_cam.save_txt_cam = True
1537 |
1538 | # cam终止按钮及相关状态处理
1539 | def cam_stop(self):
1540 | # 如果 YOLO 线程正在运行,则终止线程
1541 | if self.yolo_thread_cam.isRunning():
1542 | self.yolo_thread_cam.quit() # 结束线程
1543 |
1544 | # 设置 YOLO 实例的终止标志为 True
1545 | self.yolo_predict_cam.stop_dtc = True
1546 |
1547 | # 恢复开始按钮的状态
1548 | self.run_button_cam.setChecked(False)
1549 |
1550 | # 启用保存按钮的使用权限
1551 | if self.task == "Classify":
1552 | self.save_res_button_cam.setEnabled(False)
1553 | self.save_txt_button_cam.setEnabled(False)
1554 | else:
1555 | self.save_res_button_cam.setEnabled(True)
1556 | self.save_txt_button_cam.setEnabled(True)
1557 |
1558 | # 清空预测结果显示区域的影象
1559 | self.pre_cam.clear()
1560 |
1561 | # 清空检测结果显示区域的影象
1562 | self.res_cam.clear()
1563 |
1564 | # 将进度条的值设置为0
1565 | # self.progress_bar.setValue(0)
1566 |
1567 | # 重置类别数量、目标数量和fps标签
1568 | self.Class_num_cam.setText("--")
1569 | self.Target_num_cam.setText("--")
1570 | self.fps_label_cam.setText("--")
1571 |
1572 | ####################################camera####################################
1573 |
1574 | ####################################共用####################################
1575 | # 循环监控模型文件更改
1576 | def ModelBoxRefre(self):
1577 | # 获取模型文件夹下的所有模型文件
1578 | if self.task == "Classify":
1579 | pt_list = os.listdir("./models/classify")
1580 | pt_list = [
1581 | file for file in pt_list if file.endswith((".pt", "onnx", "engine"))
1582 | ]
1583 | pt_list.sort(key=lambda x: os.path.getsize("./models/classify/" + x))
1584 |
1585 | # 如果模型文件列表发生变化,则更新模型下拉框的内容
1586 | if pt_list != self.pt_list:
1587 | self.pt_list = pt_list
1588 | self.model_box.clear()
1589 | self.model_box.addItems(self.pt_list)
1590 | self.pt_list_cam = pt_list
1591 | self.model_box_cam.clear()
1592 | self.model_box_cam.addItems(self.pt_list_cam)
1593 |
1594 | elif self.task == "Detect":
1595 | pt_list = os.listdir("./models/detect")
1596 | pt_list = [
1597 | file for file in pt_list if file.endswith((".pt", "onnx", "engine"))
1598 | ]
1599 | pt_list.sort(key=lambda x: os.path.getsize("./models/detect/" + x))
1600 | # 如果模型文件列表发生变化,则更新模型下拉框的内容
1601 | if pt_list != self.pt_list:
1602 | self.pt_list = pt_list
1603 | self.model_box.clear()
1604 | self.model_box.addItems(self.pt_list)
1605 | self.pt_list_cam = pt_list
1606 | self.model_box_cam.clear()
1607 | self.model_box_cam.addItems(self.pt_list_cam)
1608 |
1609 | elif self.task == "Pose":
1610 | pt_list = os.listdir("./models/pose")
1611 | pt_list = [
1612 | file for file in pt_list if file.endswith((".pt", "onnx", "engine"))
1613 | ]
1614 | pt_list.sort(key=lambda x: os.path.getsize("./models/pose/" + x))
1615 |
1616 | # 如果模型文件列表发生变化,则更新模型下拉框的内容
1617 | if pt_list != self.pt_list:
1618 | self.pt_list = pt_list
1619 | self.model_box.clear()
1620 | self.model_box.addItems(self.pt_list)
1621 | self.pt_list_cam = pt_list
1622 | self.model_box_cam.clear()
1623 | self.model_box_cam.addItems(self.pt_list_cam)
1624 |
1625 | elif self.task == "Segment":
1626 | pt_list = os.listdir("./models/segment")
1627 | pt_list = [
1628 | file for file in pt_list if file.endswith((".pt", "onnx", "engine"))
1629 | ]
1630 | pt_list.sort(key=lambda x: os.path.getsize("./models/segment/" + x))
1631 |
1632 | # 如果模型文件列表发生变化,则更新模型下拉框的内容
1633 | if pt_list != self.pt_list:
1634 | self.pt_list = pt_list
1635 | self.model_box.clear()
1636 | self.model_box.addItems(self.pt_list)
1637 | self.pt_list_cam = pt_list
1638 | self.model_box_cam.clear()
1639 | self.model_box_cam.addItems(self.pt_list_cam)
1640 |
1641 | elif self.task == "Track":
1642 | pt_list = os.listdir("./models/track")
1643 | pt_list = [
1644 | file for file in pt_list if file.endswith((".pt", "onnx", "engine"))
1645 | ]
1646 | pt_list.sort(key=lambda x: os.path.getsize("./models/track/" + x))
1647 |
1648 | # 如果模型文件列表发生变化,则更新模型下拉框的内容
1649 | if pt_list != self.pt_list:
1650 | self.pt_list = pt_list
1651 | self.model_box.clear()
1652 | self.model_box.addItems(self.pt_list)
1653 | self.pt_list_cam = pt_list
1654 | self.model_box_cam.clear()
1655 | self.model_box_cam.addItems(self.pt_list_cam)
1656 |
1657 | # 获取滑鼠位置(用于按住标题栏拖动窗口)
1658 | def mousePressEvent(self, event):
1659 | p = event.globalPosition()
1660 | globalPos = p.toPoint()
1661 | self.dragPos = globalPos
1662 |
1663 | # 在调整窗口大小时进行优化调整(针对拖动窗口右下角边缘调整窗口大小)
1664 | def resizeEvent(self, event):
1665 | # 更新大小调整的手柄
1666 | UIFuncitons.resize_grips(self)
1667 |
1668 | # 配置初始化
1669 | def load_config(self):
1670 | config_file = "config/setting.json"
1671 |
1672 | # 如果配置文件不存在,则创建并写入默认配置
1673 | if not os.path.exists(config_file):
1674 | iou = 0.26
1675 | conf = 0.33
1676 | rate = 10
1677 | save_res = 0
1678 | save_txt = 0
1679 | save_res_cam = 0
1680 | save_txt_cam = 0
1681 | new_config = {
1682 | "iou": iou,
1683 | "conf": conf,
1684 | "rate": rate,
1685 | "save_res": save_res,
1686 | "save_txt": save_txt,
1687 | "save_res": save_res_cam,
1688 | "save_txt": save_txt_cam,
1689 | }
1690 | new_json = json.dumps(new_config, ensure_ascii=False, indent=2)
1691 | with open(config_file, "w", encoding="utf-8") as f:
1692 | f.write(new_json)
1693 | else:
1694 | # 如果配置文件存在,读取配置
1695 | config = json.load(open(config_file, "r", encoding="utf-8"))
1696 |
1697 | # 检查配置内容是否完整,如果不完整,使用默认值
1698 | if len(config) != 7:
1699 | iou = 0.26
1700 | conf = 0.33
1701 | rate = 10
1702 | save_res = 0
1703 | save_txt = 0
1704 | save_res_cam = 0
1705 | save_txt_cam = 0
1706 | else:
1707 | iou = config["iou"]
1708 | conf = config["conf"]
1709 | rate = config["rate"]
1710 | save_res = config["save_res"]
1711 | save_txt = config["save_txt"]
1712 | save_res_cam = config["save_res_cam"]
1713 | save_txt_cam = config["save_txt_cam"]
1714 |
1715 | # 根据配置设置界面元素的状态
1716 | self.save_res_button.setCheckState(Qt.CheckState(save_res))
1717 | self.yolo_predict.save_res = False if save_res == 0 else True
1718 | self.save_txt_button.setCheckState(Qt.CheckState(save_txt))
1719 | self.yolo_predict.save_txt = False if save_txt == 0 else True
1720 | self.run_button.setChecked(False)
1721 |
1722 | self.save_res_button_cam.setCheckState(Qt.CheckState(save_res_cam))
1723 | self.yolo_predict_cam.save_res_cam = False if save_res_cam == 0 else True
1724 | self.save_txt_button_cam.setCheckState(Qt.CheckState(save_txt_cam))
1725 | self.yolo_predict_cam.save_txt_cam = False if save_txt_cam == 0 else True
1726 | self.run_button_cam.setChecked(False)
1727 | self.show_status("欢迎使用YOLOv8检测系统,请选择Mode")
1728 | # self.show_status("目前为image or video检测页面")
1729 |
1730 | # 关闭事件,退出线程,保存设置
1731 | def closeEvent(self, event):
1732 | # 保存配置到设定文件
1733 | config_file = "config/setting.json"
1734 | config = dict()
1735 | config["iou"] = self.iou_spinbox.value()
1736 | config["conf"] = self.conf_spinbox.value()
1737 | config["rate"] = self.speed_spinbox.value()
1738 | config["save_res"] = (
1739 | 0 if self.save_res_button.checkState() == Qt.Unchecked else 2
1740 | )
1741 | config["save_txt"] = (
1742 | 0 if self.save_txt_button.checkState() == Qt.Unchecked else 2
1743 | )
1744 | config["save_res_cam"] = (
1745 | 0 if self.save_res_button_cam.checkState() == Qt.Unchecked else 2
1746 | )
1747 | config["save_txt_cam"] = (
1748 | 0 if self.save_txt_button_cam.checkState() == Qt.Unchecked else 2
1749 | )
1750 | config_json = json.dumps(config, ensure_ascii=False, indent=2)
1751 | with open(config_file, "w", encoding="utf-8") as f:
1752 | f.write(config_json)
1753 |
1754 | # 退出线程和应用程序
1755 | if self.yolo_thread.isRunning() or self.yolo_thread_cam.isRunning():
1756 | # 如果 YOLO 线程正在运行,则终止线程
1757 | self.yolo_predict.stop_dtc = True
1758 | self.yolo_thread.quit()
1759 |
1760 | self.yolo_predict_cam.stop_dtc = True
1761 | self.yolo_thread_cam.quit()
1762 | # 显示退出提示,等待3秒
1763 | MessageBox(
1764 | self.close_button,
1765 | title="Note",
1766 | text="Exiting, please wait...",
1767 | time=3000,
1768 | auto=True,
1769 | ).exec()
1770 |
1771 | # 退出应用程序
1772 | sys.exit(0)
1773 | else:
1774 | # 如果 YOLO 线程未运行,直接退出应用程序
1775 | sys.exit(0)
1776 |
1777 | ####################################共用####################################
1778 |
1779 |
1780 | if __name__ == "__main__":
1781 | app = QApplication(sys.argv)
1782 | Home = MainWindow()
1783 | # 创建相机线程
1784 | # camera_thread = CameraThread()
1785 | # camera_thread.imageCaptured.connect(Home.cam_data)
1786 | # camera_thread.start()
1787 | Home.show()
1788 | sys.exit(app.exec())
1789 |
--------------------------------------------------------------------------------
/models/classify/yolov8n-cls.pt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/models/classify/yolov8n-cls.pt
--------------------------------------------------------------------------------
/models/detect/yolov8n.pt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/models/detect/yolov8n.pt
--------------------------------------------------------------------------------
/models/pose/yolov8n-pose.pt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/models/pose/yolov8n-pose.pt
--------------------------------------------------------------------------------
/models/segment/yolov8n-seg.pt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/WangQvQ/Ultralytics-PySide6/22bafef6a52368cb11a143aa22b3703a63c0387e/models/segment/yolov8n-seg.pt
--------------------------------------------------------------------------------
/resources.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | img/pause.png
4 | img/model.png
5 | img/save.png
6 | img/check_yes.png
7 | img/check_no.png
8 | img/delay.png
9 | img/conf.png
10 | img/box_down.png
11 | img/box_up.png
12 | img/IOU.png
13 | img/stop.png
14 | img/begin.png
15 | img/set.png
16 | img/menu.png
17 | img/file.png
18 | img/cam.png
19 | img/RTSP.png
20 | img/classify.png
21 | img/detect.png
22 | img/pose.png
23 | img/segment.png
24 | img/track.png
25 | img/logo.png
26 |
27 |
28 |
--------------------------------------------------------------------------------
/ui/CustomMessageBox.py:
--------------------------------------------------------------------------------
1 | from PySide6.QtCore import QTimer, Qt
2 | from PySide6.QtWidgets import QMessageBox
3 | from PySide6.QtGui import QPixmap, QIcon
4 |
5 |
6 | # Single-button dialog box, which disappears automatically after appearing for a specified period of time
7 | class MessageBox(QMessageBox):
8 | def __init__(self, *args, title="提示", count=1, time=1000, auto=False, **kwargs):
9 | super(MessageBox, self).__init__(*args, **kwargs)
10 | self._count = count
11 | self._time = time
12 | self._auto = auto # Whether to close automatically
13 | assert count > 0 # must be greater than 0
14 | assert time >= 500 # Must be >=500 milliseconds
15 | self.setStyleSheet(
16 | """
17 | QWidget{color:black;
18 | background-color: qlineargradient(x0:0, y0:1, x1:1, y1:1,stop:0.4 rgb(107, 128, 210),stop:1 rgb(180, 140, 255));
19 | font: 13pt "Microsoft YaHei UI";
20 | padding-right: 5px;
21 | padding-top: 14px;
22 | font-weight: light;}
23 | QLabel{
24 | color:white;
25 | background-color: rgba(107, 128, 210, 0);}"""
26 | )
27 |
28 | self.setWindowTitle(title)
29 |
30 | self.setStandardButtons(QMessageBox.StandardButton.Close) # close button
31 | self.closeBtn = self.button(
32 | QMessageBox.StandardButton.Close
33 | ) # get close button
34 | self.closeBtn.setText("Close")
35 | self.closeBtn.setVisible(False)
36 | self._timer = QTimer(self, timeout=self.doCountDown)
37 | self._timer.start(self._time)
38 |
39 | def doCountDown(self):
40 | self._count -= 1
41 | if self._count <= 0:
42 | self._timer.stop()
43 | if self._auto: # auto close
44 | self.accept()
45 | self.close()
46 |
47 |
48 | if __name__ == "__main__":
49 | MessageBox(QWidget=None, text="123", auto=True).exec()
50 |
--------------------------------------------------------------------------------
/ui/rtsp_dialog.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Form
4 |
5 |
6 |
7 | 0
8 | 0
9 | 783
10 | 40
11 |
12 |
13 |
14 |
15 | 0
16 | 40
17 |
18 |
19 |
20 |
21 | 16777215
22 | 41
23 |
24 |
25 |
26 | Form
27 |
28 |
29 |
30 | :/img/icon/实时视频流解析.png:/img/icon/实时视频流解析.png
31 |
32 |
33 | #Form{background:rgba(120,120,120,255)}
34 |
35 |
36 |
37 | 5
38 |
39 |
40 | 5
41 |
42 | -
43 |
44 |
45 |
46 | 0
47 | 30
48 |
49 |
50 |
51 |
52 | 16777215
53 | 30
54 |
55 |
56 |
57 | QLabel{font-family: "Microsoft YaHei";
58 | font-size: 18px;
59 | font-weight: bold;
60 | color:white;}
61 |
62 |
63 | rtsp address:
64 |
65 |
66 |
67 | -
68 |
69 |
70 |
71 | 0
72 | 31
73 |
74 |
75 |
76 | background-color: rgb(207, 207, 207);
77 |
78 |
79 |
80 | -
81 |
82 |
83 | QPushButton{font-family: "Microsoft YaHei";
84 | font-size: 18px;
85 | font-weight: bold;
86 | color:white;
87 | text-align: center center;
88 | padding-left: 5px;
89 | padding-right: 5px;
90 | padding-top: 4px;
91 | padding-bottom: 4px;
92 | border-style: solid;
93 | border-width: 0px;
94 | border-color: rgba(255, 255, 255, 255);
95 | border-radius: 3px;
96 | background-color: rgba(255,255,255,30);}
97 |
98 | QPushButton:focus{outline: none;}
99 |
100 | QPushButton::pressed{font-family: "Microsoft YaHei";
101 | font-size: 16px;
102 | font-weight: bold;
103 | color:rgb(200,200,200);
104 | text-align: center center;
105 | padding-left: 5px;
106 | padding-right: 5px;
107 | padding-top: 4px;
108 | padding-bottom: 4px;
109 | border-style: solid;
110 | border-width: 0px;
111 | border-color: rgba(255, 255, 255, 255);
112 | border-radius: 3px;
113 | background-color: rgba(255,255,255,150);}
114 |
115 | QPushButton::hover {
116 | border-style: solid;
117 | border-width: 0px;
118 | border-radius: 0px;
119 | background-color: rgba(255,255,255,50);}
120 |
121 |
122 | confirm
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/ui/rtsp_dialog_ui.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | ################################################################################
4 | ## Form generated from reading UI file 'rtsp_dialog.ui'
5 | ##
6 | ## Created by: Qt User Interface Compiler version 6.4.2
7 | ##
8 | ## WARNING! All changes made in this file will be lost when recompiling UI file!
9 | ################################################################################
10 |
11 | from PySide6.QtCore import (
12 | QCoreApplication,
13 | QDate,
14 | QDateTime,
15 | QLocale,
16 | QMetaObject,
17 | QObject,
18 | QPoint,
19 | QRect,
20 | QSize,
21 | QTime,
22 | QUrl,
23 | Qt,
24 | )
25 | from PySide6.QtGui import (
26 | QBrush,
27 | QColor,
28 | QConicalGradient,
29 | QCursor,
30 | QFont,
31 | QFontDatabase,
32 | QGradient,
33 | QIcon,
34 | QImage,
35 | QKeySequence,
36 | QLinearGradient,
37 | QPainter,
38 | QPalette,
39 | QPixmap,
40 | QRadialGradient,
41 | QTransform,
42 | )
43 | from PySide6.QtWidgets import (
44 | QApplication,
45 | QHBoxLayout,
46 | QLabel,
47 | QLineEdit,
48 | QPushButton,
49 | QSizePolicy,
50 | QWidget,
51 | )
52 | import apprcc_rc
53 |
54 |
55 | class Ui_Form(object):
56 | def setupUi(self, Form):
57 | if not Form.objectName():
58 | Form.setObjectName(u"Form")
59 | Form.resize(783, 40)
60 | Form.setMinimumSize(QSize(0, 40))
61 | Form.setMaximumSize(QSize(16777215, 41))
62 | icon = QIcon()
63 | icon.addFile(
64 | u":/img/icon/\u5b9e\u65f6\u89c6\u9891\u6d41\u89e3\u6790.png",
65 | QSize(),
66 | QIcon.Normal,
67 | QIcon.Off,
68 | )
69 | Form.setWindowIcon(icon)
70 | Form.setStyleSheet(u"#Form{background:rgba(120,120,120,255)}")
71 | self.horizontalLayout = QHBoxLayout(Form)
72 | self.horizontalLayout.setObjectName(u"horizontalLayout")
73 | self.horizontalLayout.setContentsMargins(-1, 5, -1, 5)
74 | self.label = QLabel(Form)
75 | self.label.setObjectName(u"label")
76 | self.label.setMinimumSize(QSize(0, 30))
77 | self.label.setMaximumSize(QSize(16777215, 30))
78 | self.label.setStyleSheet(
79 | u'QLabel{font-family: "Microsoft YaHei";\n'
80 | "font-size: 18px;\n"
81 | "font-weight: bold;\n"
82 | "color:white;}"
83 | )
84 |
85 | self.horizontalLayout.addWidget(self.label)
86 |
87 | self.rtspEdit = QLineEdit(Form)
88 | self.rtspEdit.setObjectName(u"rtspEdit")
89 | self.rtspEdit.setMinimumSize(QSize(0, 31))
90 | self.rtspEdit.setStyleSheet(u"background-color: rgb(207, 207, 207);")
91 |
92 | self.horizontalLayout.addWidget(self.rtspEdit)
93 |
94 | self.rtspButton = QPushButton(Form)
95 | self.rtspButton.setObjectName(u"rtspButton")
96 | self.rtspButton.setStyleSheet(
97 | u'QPushButton{font-family: "Microsoft YaHei";\n'
98 | "font-size: 18px;\n"
99 | "font-weight: bold;\n"
100 | "color:white;\n"
101 | "text-align: center center;\n"
102 | "padding-left: 5px;\n"
103 | "padding-right: 5px;\n"
104 | "padding-top: 4px;\n"
105 | "padding-bottom: 4px;\n"
106 | "border-style: solid;\n"
107 | "border-width: 0px;\n"
108 | "border-color: rgba(255, 255, 255, 255);\n"
109 | "border-radius: 3px;\n"
110 | "background-color: rgba(255,255,255,30);}\n"
111 | "\n"
112 | "QPushButton:focus{outline: none;}\n"
113 | "\n"
114 | 'QPushButton::pressed{font-family: "Microsoft YaHei";\n'
115 | " font-size: 16px;\n"
116 | " font-weight: bold;\n"
117 | " color:rgb(200,200,200);\n"
118 | " text-align: center center;\n"
119 | " padding-left: 5px;\n"
120 | " padding-right: 5px;\n"
121 | " padding-top: 4px;\n"
122 | " padding-bottom: 4px;\n"
123 | " border-style: solid;\n"
124 | " border-width: 0px;\n"
125 | " border-color: rgba(255, 255, 255, 255);\n"
126 | " "
127 | " border-radius: 3px;\n"
128 | " background-color: rgba(255,255,255,150);}\n"
129 | "\n"
130 | "QPushButton::hover {\n"
131 | "border-style: solid;\n"
132 | "border-width: 0px;\n"
133 | "border-radius: 0px;\n"
134 | "background-color: rgba(255,255,255,50);}"
135 | )
136 |
137 | self.horizontalLayout.addWidget(self.rtspButton)
138 |
139 | self.retranslateUi(Form)
140 |
141 | QMetaObject.connectSlotsByName(Form)
142 |
143 | # setupUi
144 |
145 | def retranslateUi(self, Form):
146 | Form.setWindowTitle(QCoreApplication.translate("Form", u"Form", None))
147 | self.label.setText(QCoreApplication.translate("Form", u"rtsp address:", None))
148 | self.rtspButton.setText(QCoreApplication.translate("Form", u"confirm", None))
149 |
150 | # retranslateUi
151 |
--------------------------------------------------------------------------------
/utils/capnums.py:
--------------------------------------------------------------------------------
1 | import cv2
2 |
3 |
4 | class Camera:
5 | def __init__(self, cam_preset_num=5):
6 | self.cam_preset_num = cam_preset_num
7 |
8 | def get_cam_num(self):
9 | cnt = 0
10 | devices = []
11 | for device in range(0, self.cam_preset_num):
12 | stream = cv2.VideoCapture(device, cv2.CAP_DSHOW)
13 | grabbed = stream.grab()
14 | stream.release()
15 | if not grabbed:
16 | continue
17 | else:
18 | cnt = cnt + 1
19 | devices.append(device)
20 | return cnt, devices
21 |
22 |
23 | if __name__ == "__main__":
24 | cam = Camera()
25 | cam_num, devices = cam.get_cam_num()
26 | print(cam_num, devices)
27 |
--------------------------------------------------------------------------------
/utils/rtsp_dialog.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file 'rtsp_dialog.ui'
4 | #
5 | # Created by: PyQt5 UI code generator 5.15.2
6 | #
7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is
8 | # run again. Do not edit this file unless you know what you are doing.
9 |
10 |
11 | from PySide6 import QtCore, QtGui, QtWidgets
12 |
13 |
14 | class Ui_Form(object):
15 | def setupUi(self, Form):
16 | Form.setObjectName("Form")
17 | Form.resize(783, 40)
18 | Form.setMinimumSize(QtCore.QSize(0, 40))
19 | Form.setMaximumSize(QtCore.QSize(16777215, 41))
20 | icon = QtGui.QIcon()
21 | icon.addPixmap(
22 | QtGui.QPixmap(":/img/None.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off
23 | )
24 | Form.setWindowIcon(icon)
25 | Form.setStyleSheet("#Form{background:rgba(120,120,120,255)}")
26 | self.horizontalLayout = QtWidgets.QHBoxLayout(Form)
27 | self.horizontalLayout.setContentsMargins(-1, 5, -1, 5)
28 | self.horizontalLayout.setObjectName("horizontalLayout")
29 | self.label = QtWidgets.QLabel(Form)
30 | self.label.setMinimumSize(QtCore.QSize(0, 30))
31 | self.label.setMaximumSize(QtCore.QSize(16777215, 30))
32 | self.label.setStyleSheet(
33 | 'QLabel{font-family: "Microsoft YaHei";\n'
34 | "font-size: 18px;\n"
35 | "font-weight: bold;\n"
36 | "color:white;}"
37 | )
38 | self.label.setObjectName("label")
39 | self.horizontalLayout.addWidget(self.label)
40 | self.rtspEdit = QtWidgets.QLineEdit(Form)
41 | self.rtspEdit.setMinimumSize(QtCore.QSize(0, 31))
42 | self.rtspEdit.setStyleSheet("background-color: rgb(207, 207, 207);")
43 | self.rtspEdit.setObjectName("rtspEdit")
44 | self.horizontalLayout.addWidget(self.rtspEdit)
45 | self.rtspButton = QtWidgets.QPushButton(Form)
46 | self.rtspButton.setStyleSheet(
47 | 'QPushButton{font-family: "Microsoft YaHei";\n'
48 | "font-size: 18px;\n"
49 | "font-weight: bold;\n"
50 | "color:white;\n"
51 | "text-align: center center;\n"
52 | "padding-left: 5px;\n"
53 | "padding-right: 5px;\n"
54 | "padding-top: 4px;\n"
55 | "padding-bottom: 4px;\n"
56 | "border-style: solid;\n"
57 | "border-width: 0px;\n"
58 | "border-color: rgba(255, 255, 255, 255);\n"
59 | "border-radius: 3px;\n"
60 | "background-color: rgba(255,255,255,30);}\n"
61 | "\n"
62 | "QPushButton:focus{outline: none;}\n"
63 | "\n"
64 | 'QPushButton::pressed{font-family: "Microsoft YaHei";\n'
65 | " font-size: 16px;\n"
66 | " font-weight: bold;\n"
67 | " color:rgb(200,200,200);\n"
68 | " text-align: center center;\n"
69 | " padding-left: 5px;\n"
70 | " padding-right: 5px;\n"
71 | " padding-top: 4px;\n"
72 | " padding-bottom: 4px;\n"
73 | " border-style: solid;\n"
74 | " border-width: 0px;\n"
75 | " border-color: rgba(255, 255, 255, 255);\n"
76 | " border-radius: 3px;\n"
77 | " background-color: rgba(255,255,255,150);}\n"
78 | "\n"
79 | "QPushButton::hover {\n"
80 | "border-style: solid;\n"
81 | "border-width: 0px;\n"
82 | "border-radius: 0px;\n"
83 | "background-color: rgba(255,255,255,50);}"
84 | )
85 | self.rtspButton.setObjectName("rtspButton")
86 | self.horizontalLayout.addWidget(self.rtspButton)
87 |
88 | self.retranslateUi(Form)
89 | QtCore.QMetaObject.connectSlotsByName(Form)
90 |
91 | def retranslateUi(self, Form):
92 | _translate = QtCore.QCoreApplication.translate
93 | Form.setWindowTitle(_translate("Form", "Form"))
94 | self.label.setText(_translate("Form", "rtsp address:"))
95 | self.rtspButton.setText(_translate("Form", "confirm"))
96 |
97 |
98 | # import apprcc_rc
99 |
--------------------------------------------------------------------------------
/utils/rtsp_win.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from PySide6.QtWidgets import QApplication, QWidget
3 | from utils.rtsp_dialog import Ui_Form
4 |
5 |
6 | class Window(QWidget, Ui_Form):
7 | def __init__(self):
8 | super(Window, self).__init__()
9 | self.setupUi(self)
10 |
11 |
12 | if __name__ == "__main__":
13 | app = QApplication(sys.argv)
14 | window = Window()
15 | window.show()
16 | sys.exit(app.exec())
17 |
--------------------------------------------------------------------------------