├── .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 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.11063548.svg)](https://doi.org/10.5281/zenodo.11063548) 3 | ## 页面效果 4 | 5 | ![main](assets/main-page.png) 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 | [![Star History Chart](https://api.star-history.com/svg?repos=WangQvQ/Ultralytics-PySide6&type=Timeline)](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 | --------------------------------------------------------------------------------