Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 03c7cb58da | |||
| b1871aa9e7 | |||
| de8ce7c4a6 | |||
| bba4cf7939 | |||
| 93c7c1a86b | |||
| 8b07397b5b | |||
| ff4a4cabc8 | |||
| 5adce6c8df | |||
| d6521d4be1 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -2,11 +2,9 @@
|
||||
bin/**
|
||||
build/**
|
||||
|
||||
installer/output/
|
||||
installer/*.wixobj
|
||||
installer/*.msi
|
||||
installer/*.wixpdb
|
||||
*.ps1
|
||||
|
||||
*/.venv
|
||||
|
||||
CLAUDE.md
|
||||
CLAUDE.md
|
||||
.claude/
|
||||
@@ -1,5 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
project(D330Viewer VERSION 1.0.0 LANGUAGES CXX C)
|
||||
project(Viewer VERSION 1.0.0 LANGUAGES CXX C)
|
||||
|
||||
# 设置C++标准
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
@@ -124,37 +124,42 @@ if(WIN32)
|
||||
endif()
|
||||
|
||||
# ==================== 安装配置 ====================
|
||||
# 安装可执行文件
|
||||
# 安装可执行文件到根目录
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
RUNTIME DESTINATION bin
|
||||
RUNTIME DESTINATION .
|
||||
)
|
||||
|
||||
# 安装所有DLL
|
||||
# 安装所有DLL到根目录
|
||||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/bin/
|
||||
DESTINATION bin
|
||||
DESTINATION .
|
||||
FILES_MATCHING PATTERN "*.dll"
|
||||
)
|
||||
|
||||
# 安装Qt平台插件
|
||||
# 安装Qt平台插件到platforms子目录
|
||||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/bin/platforms/
|
||||
DESTINATION bin/platforms
|
||||
DESTINATION platforms
|
||||
FILES_MATCHING PATTERN "*.dll"
|
||||
)
|
||||
|
||||
# ==================== CPack配置 - MSI安装程序 ====================
|
||||
set(CPACK_PACKAGE_NAME "D330Viewer")
|
||||
set(CPACK_PACKAGE_NAME "Viewer")
|
||||
set(CPACK_PACKAGE_VENDOR "Lorenzo Zhao")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "D330M Depth Camera Control System")
|
||||
set(CPACK_PACKAGE_VERSION "0.1.0")
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Depth Camera Control System")
|
||||
set(CPACK_PACKAGE_VERSION "0.3.0")
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR "0")
|
||||
set(CPACK_PACKAGE_VERSION_MINOR "1")
|
||||
set(CPACK_PACKAGE_VERSION_MINOR "3")
|
||||
set(CPACK_PACKAGE_VERSION_PATCH "0")
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY "D330Viewer")
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY "Viewer")
|
||||
|
||||
# WiX生成器配置(用于MSI)
|
||||
set(CPACK_GENERATOR "WIX")
|
||||
set(CPACK_WIX_UPGRADE_GUID "12345678-1234-1234-1234-123456789012")
|
||||
set(CPACK_WIX_PROGRAM_MENU_FOLDER "D330Viewer")
|
||||
set(CPACK_WIX_UPGRADE_GUID "42365CB0-5840-487F-A2C8-56F9699A9022")
|
||||
set(CPACK_WIX_PROGRAM_MENU_FOLDER "Viewer")
|
||||
set(CPACK_WIX_LICENSE_RTF "${CMAKE_SOURCE_DIR}/LICENSE.rtf")
|
||||
|
||||
# 创建开始菜单和桌面快捷方式
|
||||
set(CPACK_PACKAGE_EXECUTABLES "Viewer" "Viewer")
|
||||
set(CPACK_CREATE_DESKTOP_LINKS "Viewer")
|
||||
|
||||
# 包含CPack模块
|
||||
include(CPack)
|
||||
|
||||
674
LICENSE
Normal file
674
LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
19
LICENSE.rtf
Normal file
19
LICENSE.rtf
Normal file
@@ -0,0 +1,19 @@
|
||||
{\rtf1\ansi\deff0
|
||||
{\fonttbl{\f0 Courier New;}}
|
||||
\f0\fs20
|
||||
GNU GENERAL PUBLIC LICENSE\line
|
||||
Version 3, 29 June 2007\line
|
||||
\line
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\line
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.\line
|
||||
\line
|
||||
Preamble\line
|
||||
\line
|
||||
The GNU General Public License is a free, copyleft license for software and other kinds of works.\line
|
||||
\line
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\line
|
||||
\line
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\line
|
||||
\line
|
||||
You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.\line
|
||||
}
|
||||
39
README.md
39
README.md
@@ -109,25 +109,36 @@ candle.exe -?
|
||||
|
||||
**生成MSI安装程序**:
|
||||
|
||||
**方式1:使用CPack(推荐)**
|
||||
```bash
|
||||
# 编译完成后,在build目录运行
|
||||
cd build
|
||||
cpack -G WIX -C Release
|
||||
```
|
||||
生成的安装程序:`build/D330Viewer-0.1.0-win64.msi`
|
||||
|
||||
**方式2:使用WiX Toolset手动构建**
|
||||
```bash
|
||||
build_installer.bat
|
||||
```
|
||||
生成的安装程序:`installer/output/D330Viewer.msi`
|
||||
生成的安装程序:`build/D330Viewer-0.1.0-win64.msi`
|
||||
|
||||
**MSI安装程序包含**:
|
||||
- D330Viewer.exe(主程序)
|
||||
- 所有必需的DLL(37个)
|
||||
- 所有必需的DLL
|
||||
- Qt平台插件(platforms目录)
|
||||
- 开始菜单快捷方式
|
||||
- 桌面快捷方式
|
||||
- 卸载程序快捷方式(开始菜单)
|
||||
|
||||
**安装目录结构**:
|
||||
```
|
||||
C:\Program Files\D330Viewer\
|
||||
├── D330Viewer.exe # 主程序
|
||||
├── *.dll # 所有依赖库
|
||||
└── platforms\ # Qt平台插件
|
||||
└── qwindows.dll
|
||||
```
|
||||
|
||||
**快捷方式**:
|
||||
- 桌面:D330Viewer(卸载时自动删除)
|
||||
- 开始菜单:D330Viewer 文件夹
|
||||
- D330Viewer(启动程序)
|
||||
- 卸载 D330Viewer(卸载程序)
|
||||
|
||||
## 功能特性
|
||||
|
||||
@@ -137,10 +148,11 @@ build_installer.bat
|
||||
- ✅ UDP网络通信(GVSP协议解析)
|
||||
- ✅ 自动设备扫描和发现
|
||||
- ✅ 相机连接管理(连接/断开)
|
||||
- ✅ 命令发送(START/STOP/ONCE)
|
||||
- ✅ 命令发送(START/STOP)
|
||||
|
||||
#### 可视化
|
||||
- ✅ 实时深度图显示(OpenCV,垂直翻转校正)
|
||||
- ✅ 实时左红外、右红外相机图像显示
|
||||
- ✅ 实时RGB相机图像显示
|
||||
- ✅ 实时3D点云显示(自定义OpenGL渲染)
|
||||
- ✅ 正交投影视角(平面视图)
|
||||
- ✅ 交互式点云操作:
|
||||
@@ -150,7 +162,7 @@ build_installer.bat
|
||||
|
||||
#### 用户界面
|
||||
- ✅ Qt6 GUI主窗口(三栏布局)
|
||||
- ✅ 相机控制面板(START/STOP/ONCE按钮)
|
||||
- ✅ 相机控制面板(START/STOP按钮)
|
||||
- ✅ 曝光时间调节(滑块,范围460-100000μs)
|
||||
- ✅ 网络配置(IP地址、端口设置)
|
||||
- ✅ 连接状态指示
|
||||
@@ -190,12 +202,10 @@ d330viewer/
|
||||
│ └── core/ # 核心功能头文件
|
||||
├── bin/ # 可执行文件和DLL
|
||||
│ ├── D330Viewer.exe # 主程序
|
||||
│ └── *.dll # 依赖库(37个)
|
||||
│ └── *.dll # 依赖库
|
||||
├── build/ # CMake构建目录
|
||||
├── installer/ # MSI安装程序配置
|
||||
├── CMakeLists.txt # CMake配置
|
||||
├── configure.bat # 快速配置脚本
|
||||
├── build_installer.bat # MSI安装程序构建脚本
|
||||
└── README.md # 本文件
|
||||
```
|
||||
|
||||
@@ -249,7 +259,6 @@ d330viewer/
|
||||
|
||||
5. **停止采集**
|
||||
- 点击"停止"按钮停止采集
|
||||
- 点击"单次"按钮采集单帧
|
||||
|
||||
### 点云交互操作
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
@echo off
|
||||
echo ========================================
|
||||
echo D330Viewer MSI Installer Builder
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
REM 检查WiX Toolset是否安装
|
||||
where candle.exe >nul 2>&1
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
echo ERROR: WiX Toolset not found!
|
||||
echo Please install WiX Toolset from: https://wixtoolset.org/
|
||||
echo After installation, add WiX bin directory to PATH
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [1/5] Checking build output...
|
||||
if not exist "bin\D330Viewer.exe" (
|
||||
echo ERROR: D330Viewer.exe not found in bin directory
|
||||
echo Please build the project first: cmake --build build --config Release
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [2/5] Creating installer directory...
|
||||
if not exist "installer\output" mkdir installer\output
|
||||
|
||||
echo [3/5] Harvesting DLL files...
|
||||
REM 使用heat.exe自动收集bin目录下的所有文件
|
||||
heat.exe dir bin -cg BinFiles -gg -scom -sreg -sfrag -srd -dr INSTALLFOLDER -var var.BinDir -out installer\BinFiles.wxs
|
||||
|
||||
echo [4/5] Compiling WiX source...
|
||||
candle.exe -dBinDir=bin installer\UpperControl.wxs installer\BinFiles.wxs -out installer\output\
|
||||
|
||||
echo [5/5] Linking MSI package...
|
||||
light.exe -ext WixUIExtension installer\output\UpperControl.wixobj installer\output\BinFiles.wixobj -out installer\output\D330Viewer.msi
|
||||
|
||||
if exist "installer\output\D330Viewer.msi" (
|
||||
echo.
|
||||
echo ========================================
|
||||
echo SUCCESS! MSI installer created:
|
||||
echo installer\output\D330Viewer.msi
|
||||
echo ========================================
|
||||
) else (
|
||||
echo.
|
||||
echo ERROR: Failed to create MSI installer
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -4,7 +4,7 @@ REM CMake配置脚本 - Windows版本
|
||||
REM 请根据实际安装路径修改以下变量
|
||||
|
||||
echo ========================================
|
||||
echo D330Viewer CMake配置脚本
|
||||
echo Viewer CMake配置脚本
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
@@ -47,7 +47,7 @@ if %ERRORLEVEL% EQU 0 (
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 下一步:
|
||||
echo 1. 打开 build\D330Viewer.sln 使用Visual Studio编译
|
||||
echo 1. 打开 build\Viewer.sln 使用Visual Studio编译
|
||||
echo 2. 或运行: cmake --build build --config Release
|
||||
echo.
|
||||
) else (
|
||||
|
||||
@@ -4,31 +4,45 @@
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QImage>
|
||||
#include <QAtomicInt>
|
||||
#include <cstdint>
|
||||
|
||||
// GVSP packet types
|
||||
// GVSP packet types (GigE Vision 2.1 standard)
|
||||
#define GVSP_LEADER_PACKET 0x01
|
||||
#define GVSP_PAYLOAD_PACKET 0x02
|
||||
#define GVSP_TRAILER_PACKET 0x03
|
||||
#define GVSP_TRAILER_PACKET 0x02
|
||||
#define GVSP_PAYLOAD_PACKET 0x03
|
||||
|
||||
// Payload types
|
||||
#define PAYLOAD_TYPE_IMAGE 0x0001
|
||||
#define PAYLOAD_TYPE_BINARY 0x0003
|
||||
#define PAYLOAD_TYPE_IMAGE 0x0001
|
||||
#define PAYLOAD_TYPE_BINARY 0x0003
|
||||
#define PAYLOAD_TYPE_POINTCLOUD 0x8000 // Vendor-specific for point cloud data
|
||||
|
||||
// Image format
|
||||
#define PIXEL_FORMAT_12BIT_GRAY 0x010C0001
|
||||
#define PIXEL_FORMAT_MONO16 0x01100005 // Mono16 format (legacy)
|
||||
#define PIXEL_FORMAT_MONO16_LEFT 0x01100006 // Mono16 format for left IR camera
|
||||
#define PIXEL_FORMAT_MONO16_RIGHT 0x01100007 // Mono16 format for right IR camera
|
||||
#define PIXEL_FORMAT_MONO8_LEFT 0x01080006 // Mono8 format for left IR camera (downsampled)
|
||||
#define PIXEL_FORMAT_MONO8_RIGHT 0x01080007 // Mono8 format for right IR camera (downsampled)
|
||||
#define PIXEL_FORMAT_MJPEG 0x02180001 // MJPEG format for RGB camera
|
||||
|
||||
// Image dimensions
|
||||
#define IMAGE_WIDTH 1224
|
||||
#define IMAGE_HEIGHT 1024
|
||||
#define IR_DISPLAY_WIDTH 612 // Downsampled IR display width
|
||||
#define IR_DISPLAY_HEIGHT 512 // Downsampled IR display height
|
||||
#define RGB_WIDTH 1920
|
||||
#define RGB_HEIGHT 1080
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
// GVSP packet header
|
||||
// GVSP packet header (GigE Vision 2.1 standard - 8 bytes)
|
||||
struct GVSPPacketHeader {
|
||||
uint16_t status;
|
||||
uint16_t block_id;
|
||||
uint32_t packet_fmt_id;
|
||||
uint16_t status; // Status flags
|
||||
uint16_t block_id; // Block ID (frame number)
|
||||
uint8_t packet_format; // Packet type: 0x01=Leader, 0x02=Trailer, 0x03=Payload
|
||||
uint8_t reserved; // Reserved byte
|
||||
uint16_t packet_id; // Packet ID within block
|
||||
};
|
||||
|
||||
// Image data leader
|
||||
@@ -71,6 +85,22 @@ struct GVSPBinaryDataTrailer {
|
||||
uint32_t checksum;
|
||||
};
|
||||
|
||||
// Point cloud data leader (vendor-specific, payload_type = 0x8000)
|
||||
struct GVSPPointCloudDataLeader {
|
||||
uint16_t reserved;
|
||||
uint16_t payload_type; // 0x8000
|
||||
uint32_t timestamp_high;
|
||||
uint32_t timestamp_low;
|
||||
uint32_t data_size; // Total size of point cloud data
|
||||
};
|
||||
|
||||
// Point cloud data trailer
|
||||
struct GVSPPointCloudDataTrailer {
|
||||
uint32_t reserved;
|
||||
uint16_t payload_type; // 0x8000
|
||||
uint32_t checksum;
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
class GVSPParser : public QObject
|
||||
@@ -85,37 +115,57 @@ public:
|
||||
void reset();
|
||||
|
||||
signals:
|
||||
void imageReceived(const QImage &image, uint32_t blockId);
|
||||
void depthDataReceived(const QByteArray &depthData, uint32_t blockId);
|
||||
void leftImageReceived(const QByteArray &jpegData, uint32_t blockId); // 左红外图像(JPEG)
|
||||
void rightImageReceived(const QByteArray &jpegData, uint32_t blockId); // 右红外图像(JPEG)
|
||||
void rgbImageReceived(const QByteArray &jpegData, uint32_t blockId); // RGB图像(MJPEG)
|
||||
void pointCloudDataReceived(const QByteArray &cloudData, uint32_t blockId);
|
||||
void parseError(const QString &error);
|
||||
|
||||
// 兼容旧代码
|
||||
void imageReceived(const QImage &image, uint32_t blockId);
|
||||
void depthDataReceived(const QByteArray &depthData, uint32_t blockId);
|
||||
|
||||
private:
|
||||
// 每种数据流的独立接收状态
|
||||
struct StreamState {
|
||||
bool isReceiving;
|
||||
uint32_t blockId;
|
||||
QByteArray dataBuffer;
|
||||
size_t expectedSize;
|
||||
size_t receivedSize;
|
||||
int packetCount;
|
||||
|
||||
// 图像特有信息
|
||||
uint32_t imageWidth;
|
||||
uint32_t imageHeight;
|
||||
uint32_t pixelFormat;
|
||||
|
||||
StreamState() : isReceiving(false), blockId(0), expectedSize(0),
|
||||
receivedSize(0), packetCount(0), imageWidth(0),
|
||||
imageHeight(0), pixelFormat(0) {}
|
||||
};
|
||||
|
||||
void handleLeaderPacket(const uint8_t *data, size_t size);
|
||||
void handlePayloadPacket(const uint8_t *data, size_t size);
|
||||
void handleTrailerPacket(const uint8_t *data, size_t size);
|
||||
|
||||
void processImageData();
|
||||
void processDepthData();
|
||||
void processImageData(StreamState *state);
|
||||
void processDepthData(StreamState *state);
|
||||
void processPointCloudData(StreamState *state);
|
||||
|
||||
private:
|
||||
// Reception state
|
||||
bool m_isReceiving;
|
||||
int m_dataType; // 0=unknown, 1=image, 3=depth
|
||||
uint32_t m_currentBlockId;
|
||||
|
||||
// Data buffer
|
||||
QByteArray m_dataBuffer;
|
||||
size_t m_expectedSize;
|
||||
size_t m_receivedSize;
|
||||
|
||||
// Image info
|
||||
uint32_t m_imageWidth;
|
||||
uint32_t m_imageHeight;
|
||||
uint32_t m_pixelFormat;
|
||||
// 为每种数据类型维护独立的状态
|
||||
StreamState m_leftIRState; // 左红外
|
||||
StreamState m_rightIRState; // 右红外
|
||||
StreamState m_rgbState; // RGB
|
||||
StreamState m_depthState; // 深度数据
|
||||
StreamState m_pointCloudState; // 点云数据
|
||||
|
||||
// Statistics
|
||||
uint32_t m_lastBlockId;
|
||||
int m_packetCount;
|
||||
int m_imageSequence;
|
||||
|
||||
// Async processing control
|
||||
QAtomicInt m_imageProcessingCount;
|
||||
};
|
||||
|
||||
#endif // GVSPPARSER_H
|
||||
|
||||
@@ -27,6 +27,9 @@ public:
|
||||
// 将深度数据转换为点云(使用OpenCL GPU加速)
|
||||
void processDepthData(const QByteArray &depthData, uint32_t blockId);
|
||||
|
||||
// 处理已经计算好的点云数据(x,y,z格式)
|
||||
void processPointCloudData(const QByteArray &cloudData, uint32_t blockId);
|
||||
|
||||
signals:
|
||||
void pointCloudReady(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, uint32_t blockId);
|
||||
void errorOccurred(const QString &error);
|
||||
|
||||
@@ -23,6 +23,8 @@ public:
|
||||
~PointCloudGLWidget();
|
||||
|
||||
void updatePointCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud);
|
||||
void setColorMode(bool enabled) { m_colorMode = enabled ? 1 : 0; update(); }
|
||||
bool colorMode() const { return m_colorMode != 0; }
|
||||
|
||||
protected:
|
||||
void initializeGL() override;
|
||||
@@ -48,21 +50,31 @@ private:
|
||||
// 点云数据
|
||||
std::vector<float> m_vertices;
|
||||
int m_pointCount;
|
||||
float m_minZ, m_maxZ; // 深度范围(用于着色)
|
||||
|
||||
// 相机参数
|
||||
QMatrix4x4 m_projection;
|
||||
QMatrix4x4 m_view;
|
||||
QMatrix4x4 m_model;
|
||||
|
||||
float m_orthoSize; // 正交投影视野大小(控制缩放)
|
||||
float m_fov; // 透视投影视场角
|
||||
float m_rotationX; // X轴旋转角度
|
||||
float m_rotationY; // Y轴旋转角度
|
||||
QVector3D m_translation; // 平移
|
||||
QVector3D m_cloudCenter; // 点云中心
|
||||
float m_viewDistance; // 观察距离
|
||||
QVector3D m_panOffset; // 用户平移偏移
|
||||
float m_zoom; // 缩放因子
|
||||
|
||||
// 鼠标交互状态
|
||||
QPoint m_lastMousePos;
|
||||
bool m_leftButtonPressed;
|
||||
bool m_rightButtonPressed;
|
||||
|
||||
// 首帧标志(只在首帧时自动居中)
|
||||
bool m_firstFrame;
|
||||
|
||||
// 颜色模式(0=黑白,1=彩色)
|
||||
int m_colorMode;
|
||||
};
|
||||
|
||||
#endif // POINTCLOUDGLWIDGET_H
|
||||
|
||||
@@ -19,6 +19,10 @@ public:
|
||||
// 更新点云显示
|
||||
void updatePointCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud);
|
||||
|
||||
// 颜色模式控制
|
||||
void setColorMode(bool enabled);
|
||||
bool colorMode() const;
|
||||
|
||||
private:
|
||||
QLabel *m_statusLabel;
|
||||
PointCloudGLWidget *m_glWidget;
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Fragment>
|
||||
<DirectoryRef Id="INSTALLFOLDER">
|
||||
<Component Id="cmpC3C56006B21D7A9B1C3D970559A58022" Guid="{CC08CF56-C517-49D0-ABCF-9F873190FB5D}">
|
||||
<File Id="filA38101C31D3FE72B90DABBD659FA3749" KeyPath="yes" Source="$(var.BinDir)\CLAllSerial_MD_VC120_v3_1.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp94AE231A385A67D66B846FBD16D60636" Guid="{AF528182-E790-4F99-865F-96A8BAC63C64}">
|
||||
<File Id="fil24783C0C3176434BE2433E90A26639B5" KeyPath="yes" Source="$(var.BinDir)\CLProtocol_MD_VC120_v3_1.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp5BBD8A0A8D3848D3C63356E9ADDC3DAB" Guid="{49379FC3-4074-47AC-912F-9AF48F097D4A}">
|
||||
<File Id="fil96040E48A1CEC0DD77E92374F304C405" KeyPath="yes" Source="$(var.BinDir)\clsercom.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp41DC9741013CA4AD62A4A5FF421F08BC" Guid="{2D62ADE6-D030-4233-9A5B-140D90B3D415}">
|
||||
<File Id="fil84B847A9F8DABE6B95AF875695C9E6D1" KeyPath="yes" Source="$(var.BinDir)\d3dcompiler_47.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp5CADE23B444DBEA082D543627C541D33" Guid="{DA896890-8E2B-4061-86F6-321716A99339}">
|
||||
<File Id="fil8F54BE958899A2BFF99AC72F9A899AF9" KeyPath="yes" Source="$(var.BinDir)\DkamSDK.dll" />
|
||||
</Component>
|
||||
<Component Id="cmpFC9F1FCE7DF02EBFFBBB05A2D04DF5C9" Guid="{621784F4-1F1E-445B-B77F-4E22E28EA9AB}">
|
||||
<File Id="fil5B0CA26742B9001E819A6C24AB1DFE4E" KeyPath="yes" Source="$(var.BinDir)\DkamSDK_CSharp.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp0A946DD3BC4835FBF8A86E6F14AFF450" Guid="{438AC89E-097C-4091-BA8F-C3292A615684}">
|
||||
<File Id="filC769392EA3BDC8D6035070F861669864" KeyPath="yes" Source="$(var.BinDir)\DkamSDK_CSharp_Namespace.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp304DB2D25065C3D5B1F99012C3F097B3" Guid="{FD24F279-1A97-4F5F-893B-A8DBC387F094}">
|
||||
<File Id="fil47D910A70309D29213F27AFB1B0B9421" KeyPath="yes" Source="$(var.BinDir)\FirmwareUpdate_MD_VC120_v3_1.dll" />
|
||||
</Component>
|
||||
<Component Id="cmpA1FAB8CA5E7B238E225525086D59754B" Guid="{D61BBE30-31BB-44BA-9AAC-62F888F0FA0C}">
|
||||
<File Id="filEBA00F3DBC3F68DCF9B825874F6738CE" KeyPath="yes" Source="$(var.BinDir)\GCBase_MD_VC120_v3_1.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp0D1074CD4A4DC67B6653DCF74385DF0E" Guid="{94C5E933-E14B-4AF4-B172-78663F76989A}">
|
||||
<File Id="fil30A6FD8B9DD3FC13C5C579A4DBBDBC92" KeyPath="yes" Source="$(var.BinDir)\GenApi_MD_VC120_v3_1.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp9A9D29623257FA41663F84EAA2612688" Guid="{CEA04915-E4F8-4531-AE8D-41CAFE22C6D8}">
|
||||
<File Id="filB94200E89E3B121E295845B256282799" KeyPath="yes" Source="$(var.BinDir)\GenCP_MD_VC120_v3_1.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp45C131D18AAA1AE3EF9B48BCFCC28F6D" Guid="{F96A1BAD-8C80-4802-B549-481D5C246567}">
|
||||
<File Id="fil8354EA4C33A6C488882967BAC14C59EA" KeyPath="yes" Source="$(var.BinDir)\log4cpp_MD_VC120_v3_1.dll" />
|
||||
</Component>
|
||||
<Component Id="cmpA829C4573EB498556AC47A36290EBF20" Guid="{0016F5BD-D8CE-40D1-A8BC-0D01F9D6AD49}">
|
||||
<File Id="fil4D74A6B62C6930C4CE69FA293FA47754" KeyPath="yes" Source="$(var.BinDir)\Log_MD_VC120_v3_1.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp61CA0314C827E13940ED8588B8A8BA13" Guid="{574DB953-993C-46E7-94ED-CC770194A6B9}">
|
||||
<File Id="filBB5A2C2AEC26359B2C6F23420800AE9D" KeyPath="yes" Source="$(var.BinDir)\MathParser_MD_VC120_v3_1.dll" />
|
||||
</Component>
|
||||
<Component Id="cmpEDDED65BD9A61BB47433821AA149DC39" Guid="{9AAE661B-0960-4D6A-B64E-B3E838DADD4E}">
|
||||
<File Id="fil9EAA71401D5302036DABA47B8EF6B434" KeyPath="yes" Source="$(var.BinDir)\NodeMapData_MD_VC120_v3_1.dll" />
|
||||
</Component>
|
||||
<Component Id="cmpDAC39AC2BB8715FBE58F904CC2E9F698" Guid="{CF6BC55C-FF3E-421D-A7BF-98435449455E}">
|
||||
<File Id="fil3940E64D4079916BAF62ED6C54607F0E" KeyPath="yes" Source="$(var.BinDir)\opencv_world4130.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp316B7EADCCFA5F980015637E294FFC4B" Guid="{F99C0165-0728-4384-A782-7659F766FA25}">
|
||||
<File Id="fil07CE2ABE9F28D446757A019F07E1898F" KeyPath="yes" Source="$(var.BinDir)\opengl32sw.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp0C61783999259F0182CE807EF9128FF4" Guid="{184991A1-36A0-4830-B9DD-A58E07FBC1A1}">
|
||||
<File Id="fil449440E65BE6F95AC788ED910D6D3FB4" KeyPath="yes" Source="$(var.BinDir)\pcl_common.dll" />
|
||||
</Component>
|
||||
<Component Id="cmpB9B2B93E50693FCD82547A4EEDF59C47" Guid="{60CA5622-9531-43B7-BD6B-C82625D7B805}">
|
||||
<File Id="filC346A45D1A8B2E29F1558CA7C1156D48" KeyPath="yes" Source="$(var.BinDir)\pcl_filters.dll" />
|
||||
</Component>
|
||||
<Component Id="cmpE0368B606CE94148F4CA5F032A18B8AA" Guid="{F8F62E47-F16E-4FC1-BA4C-2A786A73A80B}">
|
||||
<File Id="filE14680FDD24600E0C7BC68717F06DB13" KeyPath="yes" Source="$(var.BinDir)\pcl_io.dll" />
|
||||
</Component>
|
||||
<Component Id="cmpA478CC827F5FA0CCF27AAD818849CBD7" Guid="{0C9073A4-A6FA-4BAB-9733-8A704E8ECF01}">
|
||||
<File Id="fil3B0E0708353E72AE7456B38B7F5BC970" KeyPath="yes" Source="$(var.BinDir)\pcl_io_ply.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp5878BDE96D855AFAF94266F3B9677E88" Guid="{279F0111-C41A-4501-BA4B-A750DD69B0F4}">
|
||||
<File Id="fil7470D204801B87A04BF47FD21B3750BF" KeyPath="yes" Source="$(var.BinDir)\pcl_kdtree.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp0759BB73FE6272908152EAACE25509E2" Guid="{62BD1558-338F-46F7-9B67-4C7F18FD443C}">
|
||||
<File Id="filCE419FFD12F6A6C0A0ED57B48BC8D9D8" KeyPath="yes" Source="$(var.BinDir)\pcl_octree.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp6B64D18EAB4E2AA54759D9616AB99FC6" Guid="{5972BA2F-7BED-4D8D-8C73-8E6131DAD884}">
|
||||
<File Id="fil82FA0626A335B90C9078B5F4C52872B3" KeyPath="yes" Source="$(var.BinDir)\pcl_outofcore.dll" />
|
||||
</Component>
|
||||
<Component Id="cmpE6ECB1DBD11FF2337C376EACE8AC5BC6" Guid="{9CC56B62-4A75-4583-B94B-09DE77D057D3}">
|
||||
<File Id="fil750E9427DCF40DCEB7349243CE6BCCFE" KeyPath="yes" Source="$(var.BinDir)\pcl_sample_consensus.dll" />
|
||||
</Component>
|
||||
<Component Id="cmpE6D8206385B0C897A99FF044F618C63E" Guid="{5F5D35F9-F55E-4D38-8AD2-991EA8C65DB5}">
|
||||
<File Id="fil360C6C1D4A2DB31173843F5596F2C54F" KeyPath="yes" Source="$(var.BinDir)\pcl_search.dll" />
|
||||
</Component>
|
||||
<Component Id="cmpD5BFC0D57560599B60E42E5501314FEB" Guid="{DB78A410-C481-4E70-B94C-02FCB980E63E}">
|
||||
<File Id="fil263B8621AC2C78E67B986C7BC1E576CD" KeyPath="yes" Source="$(var.BinDir)\pcl_segmentation.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp82FF9A9C17F8D75F054D5CDDB8768C5C" Guid="{38967235-8639-4C79-9E7C-724504137A1E}">
|
||||
<File Id="filD3270351B41213BA8E4318BBB4E5F2E9" KeyPath="yes" Source="$(var.BinDir)\pcl_surface.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp396E4B41C7A33BE56F778473828172F7" Guid="{B219C434-6BE7-45F0-8831-22D44291DF41}">
|
||||
<File Id="filCE07DA5E17AE0C265C2A1738147B6059" KeyPath="yes" Source="$(var.BinDir)\Qt6Concurrent.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp02DF6A9A3BF862D35391F764FE67153F" Guid="{0244645B-D7B2-42C9-8D61-09EBDE868F3D}">
|
||||
<File Id="fil494DCCE77F31DE27CD7C03A13FD938B0" KeyPath="yes" Source="$(var.BinDir)\Qt6Core.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp701A262B1CE5139B2E57DF21A5AA3B94" Guid="{53E7B97E-8BD1-4EFB-B232-D80A305CE61E}">
|
||||
<File Id="filD9F54CB97035337EAF318E4E55DD2F5F" KeyPath="yes" Source="$(var.BinDir)\Qt6Gui.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp1EAE9F3A379CF532AF1EBC883CDCD1D2" Guid="{5267D680-F1AD-4648-84D2-F68A45111EC0}">
|
||||
<File Id="fil15AC8A9B3B6A6F1FE6BBDDAE2042D5D0" KeyPath="yes" Source="$(var.BinDir)\Qt6Network.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp6C95449DAD236B0C9989FA43DD56CA85" Guid="{9BFC4239-AAD9-4D40-9058-FB26E9E2F8A9}">
|
||||
<File Id="fil93AD72205F2074362FE1D69894AF2E4E" KeyPath="yes" Source="$(var.BinDir)\Qt6OpenGL.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp11109A2A7F5FEC28295B469293762519" Guid="{B83D1B28-0FB2-4BDC-BE57-0943E296AAC0}">
|
||||
<File Id="fil9BB89E2877718991879476005F911AF2" KeyPath="yes" Source="$(var.BinDir)\Qt6OpenGLWidgets.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp5B0A9912E5069950C30FC5FF8EAA16D4" Guid="{70A602C1-1808-4DC5-9822-ECF2A321A750}">
|
||||
<File Id="fil9F3707482AFAB8A22C7B5C23D8C7CE69" KeyPath="yes" Source="$(var.BinDir)\Qt6Widgets.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp1B37E98B173B46E1E5315143EF8D7728" Guid="{89E23A4E-DACC-4A16-AC16-98C696218F10}">
|
||||
<File Id="fil27A091E66DC753FB25B8F53726B5D53D" KeyPath="yes" Source="$(var.BinDir)\Qt6Xml.dll" />
|
||||
</Component>
|
||||
<Component Id="cmp2F414B6DDC2ADD0824C2DDE58E9102FA" Guid="{B50C2D2D-629F-4688-8D16-271CB92626B7}">
|
||||
<File Id="filB3ED52B8FA49C0BD845D3E52B9F7F279" KeyPath="yes" Source="$(var.BinDir)\uppercontrol.log" />
|
||||
</Component>
|
||||
<Component Id="cmp3FB3F428740E959925E49E3155B50A4B" Guid="{D5A06D7C-FD95-456E-861D-EEC0ED327631}">
|
||||
<File Id="filF458411F7CE6D0DFCA440791D37E30EA" KeyPath="yes" Source="$(var.BinDir)\UpperControlGUI.exe" />
|
||||
</Component>
|
||||
<Component Id="cmpACC01C1F599CA1C44DC57D4D6199427D" Guid="{DA47AC68-F657-4FDD-90B9-5B3E82088F21}">
|
||||
<File Id="filBC2C4C6DBCF2DD007E8E416D1CFFBDC7" KeyPath="yes" Source="$(var.BinDir)\XmlParser_MD_VC120_v3_1.dll" />
|
||||
</Component>
|
||||
<Directory Id="dir908DFC228501FBE723FC009E659CB04F" Name="platforms">
|
||||
<Component Id="cmp3E80143A725EE7380FA78AA9E8BD2283" Guid="{04575A8F-DF0B-4345-BBCE-CB9B5BCD7AF6}">
|
||||
<File Id="fil702C83C4DEF82CE8980ED1100FE61CE2" KeyPath="yes" Source="$(var.BinDir)\platforms\qwindows.dll" />
|
||||
</Component>
|
||||
</Directory>
|
||||
</DirectoryRef>
|
||||
</Fragment>
|
||||
<Fragment>
|
||||
<ComponentGroup Id="BinFiles">
|
||||
<ComponentRef Id="cmpC3C56006B21D7A9B1C3D970559A58022" />
|
||||
<ComponentRef Id="cmp94AE231A385A67D66B846FBD16D60636" />
|
||||
<ComponentRef Id="cmp5BBD8A0A8D3848D3C63356E9ADDC3DAB" />
|
||||
<ComponentRef Id="cmp41DC9741013CA4AD62A4A5FF421F08BC" />
|
||||
<ComponentRef Id="cmp5CADE23B444DBEA082D543627C541D33" />
|
||||
<ComponentRef Id="cmpFC9F1FCE7DF02EBFFBBB05A2D04DF5C9" />
|
||||
<ComponentRef Id="cmp0A946DD3BC4835FBF8A86E6F14AFF450" />
|
||||
<ComponentRef Id="cmp304DB2D25065C3D5B1F99012C3F097B3" />
|
||||
<ComponentRef Id="cmpA1FAB8CA5E7B238E225525086D59754B" />
|
||||
<ComponentRef Id="cmp0D1074CD4A4DC67B6653DCF74385DF0E" />
|
||||
<ComponentRef Id="cmp9A9D29623257FA41663F84EAA2612688" />
|
||||
<ComponentRef Id="cmp45C131D18AAA1AE3EF9B48BCFCC28F6D" />
|
||||
<ComponentRef Id="cmpA829C4573EB498556AC47A36290EBF20" />
|
||||
<ComponentRef Id="cmp61CA0314C827E13940ED8588B8A8BA13" />
|
||||
<ComponentRef Id="cmpEDDED65BD9A61BB47433821AA149DC39" />
|
||||
<ComponentRef Id="cmpDAC39AC2BB8715FBE58F904CC2E9F698" />
|
||||
<ComponentRef Id="cmp316B7EADCCFA5F980015637E294FFC4B" />
|
||||
<ComponentRef Id="cmp0C61783999259F0182CE807EF9128FF4" />
|
||||
<ComponentRef Id="cmpB9B2B93E50693FCD82547A4EEDF59C47" />
|
||||
<ComponentRef Id="cmpE0368B606CE94148F4CA5F032A18B8AA" />
|
||||
<ComponentRef Id="cmpA478CC827F5FA0CCF27AAD818849CBD7" />
|
||||
<ComponentRef Id="cmp5878BDE96D855AFAF94266F3B9677E88" />
|
||||
<ComponentRef Id="cmp0759BB73FE6272908152EAACE25509E2" />
|
||||
<ComponentRef Id="cmp6B64D18EAB4E2AA54759D9616AB99FC6" />
|
||||
<ComponentRef Id="cmpE6ECB1DBD11FF2337C376EACE8AC5BC6" />
|
||||
<ComponentRef Id="cmpE6D8206385B0C897A99FF044F618C63E" />
|
||||
<ComponentRef Id="cmpD5BFC0D57560599B60E42E5501314FEB" />
|
||||
<ComponentRef Id="cmp82FF9A9C17F8D75F054D5CDDB8768C5C" />
|
||||
<ComponentRef Id="cmp396E4B41C7A33BE56F778473828172F7" />
|
||||
<ComponentRef Id="cmp02DF6A9A3BF862D35391F764FE67153F" />
|
||||
<ComponentRef Id="cmp701A262B1CE5139B2E57DF21A5AA3B94" />
|
||||
<ComponentRef Id="cmp1EAE9F3A379CF532AF1EBC883CDCD1D2" />
|
||||
<ComponentRef Id="cmp6C95449DAD236B0C9989FA43DD56CA85" />
|
||||
<ComponentRef Id="cmp11109A2A7F5FEC28295B469293762519" />
|
||||
<ComponentRef Id="cmp5B0A9912E5069950C30FC5FF8EAA16D4" />
|
||||
<ComponentRef Id="cmp1B37E98B173B46E1E5315143EF8D7728" />
|
||||
<ComponentRef Id="cmp2F414B6DDC2ADD0824C2DDE58E9102FA" />
|
||||
<ComponentRef Id="cmp3FB3F428740E959925E49E3155B50A4B" />
|
||||
<ComponentRef Id="cmpACC01C1F599CA1C44DC57D4D6199427D" />
|
||||
<ComponentRef Id="cmp3E80143A725EE7380FA78AA9E8BD2283" />
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -1,79 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Product Id="*"
|
||||
Name="D330Viewer"
|
||||
Language="1033"
|
||||
Version="0.1.0.0"
|
||||
Manufacturer="Your Company"
|
||||
UpgradeCode="12345678-1234-1234-1234-123456789012">
|
||||
|
||||
<Package InstallerVersion="200"
|
||||
Compressed="yes"
|
||||
InstallScope="perMachine"
|
||||
Description="D330M Depth Camera Control System"
|
||||
Comments="Qt6-based depth camera control and visualization system" />
|
||||
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<!-- 安装目录 -->
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
<Directory Id="ProgramFilesFolder">
|
||||
<Directory Id="INSTALLFOLDER" Name="D330Viewer" />
|
||||
</Directory>
|
||||
<Directory Id="ProgramMenuFolder">
|
||||
<Directory Id="ApplicationProgramsFolder" Name="D330Viewer"/>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<!-- 开始菜单快捷方式 -->
|
||||
<DirectoryRef Id="ApplicationProgramsFolder">
|
||||
<Component Id="ApplicationShortcut" Guid="12345678-1234-1234-1234-123456789013">
|
||||
<Shortcut Id="ApplicationStartMenuShortcut"
|
||||
Name="D330Viewer"
|
||||
Description="D330M Depth Camera Control System"
|
||||
Target="[INSTALLFOLDER]D330Viewer.exe"
|
||||
WorkingDirectory="INSTALLFOLDER"/>
|
||||
<Shortcut Id="UninstallProduct"
|
||||
Name="卸载 D330Viewer"
|
||||
Description="卸载 D330M 深度相机控制系统"
|
||||
Target="[SystemFolder]msiexec.exe"
|
||||
Arguments="/x [ProductCode]"/>
|
||||
<RemoveFolder Id="CleanUpShortCut" Directory="ApplicationProgramsFolder" On="uninstall"/>
|
||||
<RegistryValue Root="HKCU"
|
||||
Key="Software\D330Viewer"
|
||||
Name="installed"
|
||||
Type="integer"
|
||||
Value="1"
|
||||
KeyPath="yes"/>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<!-- 用户配置注册表项(卸载时删除) -->
|
||||
<DirectoryRef Id="TARGETDIR">
|
||||
<Component Id="UserSettings" Guid="12345678-1234-1234-1234-123456789015">
|
||||
<RegistryKey Root="HKCU" Key="Software\D330Viewer\D330Viewer" Action="createAndRemoveOnUninstall">
|
||||
<RegistryValue Type="string" Name="placeholder" Value="1" KeyPath="yes"/>
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<Feature Id="ProductFeature" Title="D330Viewer" Level="1">
|
||||
<ComponentGroupRef Id="ProductComponents" />
|
||||
<ComponentGroupRef Id="BinFiles" />
|
||||
<ComponentRef Id="ApplicationShortcut" />
|
||||
<ComponentRef Id="UserSettings" />
|
||||
</Feature>
|
||||
</Product>
|
||||
|
||||
<!-- 主程序文件 -->
|
||||
<Fragment>
|
||||
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
|
||||
<Component Id="MainExecutable" Guid="12345678-1234-1234-1234-123456789014">
|
||||
<File Id="D330Viewer.exe"
|
||||
Source="$(var.BinDir)\D330Viewer.exe"
|
||||
KeyPath="yes" />
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "config/ConfigManager.h"
|
||||
|
||||
ConfigManager::ConfigManager()
|
||||
: m_settings(std::make_unique<QSettings>("D330Viewer", "D330Viewer"))
|
||||
: m_settings(std::make_unique<QSettings>("Viewer", "Viewer"))
|
||||
{
|
||||
// 构造函数:初始化QSettings
|
||||
}
|
||||
@@ -45,7 +45,7 @@ void ConfigManager::setDataPort(int port)
|
||||
// ========== 相机配置 ==========
|
||||
int ConfigManager::getExposureTime() const
|
||||
{
|
||||
return m_settings->value("Camera/ExposureTime", 10000).toInt();
|
||||
return m_settings->value("Camera/ExposureTime", 1000).toInt();
|
||||
}
|
||||
|
||||
void ConfigManager::setExposureTime(int exposure)
|
||||
|
||||
@@ -94,7 +94,7 @@ void DeviceScanner::onReadyRead()
|
||||
if (response.contains("D330M_CAMERA")) {
|
||||
DeviceInfo device;
|
||||
device.ipAddress = senderIp;
|
||||
device.deviceName = "D330M Camera";
|
||||
device.deviceName = "Camera";
|
||||
device.port = SCAN_PORT;
|
||||
device.responseTime = 0;
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
QString DeviceScanner::getLocalSubnet()
|
||||
{
|
||||
// Get all network interfaces
|
||||
QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
|
||||
|
||||
// Priority 1: Look for Ethernet adapter (以太网)
|
||||
for (const QNetworkInterface &iface : interfaces) {
|
||||
QString name = iface.humanReadableName().toLower();
|
||||
|
||||
// Skip virtual adapters
|
||||
if (name.contains("virtual") || name.contains("vmware") ||
|
||||
name.contains("virtualbox") || name.contains("hyper-v") ||
|
||||
name.contains("vpn") || name.contains("tap") || name.contains("tun")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look for Ethernet adapter
|
||||
if (name.contains("ethernet") || name.contains("以太网")) {
|
||||
QList<QNetworkAddressEntry> entries = iface.addressEntries();
|
||||
for (const QNetworkAddressEntry &entry : entries) {
|
||||
QHostAddress addr = entry.ip();
|
||||
if (addr.protocol() == QAbstractSocket::IPv4Protocol && !addr.isLoopback()) {
|
||||
QString ip = addr.toString();
|
||||
QStringList parts = ip.split('.');
|
||||
if (parts.size() == 4) {
|
||||
qDebug() << "Found Ethernet adapter:" << iface.humanReadableName() << "IP:" << ip;
|
||||
return parts[0] + "." + parts[1] + "." + parts[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 2: Any non-virtual adapter
|
||||
for (const QNetworkInterface &iface : interfaces) {
|
||||
QString name = iface.humanReadableName().toLower();
|
||||
|
||||
if (name.contains("virtual") || name.contains("vmware") ||
|
||||
name.contains("virtualbox") || name.contains("hyper-v") ||
|
||||
name.contains("vpn") || name.contains("tap") || name.contains("tun")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QList<QNetworkAddressEntry> entries = iface.addressEntries();
|
||||
for (const QNetworkAddressEntry &entry : entries) {
|
||||
QHostAddress addr = entry.ip();
|
||||
if (addr.protocol() == QAbstractSocket::IPv4Protocol && !addr.isLoopback()) {
|
||||
QString ip = addr.toString();
|
||||
QStringList parts = ip.split('.');
|
||||
if (parts.size() == 4) {
|
||||
qDebug() << "Found adapter:" << iface.humanReadableName() << "IP:" << ip;
|
||||
return parts[0] + "." + parts[1] + "." + parts[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "192.168.0";
|
||||
}
|
||||
@@ -1,21 +1,15 @@
|
||||
#include "core/GVSPParser.h"
|
||||
#include <QDebug>
|
||||
#include <QtConcurrent>
|
||||
#include <cstring>
|
||||
#include <winsock2.h>
|
||||
|
||||
GVSPParser::GVSPParser(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_isReceiving(false)
|
||||
, m_dataType(0)
|
||||
, m_currentBlockId(0)
|
||||
, m_expectedSize(0)
|
||||
, m_receivedSize(0)
|
||||
, m_imageWidth(0)
|
||||
, m_imageHeight(0)
|
||||
, m_pixelFormat(0)
|
||||
, m_lastBlockId(0)
|
||||
, m_packetCount(0)
|
||||
, m_imageSequence(0)
|
||||
{
|
||||
m_imageProcessingCount.storeRelaxed(0);
|
||||
}
|
||||
|
||||
GVSPParser::~GVSPParser()
|
||||
@@ -24,17 +18,18 @@ GVSPParser::~GVSPParser()
|
||||
|
||||
void GVSPParser::reset()
|
||||
{
|
||||
m_isReceiving = false;
|
||||
m_dataType = 0;
|
||||
m_currentBlockId = 0;
|
||||
m_dataBuffer.clear();
|
||||
m_expectedSize = 0;
|
||||
m_receivedSize = 0;
|
||||
m_packetCount = 0;
|
||||
m_leftIRState = StreamState();
|
||||
m_rightIRState = StreamState();
|
||||
m_rgbState = StreamState();
|
||||
m_depthState = StreamState();
|
||||
m_pointCloudState = StreamState();
|
||||
m_imageSequence = 0;
|
||||
}
|
||||
|
||||
void GVSPParser::parsePacket(const QByteArray &packet)
|
||||
{
|
||||
static int debugCount = 0;
|
||||
|
||||
if (packet.size() < sizeof(GVSPPacketHeader)) {
|
||||
return;
|
||||
}
|
||||
@@ -42,27 +37,20 @@ void GVSPParser::parsePacket(const QByteArray &packet)
|
||||
const uint8_t *data = reinterpret_cast<const uint8_t*>(packet.constData());
|
||||
const GVSPPacketHeader *header = reinterpret_cast<const GVSPPacketHeader*>(data);
|
||||
|
||||
// 注释掉调试日志以提高性能
|
||||
// static int debugCount = 0;
|
||||
// if (debugCount < 3) {
|
||||
// qDebug() << "Packet" << debugCount << "first 16 bytes (hex):";
|
||||
// QString hexStr;
|
||||
// for (int i = 0; i < qMin(16, packet.size()); i++) {
|
||||
// hexStr += QString("%1 ").arg(data[i], 2, 16, QChar('0'));
|
||||
// }
|
||||
// qDebug() << hexStr;
|
||||
// debugCount++;
|
||||
// }
|
||||
|
||||
// GVSP包头格式:status(2) + block_id(2) + packet_format(4)
|
||||
// 包类型在packet_format的高字节
|
||||
uint32_t packet_fmt = ntohl(header->packet_fmt_id);
|
||||
uint8_t packetType = (packet_fmt >> 24) & 0xFF;
|
||||
// GigE Vision 2.1 标准 GVSP 包头解析
|
||||
uint16_t blockId = ntohs(header->block_id);
|
||||
uint8_t packetType = header->packet_format;
|
||||
uint16_t packetId = ntohs(header->packet_id);
|
||||
|
||||
// 注释掉频繁的日志输出以提高性能
|
||||
// static int leaderCount = 0;
|
||||
// static int trailerCount = 0;
|
||||
// 打印前5个包的详细信息
|
||||
if (debugCount < 5) {
|
||||
// qDebug() << "[GVSPParser] Packet" << debugCount
|
||||
// << "Type:" << packetType
|
||||
// << "BlockID:" << blockId
|
||||
// << "PacketID:" << packetId
|
||||
// << "Size:" << packet.size();
|
||||
debugCount++;
|
||||
}
|
||||
|
||||
switch (packetType) {
|
||||
case GVSP_LEADER_PACKET:
|
||||
@@ -81,12 +69,14 @@ void GVSPParser::parsePacket(const QByteArray &packet)
|
||||
|
||||
void GVSPParser::handleLeaderPacket(const uint8_t *data, size_t size)
|
||||
{
|
||||
if (size < sizeof(GVSPPacketHeader) + sizeof(GVSPImageDataLeader)) {
|
||||
// 最小大小检查:至少要有包头 + 2字节reserved + 2字节payload_type
|
||||
if (size < sizeof(GVSPPacketHeader) + 4) {
|
||||
qDebug() << "[GVSPParser] Leader packet too small:" << size << "bytes";
|
||||
return;
|
||||
}
|
||||
|
||||
const GVSPPacketHeader *header = reinterpret_cast<const GVSPPacketHeader*>(data);
|
||||
m_currentBlockId = ntohs(header->block_id);
|
||||
uint32_t blockId = ntohs(header->block_id);
|
||||
|
||||
// Check payload type
|
||||
const uint16_t *payload_type_ptr = reinterpret_cast<const uint16_t*>(data + sizeof(GVSPPacketHeader) + 2);
|
||||
@@ -94,49 +84,110 @@ void GVSPParser::handleLeaderPacket(const uint8_t *data, size_t size)
|
||||
|
||||
if (payload_type == PAYLOAD_TYPE_IMAGE) {
|
||||
// Image data leader
|
||||
if (size < sizeof(GVSPPacketHeader) + sizeof(GVSPImageDataLeader)) {
|
||||
qDebug() << "[GVSPParser] Image leader too small";
|
||||
return;
|
||||
}
|
||||
const GVSPImageDataLeader *leader = reinterpret_cast<const GVSPImageDataLeader*>(data + sizeof(GVSPPacketHeader));
|
||||
|
||||
m_dataType = 1;
|
||||
m_imageWidth = ntohl(leader->size_x);
|
||||
m_imageHeight = ntohl(leader->size_y);
|
||||
m_pixelFormat = ntohl(leader->pixel_format);
|
||||
m_expectedSize = m_imageWidth * m_imageHeight * 2; // 12-bit packed in 16-bit
|
||||
|
||||
m_dataBuffer.clear();
|
||||
m_dataBuffer.reserve(m_expectedSize);
|
||||
m_receivedSize = 0;
|
||||
m_isReceiving = true;
|
||||
m_packetCount = 0;
|
||||
|
||||
// 注释掉频繁的日志输出
|
||||
// qDebug() << "Image Leader: Block" << m_currentBlockId
|
||||
// << "Size:" << m_imageWidth << "x" << m_imageHeight;
|
||||
uint32_t imageWidth = ntohl(leader->size_x);
|
||||
uint32_t imageHeight = ntohl(leader->size_y);
|
||||
uint32_t pixelFormat = ntohl(leader->pixel_format);
|
||||
|
||||
// 根据像素格式选择对应的状态
|
||||
StreamState *state = nullptr;
|
||||
if (pixelFormat == PIXEL_FORMAT_MONO16_LEFT || pixelFormat == PIXEL_FORMAT_MONO8_LEFT) {
|
||||
state = &m_leftIRState;
|
||||
} else if (pixelFormat == PIXEL_FORMAT_MONO16_RIGHT || pixelFormat == PIXEL_FORMAT_MONO8_RIGHT) {
|
||||
state = &m_rightIRState;
|
||||
} else if (pixelFormat == PIXEL_FORMAT_MJPEG) {
|
||||
state = &m_rgbState;
|
||||
} else if (pixelFormat == PIXEL_FORMAT_MONO16 || pixelFormat == PIXEL_FORMAT_12BIT_GRAY) {
|
||||
// Legacy格式:根据序号分配
|
||||
int imageType = m_imageSequence % 2;
|
||||
state = (imageType == 0) ? &m_leftIRState : &m_rightIRState;
|
||||
}
|
||||
|
||||
if (state) {
|
||||
state->blockId = blockId;
|
||||
state->imageWidth = imageWidth;
|
||||
state->imageHeight = imageHeight;
|
||||
state->pixelFormat = pixelFormat;
|
||||
|
||||
// 根据pixel format设置期望大小
|
||||
if (pixelFormat == PIXEL_FORMAT_MJPEG) {
|
||||
// MJPEG是压缩格式,实际大小未知,设置为0表示动态接收
|
||||
state->expectedSize = 0;
|
||||
} else if (pixelFormat == PIXEL_FORMAT_MONO8_LEFT || pixelFormat == PIXEL_FORMAT_MONO8_RIGHT) {
|
||||
// 8-bit灰度格式(下采样)
|
||||
state->expectedSize = imageWidth * imageHeight;
|
||||
} else {
|
||||
// 16-bit或12-bit灰度等固定格式
|
||||
state->expectedSize = imageWidth * imageHeight * 2;
|
||||
}
|
||||
|
||||
state->dataBuffer.clear();
|
||||
if (state->expectedSize > 0) {
|
||||
state->dataBuffer.reserve(state->expectedSize);
|
||||
}
|
||||
state->receivedSize = 0;
|
||||
state->isReceiving = true;
|
||||
state->packetCount = 0;
|
||||
}
|
||||
}
|
||||
else if (payload_type == PAYLOAD_TYPE_BINARY) {
|
||||
// Depth data leader
|
||||
const GVSPBinaryDataLeader *leader = reinterpret_cast<const GVSPBinaryDataLeader*>(data + sizeof(GVSPPacketHeader));
|
||||
|
||||
m_dataType = 3;
|
||||
m_expectedSize = ntohl(leader->file_size);
|
||||
|
||||
m_dataBuffer.clear();
|
||||
m_dataBuffer.reserve(m_expectedSize);
|
||||
m_receivedSize = 0;
|
||||
m_isReceiving = true;
|
||||
m_packetCount = 0;
|
||||
|
||||
// 注释掉频繁的日志输出
|
||||
// qDebug() << "Depth Leader: Block" << m_currentBlockId
|
||||
// << "Size:" << m_expectedSize << "bytes";
|
||||
m_depthState.blockId = blockId;
|
||||
m_depthState.expectedSize = ntohl(leader->file_size);
|
||||
m_depthState.dataBuffer.clear();
|
||||
m_depthState.dataBuffer.reserve(m_depthState.expectedSize);
|
||||
m_depthState.receivedSize = 0;
|
||||
m_depthState.isReceiving = true;
|
||||
m_depthState.packetCount = 0;
|
||||
}
|
||||
else if (payload_type == PAYLOAD_TYPE_POINTCLOUD) {
|
||||
// Point cloud data leader (vendor-specific 0x8000)
|
||||
const GVSPPointCloudDataLeader *leader = reinterpret_cast<const GVSPPointCloudDataLeader*>(data + sizeof(GVSPPacketHeader));
|
||||
|
||||
m_pointCloudState.blockId = blockId;
|
||||
m_pointCloudState.expectedSize = ntohl(leader->data_size);
|
||||
m_pointCloudState.dataBuffer.clear();
|
||||
m_pointCloudState.dataBuffer.reserve(m_pointCloudState.expectedSize);
|
||||
m_pointCloudState.receivedSize = 0;
|
||||
m_pointCloudState.isReceiving = true;
|
||||
m_pointCloudState.packetCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void GVSPParser::handlePayloadPacket(const uint8_t *data, size_t size)
|
||||
{
|
||||
if (!m_isReceiving) {
|
||||
if (size < sizeof(GVSPPacketHeader)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const GVSPPacketHeader *header = reinterpret_cast<const GVSPPacketHeader*>(data);
|
||||
uint32_t blockId = ntohs(header->block_id);
|
||||
|
||||
// 查找匹配的状态
|
||||
StreamState *state = nullptr;
|
||||
if (m_leftIRState.isReceiving && m_leftIRState.blockId == blockId) {
|
||||
state = &m_leftIRState;
|
||||
} else if (m_rightIRState.isReceiving && m_rightIRState.blockId == blockId) {
|
||||
state = &m_rightIRState;
|
||||
} else if (m_rgbState.isReceiving && m_rgbState.blockId == blockId) {
|
||||
state = &m_rgbState;
|
||||
} else if (m_depthState.isReceiving && m_depthState.blockId == blockId) {
|
||||
state = &m_depthState;
|
||||
} else if (m_pointCloudState.isReceiving && m_pointCloudState.blockId == blockId) {
|
||||
state = &m_pointCloudState;
|
||||
}
|
||||
|
||||
if (!state) {
|
||||
return; // 没有匹配的接收状态
|
||||
}
|
||||
|
||||
if (size <= sizeof(GVSPPacketHeader)) {
|
||||
return;
|
||||
}
|
||||
@@ -146,84 +197,213 @@ void GVSPParser::handlePayloadPacket(const uint8_t *data, size_t size)
|
||||
size_t payload_size = size - sizeof(GVSPPacketHeader);
|
||||
|
||||
// Append to buffer
|
||||
m_dataBuffer.append(reinterpret_cast<const char*>(payload), payload_size);
|
||||
m_receivedSize += payload_size;
|
||||
m_packetCount++;
|
||||
state->dataBuffer.append(reinterpret_cast<const char*>(payload), payload_size);
|
||||
state->receivedSize += payload_size;
|
||||
state->packetCount++;
|
||||
}
|
||||
|
||||
void GVSPParser::handleTrailerPacket(const uint8_t *data, size_t size)
|
||||
{
|
||||
if (!m_isReceiving) {
|
||||
if (size < sizeof(GVSPPacketHeader)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 注释掉频繁的日志输出
|
||||
// qDebug() << "Trailer received: Block" << m_currentBlockId
|
||||
// << "Received" << m_receivedSize << "/" << m_expectedSize << "bytes"
|
||||
// << "Packets:" << m_packetCount;
|
||||
const GVSPPacketHeader *header = reinterpret_cast<const GVSPPacketHeader*>(data);
|
||||
uint32_t blockId = ntohs(header->block_id);
|
||||
|
||||
// Process complete data
|
||||
if (m_dataType == 1) {
|
||||
processImageData();
|
||||
} else if (m_dataType == 3) {
|
||||
processDepthData();
|
||||
// 查找匹配的状态并处理
|
||||
if (m_leftIRState.isReceiving && m_leftIRState.blockId == blockId) {
|
||||
processImageData(&m_leftIRState);
|
||||
m_leftIRState.isReceiving = false;
|
||||
m_lastBlockId = blockId;
|
||||
} else if (m_rightIRState.isReceiving && m_rightIRState.blockId == blockId) {
|
||||
processImageData(&m_rightIRState);
|
||||
m_rightIRState.isReceiving = false;
|
||||
m_lastBlockId = blockId;
|
||||
} else if (m_rgbState.isReceiving && m_rgbState.blockId == blockId) {
|
||||
processImageData(&m_rgbState);
|
||||
m_rgbState.isReceiving = false;
|
||||
m_lastBlockId = blockId;
|
||||
} else if (m_depthState.isReceiving && m_depthState.blockId == blockId) {
|
||||
processDepthData(&m_depthState);
|
||||
m_depthState.isReceiving = false;
|
||||
m_lastBlockId = blockId;
|
||||
} else if (m_pointCloudState.isReceiving && m_pointCloudState.blockId == blockId) {
|
||||
processPointCloudData(&m_pointCloudState);
|
||||
m_pointCloudState.isReceiving = false;
|
||||
m_lastBlockId = blockId;
|
||||
}
|
||||
|
||||
// Reset state
|
||||
m_isReceiving = false;
|
||||
m_lastBlockId = m_currentBlockId;
|
||||
}
|
||||
|
||||
void GVSPParser::processImageData()
|
||||
void GVSPParser::processImageData(GVSPParser::StreamState *state)
|
||||
{
|
||||
if (m_dataBuffer.size() < m_expectedSize) {
|
||||
// 注释掉频繁的警告日志
|
||||
// qDebug() << "Warning: Incomplete image data" << m_dataBuffer.size() << "/" << m_expectedSize;
|
||||
if (!state) return;
|
||||
|
||||
// 处理MJPEG格式(RGB相机)
|
||||
if (state->pixelFormat == PIXEL_FORMAT_MJPEG) {
|
||||
// RGB MJPEG
|
||||
emit rgbImageReceived(state->dataBuffer, state->blockId);
|
||||
m_imageSequence++;
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert 16-bit depth data to 8-bit grayscale for display
|
||||
const uint16_t *src = reinterpret_cast<const uint16_t*>(m_dataBuffer.constData());
|
||||
QImage image(m_imageWidth, m_imageHeight, QImage::Format_Grayscale8);
|
||||
|
||||
// Find min/max for normalization
|
||||
uint16_t minVal = 65535, maxVal = 0;
|
||||
for (size_t i = 0; i < m_imageWidth * m_imageHeight; i++) {
|
||||
uint16_t val = src[i];
|
||||
if (val > 0) {
|
||||
if (val < minVal) minVal = val;
|
||||
if (val > maxVal) maxVal = val;
|
||||
// 处理Mono16格式(左右红外相机原始16位数据)
|
||||
// 使用像素格式直接区分左右,不依赖接收顺序
|
||||
if (state->pixelFormat == PIXEL_FORMAT_MONO16_LEFT) {
|
||||
// 检查数据大小
|
||||
if (state->dataBuffer.size() < state->expectedSize) {
|
||||
return;
|
||||
}
|
||||
// 左红外原始数据
|
||||
emit leftImageReceived(state->dataBuffer, state->blockId);
|
||||
m_imageSequence++;
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalize to 0-255 and flip vertically
|
||||
uint8_t *dst = image.bits();
|
||||
float scale = (maxVal > minVal) ? (255.0f / (maxVal - minVal)) : 0.0f;
|
||||
if (state->pixelFormat == PIXEL_FORMAT_MONO16_RIGHT) {
|
||||
// 检查数据大小
|
||||
if (state->dataBuffer.size() < state->expectedSize) {
|
||||
return;
|
||||
}
|
||||
// 右红外原始数据
|
||||
emit rightImageReceived(state->dataBuffer, state->blockId);
|
||||
m_imageSequence++;
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t y = 0; y < m_imageHeight; y++) {
|
||||
for (size_t x = 0; x < m_imageWidth; x++) {
|
||||
size_t src_idx = y * m_imageWidth + x;
|
||||
size_t dst_idx = (m_imageHeight - 1 - y) * m_imageWidth + x; // 垂直翻转
|
||||
// 处理Mono8格式(左右红外相机下采样8位数据)
|
||||
if (state->pixelFormat == PIXEL_FORMAT_MONO8_LEFT) {
|
||||
// 检查数据大小
|
||||
if (state->dataBuffer.size() < state->expectedSize) {
|
||||
return;
|
||||
}
|
||||
// 左红外8位数据
|
||||
emit leftImageReceived(state->dataBuffer, state->blockId);
|
||||
m_imageSequence++;
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t val = src[src_idx];
|
||||
if (val == 0) {
|
||||
dst[dst_idx] = 0;
|
||||
} else {
|
||||
dst[dst_idx] = static_cast<uint8_t>((val - minVal) * scale);
|
||||
if (state->pixelFormat == PIXEL_FORMAT_MONO8_RIGHT) {
|
||||
// 检查数据大小
|
||||
if (state->dataBuffer.size() < state->expectedSize) {
|
||||
return;
|
||||
}
|
||||
// 右红外8位数据
|
||||
emit rightImageReceived(state->dataBuffer, state->blockId);
|
||||
m_imageSequence++;
|
||||
return;
|
||||
}
|
||||
|
||||
// 兼容旧版本:使用序号区分(legacy)
|
||||
if (state->pixelFormat == PIXEL_FORMAT_MONO16) {
|
||||
// 检查数据大小
|
||||
if (state->dataBuffer.size() < state->expectedSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据图像序号区分:偶数=左红外,奇数=右红外
|
||||
int imageType = m_imageSequence % 2;
|
||||
|
||||
if (imageType == 0) {
|
||||
// 左红外原始数据
|
||||
emit leftImageReceived(state->dataBuffer, state->blockId);
|
||||
} else {
|
||||
// 右红外原始数据
|
||||
emit rightImageReceived(state->dataBuffer, state->blockId);
|
||||
}
|
||||
|
||||
m_imageSequence++;
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理12-bit灰度格式 - 固定大小,需要检查
|
||||
if (state->dataBuffer.size() < state->expectedSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理12-bit灰度图像(左右红外相机)
|
||||
if (state->pixelFormat != PIXEL_FORMAT_12BIT_GRAY) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 节流机制:如果已有3个或更多图像在处理中,跳过当前帧
|
||||
if (m_imageProcessingCount.loadAcquire() >= 3) {
|
||||
m_imageSequence++;
|
||||
return;
|
||||
}
|
||||
|
||||
// 增加处理计数
|
||||
m_imageProcessingCount.ref();
|
||||
|
||||
// 复制数据到局部变量
|
||||
QByteArray dataCopy = state->dataBuffer;
|
||||
uint32_t blockId = state->blockId;
|
||||
size_t width = state->imageWidth;
|
||||
size_t height = state->imageHeight;
|
||||
int imageSeq = m_imageSequence;
|
||||
|
||||
// 使用QtConcurrent在后台线程处理图像数据
|
||||
QtConcurrent::run([this, dataCopy, blockId, width, height, imageSeq]() {
|
||||
// Convert 16-bit depth data to 8-bit grayscale for display
|
||||
const uint16_t *src = reinterpret_cast<const uint16_t*>(dataCopy.constData());
|
||||
QImage image(width, height, QImage::Format_Grayscale8);
|
||||
|
||||
// 优化:使用采样方式快速估算min/max(每隔16个像素采样一次)
|
||||
uint16_t minVal = 65535, maxVal = 0;
|
||||
size_t totalPixels = width * height;
|
||||
const size_t sampleStep = 16; // 采样步长
|
||||
|
||||
for (size_t i = 0; i < totalPixels; i += sampleStep) {
|
||||
uint16_t val = src[i];
|
||||
if (val > 0) {
|
||||
if (val < minVal) minVal = val;
|
||||
if (val > maxVal) maxVal = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit imageReceived(image, m_currentBlockId);
|
||||
// 归一化(不翻转,保持原始方向)
|
||||
uint8_t *dst = image.bits();
|
||||
float scale = (maxVal > minVal) ? (255.0f / (maxVal - minVal)) : 0.0f;
|
||||
|
||||
for (size_t row = 0; row < height; row++) {
|
||||
for (size_t col = 0; col < width; col++) {
|
||||
size_t idx = row * width + col;
|
||||
uint16_t val = src[idx];
|
||||
dst[idx] = (val == 0) ? 0 : static_cast<uint8_t>((val - minVal) * scale);
|
||||
}
|
||||
}
|
||||
|
||||
// 注意:此代码路径已废弃,下位机现在发送JPEG格式
|
||||
// 仅保留兼容旧代码的信号
|
||||
emit imageReceived(image, blockId);
|
||||
|
||||
// 减少处理计数
|
||||
m_imageProcessingCount.deref();
|
||||
});
|
||||
|
||||
// 增加图像序号
|
||||
m_imageSequence++;
|
||||
}
|
||||
|
||||
void GVSPParser::processDepthData()
|
||||
void GVSPParser::processDepthData(GVSPParser::StreamState *state)
|
||||
{
|
||||
if (m_dataBuffer.size() < m_expectedSize) {
|
||||
// 注释掉频繁的警告日志
|
||||
// qDebug() << "Warning: Incomplete depth data" << m_dataBuffer.size() << "/" << m_expectedSize;
|
||||
if (!state) return;
|
||||
|
||||
if (state->dataBuffer.size() < state->expectedSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit depthDataReceived(m_dataBuffer, m_currentBlockId);
|
||||
emit depthDataReceived(state->dataBuffer, state->blockId);
|
||||
}
|
||||
|
||||
void GVSPParser::processPointCloudData(GVSPParser::StreamState *state)
|
||||
{
|
||||
if (!state) return;
|
||||
|
||||
if (state->dataBuffer.size() < state->expectedSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 点云数据直接发送,格式为 short 数组 (x, y, z, x, y, z, ...)
|
||||
emit pointCloudDataReceived(state->dataBuffer, state->blockId);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ Logger* Logger::instance()
|
||||
|
||||
Logger::Logger(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_maxLines(10000) // 保留最新10000行
|
||||
, m_maxLines(100000) // 保留最新10000行
|
||||
, m_currentLines(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -18,7 +18,11 @@ NetworkManager::NetworkManager(QObject *parent)
|
||||
|
||||
// 连接GVSP解析器信号
|
||||
connect(m_gvspParser, &GVSPParser::imageReceived, this, &NetworkManager::imageReceived);
|
||||
connect(m_gvspParser, &GVSPParser::leftImageReceived, this, &NetworkManager::leftImageReceived);
|
||||
connect(m_gvspParser, &GVSPParser::rightImageReceived, this, &NetworkManager::rightImageReceived);
|
||||
connect(m_gvspParser, &GVSPParser::rgbImageReceived, this, &NetworkManager::rgbImageReceived);
|
||||
connect(m_gvspParser, &GVSPParser::depthDataReceived, this, &NetworkManager::depthDataReceived);
|
||||
connect(m_gvspParser, &GVSPParser::pointCloudDataReceived, this, &NetworkManager::pointCloudDataReceived);
|
||||
}
|
||||
|
||||
NetworkManager::~NetworkManager()
|
||||
@@ -34,7 +38,7 @@ bool NetworkManager::connectToCamera(const QString &ip, int controlPort, int dat
|
||||
m_dataPort = dataPort;
|
||||
|
||||
// 绑定控制Socket到任意端口(让系统自动分配)
|
||||
if (!m_controlSocket->bind(QHostAddress::Any, 0)) {
|
||||
if(!m_controlSocket->bind(QHostAddress::Any, 0)) {
|
||||
QString error = QString("Failed to bind control socket: %1")
|
||||
.arg(m_controlSocket->errorString());
|
||||
qDebug() << error;
|
||||
@@ -44,7 +48,7 @@ bool NetworkManager::connectToCamera(const QString &ip, int controlPort, int dat
|
||||
qDebug() << "Successfully bound control socket to port" << m_controlSocket->localPort();
|
||||
|
||||
// 绑定数据接收端口
|
||||
if (!m_dataSocket->bind(QHostAddress::Any, m_dataPort)) {
|
||||
if(!m_dataSocket->bind(QHostAddress::Any, m_dataPort)) {
|
||||
QString error = QString("Failed to bind data port %1: %2")
|
||||
.arg(m_dataPort)
|
||||
.arg(m_dataSocket->errorString());
|
||||
@@ -53,8 +57,8 @@ bool NetworkManager::connectToCamera(const QString &ip, int controlPort, int dat
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置UDP接收缓冲区大小为64MB(减少丢包)
|
||||
m_dataSocket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, QVariant(64 * 1024 * 1024));
|
||||
// 设置UDP接收缓冲区大小为256MB(最大化,减少丢包)
|
||||
m_dataSocket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, QVariant(256 * 1024 * 1024));
|
||||
|
||||
qDebug() << "Successfully bound data port" << m_dataPort;
|
||||
qDebug() << "Data socket state:" << m_dataSocket->state();
|
||||
@@ -73,7 +77,7 @@ bool NetworkManager::connectToCamera(const QString &ip, int controlPort, int dat
|
||||
|
||||
void NetworkManager::disconnectFromCamera()
|
||||
{
|
||||
if (m_isConnected) {
|
||||
if(m_isConnected) {
|
||||
m_controlSocket->close();
|
||||
m_dataSocket->close();
|
||||
m_isConnected = false;
|
||||
@@ -90,7 +94,7 @@ bool NetworkManager::isConnected() const
|
||||
// ========== 发送控制命令 ==========
|
||||
bool NetworkManager::sendCommand(const QString &command)
|
||||
{
|
||||
if (!m_isConnected) {
|
||||
if(!m_isConnected) {
|
||||
qDebug() << "Not connected to camera";
|
||||
return false;
|
||||
}
|
||||
@@ -105,7 +109,7 @@ bool NetworkManager::sendCommand(const QString &command)
|
||||
qint64 sent = m_controlSocket->writeDatagram(data, QHostAddress(m_cameraIp), m_controlPort);
|
||||
|
||||
qDebug() << "writeDatagram returned:" << sent;
|
||||
if (sent == -1) {
|
||||
if(sent == -1) {
|
||||
QString error = QString("Failed to send command: %1").arg(m_controlSocket->errorString());
|
||||
qDebug() << error;
|
||||
qDebug() << "Socket error code:" << m_controlSocket->error();
|
||||
@@ -128,15 +132,51 @@ bool NetworkManager::sendStopCommand()
|
||||
return sendCommand("STOP");
|
||||
}
|
||||
|
||||
bool NetworkManager::sendOnceCommand()
|
||||
{
|
||||
return sendCommand("ONCE");
|
||||
}
|
||||
|
||||
bool NetworkManager::sendExposureCommand(int exposureTime)
|
||||
{
|
||||
QString command = QString("EXPOSURE:%1").arg(exposureTime);
|
||||
return sendCommand(command);
|
||||
QString exposureCommand = QString("EXPOSURE:%1").arg(exposureTime);
|
||||
return sendCommand(exposureCommand);
|
||||
}
|
||||
|
||||
// ========== 传输开关命令 ==========
|
||||
bool NetworkManager::sendEnableLeftIR()
|
||||
{
|
||||
return sendCommand("ENABLE_LEFT");
|
||||
}
|
||||
|
||||
bool NetworkManager::sendDisableLeftIR()
|
||||
{
|
||||
return sendCommand("DISABLE_LEFT");
|
||||
}
|
||||
|
||||
bool NetworkManager::sendEnableRightIR()
|
||||
{
|
||||
return sendCommand("ENABLE_RIGHT");
|
||||
}
|
||||
|
||||
bool NetworkManager::sendDisableRightIR()
|
||||
{
|
||||
return sendCommand("DISABLE_RIGHT");
|
||||
}
|
||||
|
||||
bool NetworkManager::sendEnableRGB()
|
||||
{
|
||||
return sendCommand("ENABLE_RGB");
|
||||
}
|
||||
|
||||
bool NetworkManager::sendDisableRGB()
|
||||
{
|
||||
return sendCommand("DISABLE_RGB");
|
||||
}
|
||||
|
||||
bool NetworkManager::sendMonocularMode()
|
||||
{
|
||||
return sendCommand("MONOCULAR");
|
||||
}
|
||||
|
||||
bool NetworkManager::sendBinocularMode()
|
||||
{
|
||||
return sendCommand("BINOCULAR");
|
||||
}
|
||||
|
||||
// ========== 槽函数 ==========
|
||||
@@ -152,9 +192,11 @@ void NetworkManager::onReadyRead()
|
||||
quint16 senderPort;
|
||||
m_dataSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
|
||||
|
||||
// 临时添加日志以诊断问题
|
||||
if (packetCount < 5) {
|
||||
qDebug() << "[NetworkManager] Received packet" << packetCount << "from" << sender.toString() << ":" << senderPort << "size:" << datagram.size();
|
||||
// 只打印前5个包的详细信息
|
||||
if(packetCount < 5) {
|
||||
// qDebug() << "[NetworkManager] Packet" << packetCount
|
||||
// << "from" << sender.toString() << ":" << senderPort
|
||||
// << "size:" << datagram.size() << "bytes";
|
||||
}
|
||||
packetCount++;
|
||||
|
||||
@@ -164,6 +206,11 @@ void NetworkManager::onReadyRead()
|
||||
// 仍然发出原始数据信号(用于调试)
|
||||
emit dataReceived(datagram);
|
||||
}
|
||||
|
||||
// 每1000个包打印一次统计(减少日志量)
|
||||
if(packetCount % 1000 == 0) {
|
||||
// qDebug() << "[NetworkManager] Total packets received:" << packetCount;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkManager::onError(QAbstractSocket::SocketError socketError)
|
||||
|
||||
@@ -26,16 +26,31 @@ public:
|
||||
bool sendCommand(const QString &command);
|
||||
bool sendStartCommand();
|
||||
bool sendStopCommand();
|
||||
bool sendOnceCommand();
|
||||
bool sendExposureCommand(int exposureTime);
|
||||
|
||||
// 发送传输开关命令
|
||||
bool sendEnableLeftIR();
|
||||
bool sendDisableLeftIR();
|
||||
bool sendEnableRightIR();
|
||||
bool sendDisableRightIR();
|
||||
bool sendEnableRGB();
|
||||
bool sendDisableRGB();
|
||||
|
||||
// 发送单目/双目模式切换命令
|
||||
bool sendMonocularMode();
|
||||
bool sendBinocularMode();
|
||||
|
||||
signals:
|
||||
void connected();
|
||||
void disconnected();
|
||||
void errorOccurred(const QString &error);
|
||||
void dataReceived(const QByteArray &data);
|
||||
void imageReceived(const QImage &image, uint32_t blockId);
|
||||
void leftImageReceived(const QByteArray &jpegData, uint32_t blockId);
|
||||
void rightImageReceived(const QByteArray &jpegData, uint32_t blockId);
|
||||
void rgbImageReceived(const QByteArray &jpegData, uint32_t blockId);
|
||||
void depthDataReceived(const QByteArray &depthData, uint32_t blockId);
|
||||
void pointCloudDataReceived(const QByteArray &cloudData, uint32_t blockId);
|
||||
|
||||
private slots:
|
||||
void onReadyRead();
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#include "core/PointCloudProcessor.h"
|
||||
#include <QDebug>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
PointCloudProcessor::PointCloudProcessor(QObject *parent)
|
||||
: QObject(parent)
|
||||
@@ -103,7 +108,7 @@ bool PointCloudProcessor::initializeOpenCL()
|
||||
"float z = depth[idx] * z_scale; "
|
||||
// 完全平面的圆柱投影:X和Y直接使用像素坐标,缩放到合适的范围
|
||||
"xyz[idx*3] = (x - cx) * 2.0f; " // X坐标,缩放系数2.0
|
||||
"xyz[idx*3+1] = (y - cy) * 2.0f; " // Y坐标,缩放系数2.0
|
||||
"xyz[idx*3+1] = -(y - cy) * 2.0f; " // Y坐标取反,修正上下颠倒
|
||||
"xyz[idx*3+2] = z; "
|
||||
"}";
|
||||
|
||||
@@ -246,6 +251,71 @@ void PointCloudProcessor::processDepthData(const QByteArray &depthData, uint32_t
|
||||
emit pointCloudReady(cloud, blockId);
|
||||
}
|
||||
|
||||
void PointCloudProcessor::processPointCloudData(const QByteArray &cloudData, uint32_t blockId)
|
||||
{
|
||||
// qDebug() << "[PointCloud] Processing pre-computed point cloud data";
|
||||
// qDebug() << "[PointCloud] Data size:" << cloudData.size() << "bytes";
|
||||
|
||||
// 验证数据大小:支持两种格式
|
||||
// 格式1:只有Z坐标 (width * height * sizeof(int16_t))
|
||||
// 格式2:完整XYZ (width * height * 3 * sizeof(int16_t))
|
||||
size_t expectedSizeZ = m_imageWidth * m_imageHeight * sizeof(int16_t);
|
||||
size_t expectedSizeXYZ = m_imageWidth * m_imageHeight * 3 * sizeof(int16_t);
|
||||
|
||||
bool isZOnly = (cloudData.size() == expectedSizeZ);
|
||||
bool isXYZ = (cloudData.size() == expectedSizeXYZ);
|
||||
|
||||
if (!isZOnly && !isXYZ) {
|
||||
qDebug() << "[PointCloud] ERROR: Invalid point cloud data size:" << cloudData.size()
|
||||
<< "expected:" << expectedSizeZ << "(Z only) or" << expectedSizeXYZ << "(XYZ)";
|
||||
return;
|
||||
}
|
||||
|
||||
// qDebug() << "[PointCloud] Data format:" << (isZOnly ? "Z only" : "XYZ");
|
||||
|
||||
// 创建PCL点云
|
||||
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
|
||||
cloud->width = m_imageWidth;
|
||||
cloud->height = m_imageHeight;
|
||||
cloud->is_dense = false;
|
||||
cloud->points.resize(m_totalPoints);
|
||||
|
||||
// 从int16_t数组读取点云数据
|
||||
const int16_t* cloudShort = reinterpret_cast<const int16_t*>(cloudData.constData());
|
||||
|
||||
if (isZOnly) {
|
||||
// Z-only格式:转换为正交投影(柱形)
|
||||
for (size_t i = 0; i < m_totalPoints; i++) {
|
||||
int row = i / m_imageWidth;
|
||||
int col = i % m_imageWidth;
|
||||
|
||||
// 读取深度值(单位:毫米)
|
||||
float z = static_cast<float>(cloudShort[i]) * m_zScale;
|
||||
|
||||
// 正交投影:X、Y使用像素坐标(Y轴翻转以修正镜像)
|
||||
cloud->points[i].x = static_cast<float>(col);
|
||||
cloud->points[i].y = static_cast<float>(m_imageHeight - 1 - row);
|
||||
cloud->points[i].z = z;
|
||||
}
|
||||
} else {
|
||||
// XYZ格式:完整的三维坐标
|
||||
// 转换为正交投影(柱形),使用像素坐标作为X、Y
|
||||
for (size_t i = 0; i < m_totalPoints; i++) {
|
||||
int row = i / m_imageWidth;
|
||||
int col = i % m_imageWidth;
|
||||
|
||||
// 正交投影:X、Y使用像素坐标(Y轴翻转以修正镜像),Z使用深度值
|
||||
cloud->points[i].x = static_cast<float>(col);
|
||||
cloud->points[i].y = static_cast<float>(m_imageHeight - 1 - row);
|
||||
cloud->points[i].z = static_cast<float>(cloudShort[i * 3 + 2]) * m_zScale;
|
||||
}
|
||||
}
|
||||
|
||||
// qDebug() << "[PointCloud] Block" << blockId << "processed successfully,"
|
||||
// << m_totalPoints << "points";
|
||||
emit pointCloudReady(cloud, blockId);
|
||||
}
|
||||
|
||||
void PointCloudProcessor::cleanupOpenCL()
|
||||
{
|
||||
if (m_depthBuffer) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,6 @@ private slots:
|
||||
// 相机控制槽函数
|
||||
void onStartClicked();
|
||||
void onStopClicked();
|
||||
void onOnceClicked();
|
||||
void onExposureChanged(int value);
|
||||
void onCaptureClicked();
|
||||
void onBrowseSavePathClicked();
|
||||
@@ -51,7 +50,11 @@ private slots:
|
||||
|
||||
// GVSP数据处理槽函数
|
||||
void onImageReceived(const QImage &image, uint32_t blockId);
|
||||
void onLeftImageReceived(const QByteArray &jpegData, uint32_t blockId);
|
||||
void onRightImageReceived(const QByteArray &jpegData, uint32_t blockId);
|
||||
void onRgbImageReceived(const QByteArray &jpegData, uint32_t blockId);
|
||||
void onDepthDataReceived(const QByteArray &depthData, uint32_t blockId);
|
||||
void onPointCloudDataReceived(const QByteArray &cloudData, uint32_t blockId);
|
||||
void onPointCloudReady(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud, uint32_t blockId);
|
||||
|
||||
// 设备扫描槽函数
|
||||
@@ -83,7 +86,9 @@ private:
|
||||
bool savePointCloud(const QString &dir, const QString &baseName, const QString &format);
|
||||
void performBackgroundSave(const QString &saveDir, const QString &baseName,
|
||||
const QString &depthFormat, const QString &pointCloudFormat,
|
||||
const QImage &image, pcl::PointCloud<pcl::PointXYZ>::Ptr cloud);
|
||||
const QImage &image, pcl::PointCloud<pcl::PointXYZ>::Ptr cloud,
|
||||
const QByteArray &leftIRData, const QByteArray &rightIRData,
|
||||
const QByteArray &rgbData);
|
||||
|
||||
// 日志辅助函数
|
||||
void addLog(const QString &message, const QString &level = "INFO");
|
||||
@@ -101,28 +106,46 @@ private:
|
||||
|
||||
// 状态变量
|
||||
bool m_isConnected;
|
||||
bool m_autoSaveOnNextFrame;
|
||||
|
||||
// 当前帧数据(用于拍照)
|
||||
QImage m_currentImage;
|
||||
pcl::PointCloud<pcl::PointXYZ>::Ptr m_currentPointCloud;
|
||||
uint32_t m_currentFrameId;
|
||||
|
||||
// 三个相机的原始JPEG数据(用于保存完整分辨率图像)
|
||||
QByteArray m_currentLeftIRData;
|
||||
QByteArray m_currentRightIRData;
|
||||
QByteArray m_currentRgbData;
|
||||
|
||||
// UI控件指针
|
||||
class QWidget *m_centralWidget;
|
||||
class QSplitter *m_mainSplitter;
|
||||
class QWidget *m_controlPanel;
|
||||
class QLabel *m_imageDisplay;
|
||||
class QLabel *m_imageDisplay; // 兼容旧代码
|
||||
PointCloudWidget *m_pointCloudWidget;
|
||||
|
||||
// 三个图像显示控件
|
||||
QLabel *m_leftImageDisplay;
|
||||
QLabel *m_rightImageDisplay;
|
||||
QLabel *m_rgbImageDisplay;
|
||||
|
||||
// 按钮控件
|
||||
QPushButton *m_refreshBtn;
|
||||
QPushButton *m_connectBtn;
|
||||
QPushButton *m_startBtn;
|
||||
QPushButton *m_stopBtn;
|
||||
QPushButton *m_onceBtn;
|
||||
QPushButton *m_captureBtn;
|
||||
|
||||
|
||||
// 数据传输开关按钮
|
||||
QPushButton *m_leftIRToggle;
|
||||
QPushButton *m_rightIRToggle;
|
||||
QPushButton *m_rgbToggle;
|
||||
QPushButton *m_pointCloudColorToggle; // 点云颜色开关
|
||||
|
||||
// 单目/双目模式切换按钮
|
||||
QPushButton *m_monocularBtn;
|
||||
QPushButton *m_binocularBtn;
|
||||
|
||||
// 输入控件
|
||||
QSlider *m_exposureSlider;
|
||||
QSpinBox *m_exposureSpinBox;
|
||||
@@ -140,20 +163,61 @@ private:
|
||||
QListWidget *m_deviceList;
|
||||
|
||||
// 统计信息控件
|
||||
QLabel *m_fpsLabel;
|
||||
QLabel *m_depthFpsLabel;
|
||||
QLabel *m_pointCloudFpsLabel;
|
||||
QLabel *m_resolutionLabel;
|
||||
QLabel *m_queueLabel;
|
||||
|
||||
// 三个相机的FPS标签
|
||||
QLabel *m_leftFpsLabel;
|
||||
QLabel *m_rightFpsLabel;
|
||||
QLabel *m_rgbFpsLabel;
|
||||
|
||||
// 日志显示控件
|
||||
class QTextEdit *m_logDisplay;
|
||||
QPushButton *m_clearLogBtn;
|
||||
QPushButton *m_saveLogBtn;
|
||||
|
||||
// 统计数据
|
||||
QDateTime m_lastFrameTime;
|
||||
int m_frameCount;
|
||||
int m_totalFrameCount;
|
||||
double m_currentFps;
|
||||
// 统计数据 - 深度图
|
||||
QDateTime m_lastDepthFrameTime;
|
||||
int m_depthFrameCount;
|
||||
int m_totalDepthFrameCount;
|
||||
double m_currentDepthFps;
|
||||
|
||||
// 统计数据 - 点云
|
||||
QDateTime m_lastPointCloudFrameTime;
|
||||
int m_pointCloudFrameCount;
|
||||
int m_totalPointCloudFrameCount;
|
||||
double m_currentPointCloudFps;
|
||||
|
||||
// 统计数据 - 左红外
|
||||
QDateTime m_lastLeftFrameTime;
|
||||
int m_leftFrameCount;
|
||||
int m_totalLeftFrameCount;
|
||||
double m_currentLeftFps;
|
||||
|
||||
// 统计数据 - 右红外
|
||||
QDateTime m_lastRightFrameTime;
|
||||
int m_rightFrameCount;
|
||||
int m_totalRightFrameCount;
|
||||
double m_currentRightFps;
|
||||
|
||||
// 统计数据 - RGB
|
||||
QDateTime m_lastRgbFrameTime;
|
||||
int m_rgbFrameCount;
|
||||
int m_totalRgbFrameCount;
|
||||
double m_currentRgbFps;
|
||||
|
||||
// 解码处理标志(防止线程积压导致闪烁)
|
||||
QAtomicInt m_rgbProcessing;
|
||||
QAtomicInt m_leftIRProcessing;
|
||||
QAtomicInt m_rightIRProcessing;
|
||||
int m_rgbSkipCounter; // RGB帧跳过计数器
|
||||
|
||||
// 相机启用状态标志(防止关闭后闪烁)
|
||||
QAtomicInt m_leftIREnabled;
|
||||
QAtomicInt m_rightIREnabled;
|
||||
QAtomicInt m_rgbEnabled;
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
void MainWindow::onDataReceived(const QByteArray &data)
|
||||
{
|
||||
static int packetCount = 0;
|
||||
packetCount++;
|
||||
|
||||
if (packetCount % 100 == 0) {
|
||||
qDebug() << "Received" << packetCount << "packets, latest size:" << data.size() << "bytes";
|
||||
}
|
||||
}
|
||||
@@ -9,12 +9,19 @@ PointCloudGLWidget::PointCloudGLWidget(QWidget *parent)
|
||||
, m_vertexBuffer(nullptr)
|
||||
, m_vao(nullptr)
|
||||
, m_pointCount(0)
|
||||
, m_orthoSize(2000.0f) // 正交投影视野大小
|
||||
, m_minZ(0.0f)
|
||||
, m_maxZ(1000.0f)
|
||||
, m_fov(60.0f) // 透视投影视场角
|
||||
, m_rotationX(0.0f) // 从正面看(0度)
|
||||
, m_rotationY(0.0f) // 从正面看(0度)
|
||||
, m_translation(0.0f, 0.0f, 0.0f)
|
||||
, m_rotationY(0.0f) // 不旋转Y轴
|
||||
, m_cloudCenter(0.0f, 0.0f, 0.0f)
|
||||
, m_viewDistance(1000.0f)
|
||||
, m_panOffset(0.0f, 0.0f, 0.0f)
|
||||
, m_zoom(1.0f) // 缩放因子
|
||||
, m_leftButtonPressed(false)
|
||||
, m_rightButtonPressed(false)
|
||||
, m_firstFrame(true)
|
||||
, m_colorMode(0) // 默认黑白模式
|
||||
{
|
||||
setMinimumSize(400, 400);
|
||||
}
|
||||
@@ -40,7 +47,7 @@ void PointCloudGLWidget::initializeGL()
|
||||
{
|
||||
initializeOpenGLFunctions();
|
||||
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClearColor(0.1f, 0.1f, 0.15f, 1.0f); // 深灰色背景
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_PROGRAM_POINT_SIZE);
|
||||
|
||||
@@ -65,18 +72,39 @@ void PointCloudGLWidget::setupShaders()
|
||||
#version 330 core
|
||||
layout(location = 0) in vec3 position;
|
||||
uniform mat4 mvp;
|
||||
uniform float minZ;
|
||||
uniform float maxZ;
|
||||
out float depth;
|
||||
void main() {
|
||||
gl_Position = mvp * vec4(position, 1.0);
|
||||
gl_PointSize = 2.0;
|
||||
gl_PointSize = 1.0;
|
||||
depth = (position.z - minZ) / (maxZ - minZ);
|
||||
}
|
||||
)";
|
||||
|
||||
// 片段着色器
|
||||
// 片段着色器 - 支持黑白和彩色两种模式
|
||||
const char *fragmentShaderSource = R"(
|
||||
#version 330 core
|
||||
in float depth;
|
||||
uniform int colorMode;
|
||||
out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
float d = clamp(depth, 0.0, 1.0);
|
||||
if (colorMode == 0) {
|
||||
fragColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
} else {
|
||||
vec3 color;
|
||||
if (d < 0.25) {
|
||||
color = mix(vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 1.0), d * 4.0);
|
||||
} else if (d < 0.5) {
|
||||
color = mix(vec3(0.0, 1.0, 1.0), vec3(0.0, 1.0, 0.0), (d - 0.25) * 4.0);
|
||||
} else if (d < 0.75) {
|
||||
color = mix(vec3(0.0, 1.0, 0.0), vec3(1.0, 1.0, 0.0), (d - 0.5) * 4.0);
|
||||
} else {
|
||||
color = mix(vec3(1.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0), (d - 0.75) * 4.0);
|
||||
}
|
||||
fragColor = vec4(color, 1.0);
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
@@ -97,11 +125,9 @@ void PointCloudGLWidget::resizeGL(int w, int h)
|
||||
{
|
||||
m_projection.setToIdentity();
|
||||
|
||||
// 使用正交投影代替透视投影,避免"喷射状"效果
|
||||
// 使用透视投影,从相机原点看向Z轴正方向
|
||||
float aspect = float(w) / float(h);
|
||||
m_projection.ortho(-m_orthoSize * aspect, m_orthoSize * aspect,
|
||||
-m_orthoSize, m_orthoSize,
|
||||
-50000.0f, 50000.0f); // 近平面和远平面
|
||||
m_projection.perspective(m_fov, aspect, 1.0f, 50000.0f);
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::paintGL()
|
||||
@@ -112,18 +138,22 @@ void PointCloudGLWidget::paintGL()
|
||||
return;
|
||||
}
|
||||
|
||||
// 每帧重新计算正交投影矩阵(确保使用最新的m_orthoSize)
|
||||
// 重新计算透视投影矩阵
|
||||
m_projection.setToIdentity();
|
||||
float aspect = float(width()) / float(height());
|
||||
m_projection.ortho(-m_orthoSize * aspect, m_orthoSize * aspect,
|
||||
-m_orthoSize, m_orthoSize,
|
||||
-50000.0f, 50000.0f);
|
||||
m_projection.perspective(m_fov / m_zoom, aspect, 1.0f, 50000.0f);
|
||||
|
||||
// 设置view矩阵
|
||||
// 设置view矩阵 - 轨道相机模式(围绕点云中心旋转)
|
||||
m_view.setToIdentity();
|
||||
// 1. 用户平移偏移
|
||||
m_view.translate(m_panOffset);
|
||||
// 2. 相机后退到观察距离
|
||||
m_view.translate(0.0f, 0.0f, -m_viewDistance);
|
||||
// 3. 应用旋转(围绕原点,即点云中心)
|
||||
m_view.rotate(m_rotationX, 1.0f, 0.0f, 0.0f);
|
||||
m_view.rotate(m_rotationY, 0.0f, 1.0f, 0.0f);
|
||||
m_view.translate(m_translation);
|
||||
// 4. 将点云中心移到原点
|
||||
m_view.translate(-m_cloudCenter);
|
||||
|
||||
// 设置model矩阵
|
||||
m_model.setToIdentity();
|
||||
@@ -134,6 +164,9 @@ void PointCloudGLWidget::paintGL()
|
||||
// 绑定shader和设置uniform
|
||||
m_program->bind();
|
||||
m_program->setUniformValue("mvp", mvp);
|
||||
m_program->setUniformValue("minZ", m_minZ);
|
||||
m_program->setUniformValue("maxZ", m_maxZ);
|
||||
m_program->setUniformValue("colorMode", m_colorMode);
|
||||
|
||||
// 绑定VAO和绘制
|
||||
m_vao->bind();
|
||||
@@ -164,10 +197,10 @@ void PointCloudGLWidget::mouseMoveEvent(QMouseEvent *event)
|
||||
m_rotationY += delta.x() * 0.5f;
|
||||
update();
|
||||
} else if (m_rightButtonPressed) {
|
||||
// 右键:平移(根据正交投影视野大小调整平移速度)
|
||||
float scale = m_orthoSize * 0.002f;
|
||||
m_translation.setX(m_translation.x() + delta.x() * scale);
|
||||
m_translation.setY(m_translation.y() - delta.y() * scale);
|
||||
// 右键:平移(根据观察距离调整平移速度)
|
||||
float scale = m_viewDistance * 0.002f;
|
||||
m_panOffset.setX(m_panOffset.x() + delta.x() * scale);
|
||||
m_panOffset.setY(m_panOffset.y() - delta.y() * scale);
|
||||
update();
|
||||
}
|
||||
}
|
||||
@@ -183,12 +216,12 @@ void PointCloudGLWidget::mouseReleaseEvent(QMouseEvent *event)
|
||||
|
||||
void PointCloudGLWidget::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
// 滚轮:缩放(调整正交投影视野大小)
|
||||
// 滚轮:缩放(调整zoom因子)
|
||||
float delta = event->angleDelta().y() / 120.0f;
|
||||
m_orthoSize *= (1.0f - delta * 0.1f);
|
||||
m_orthoSize = qMax(100.0f, qMin(m_orthoSize, 10000.0f)); // 范围:100-10000
|
||||
m_zoom *= (1.0f + delta * 0.1f);
|
||||
m_zoom = qMax(0.1f, qMin(m_zoom, 10.0f)); // 范围:0.1-10倍
|
||||
|
||||
update(); // 触发重绘,paintGL会使用新的m_orthoSize
|
||||
update();
|
||||
}
|
||||
|
||||
void PointCloudGLWidget::updatePointCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud)
|
||||
@@ -197,8 +230,9 @@ void PointCloudGLWidget::updatePointCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr cl
|
||||
return;
|
||||
}
|
||||
|
||||
// 过滤全零点并转换为顶点数组
|
||||
// 过滤全零点并转换为顶点数组,同时计算包围盒
|
||||
m_vertices.clear();
|
||||
|
||||
float minX = FLT_MAX, maxX = -FLT_MAX;
|
||||
float minY = FLT_MAX, maxY = -FLT_MAX;
|
||||
float minZ = FLT_MAX, maxZ = -FLT_MAX;
|
||||
@@ -209,7 +243,7 @@ void PointCloudGLWidget::updatePointCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr cl
|
||||
m_vertices.push_back(point.y);
|
||||
m_vertices.push_back(point.z);
|
||||
|
||||
// 统计坐标范围
|
||||
// 更新包围盒
|
||||
if (point.x < minX) minX = point.x;
|
||||
if (point.x > maxX) maxX = point.x;
|
||||
if (point.y < minY) minY = point.y;
|
||||
@@ -221,16 +255,43 @@ void PointCloudGLWidget::updatePointCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr cl
|
||||
|
||||
m_pointCount = m_vertices.size() / 3;
|
||||
|
||||
// 添加调试日志
|
||||
static int updateCount = 0;
|
||||
if (updateCount < 3 || updateCount % 100 == 0) {
|
||||
qDebug() << "[PointCloudGLWidget] Update" << updateCount << "- Points:" << m_pointCount
|
||||
<< "Total cloud size:" << cloud->size();
|
||||
qDebug() << " X range:" << minX << "to" << maxX;
|
||||
qDebug() << " Y range:" << minY << "to" << maxY;
|
||||
qDebug() << " Z range:" << minZ << "to" << maxZ;
|
||||
// 保存深度范围用于着色
|
||||
if (m_pointCount > 0) {
|
||||
m_minZ = minZ;
|
||||
m_maxZ = maxZ;
|
||||
}
|
||||
|
||||
// 只在首帧时自动调整相机位置,之后保持用户交互状态
|
||||
if (m_pointCount > 0 && m_firstFrame) {
|
||||
// 计算点云中心
|
||||
float centerX = (minX + maxX) / 2.0f;
|
||||
float centerY = (minY + maxY) / 2.0f;
|
||||
float centerZ = (minZ + maxZ) / 2.0f;
|
||||
|
||||
// 计算点云尺寸
|
||||
float depthRange = maxZ - minZ;
|
||||
float sizeX = maxX - minX;
|
||||
float sizeY = maxY - minY;
|
||||
float maxSize = std::max({sizeX, sizeY, depthRange});
|
||||
|
||||
// 设置点云中心
|
||||
m_cloudCenter = QVector3D(centerX, centerY, centerZ);
|
||||
|
||||
// 计算观察距离,让相机从外部观察点云
|
||||
m_viewDistance = maxSize * 1.5f;
|
||||
|
||||
// 重置平移偏移和旋转角度
|
||||
m_panOffset = QVector3D(0.0f, 0.0f, 0.0f);
|
||||
m_rotationX = 0.0f;
|
||||
m_rotationY = 0.0f;
|
||||
|
||||
// 设置缩放
|
||||
m_zoom = 1.0f;
|
||||
|
||||
qDebug() << "[PointCloudGLWidget] 首帧自动居中 - 点云中心:" << centerX << centerY << centerZ
|
||||
<< "观察距离:" << m_viewDistance;
|
||||
m_firstFrame = false; // 标记首帧已处理
|
||||
}
|
||||
updateCount++;
|
||||
|
||||
updateBuffers();
|
||||
update();
|
||||
|
||||
@@ -54,3 +54,15 @@ void PointCloudWidget::updatePointCloud(pcl::PointCloud<pcl::PointXYZ>::Ptr clou
|
||||
// 更新OpenGL显示
|
||||
m_glWidget->updatePointCloud(cloud);
|
||||
}
|
||||
|
||||
void PointCloudWidget::setColorMode(bool enabled)
|
||||
{
|
||||
if (m_glWidget) {
|
||||
m_glWidget->setColorMode(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
bool PointCloudWidget::colorMode() const
|
||||
{
|
||||
return m_glWidget ? m_glWidget->colorMode() : false;
|
||||
}
|
||||
|
||||
12
src/main.cpp
12
src/main.cpp
@@ -31,19 +31,19 @@ int main(int argc, char *argv[])
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// 设置应用程序信息
|
||||
app.setOrganizationName("UpperControl");
|
||||
app.setApplicationName("UpperControl GUI");
|
||||
app.setApplicationVersion("1.0.0");
|
||||
app.setOrganizationName("Viewer");
|
||||
app.setApplicationName("Viewer");
|
||||
app.setApplicationVersion("0.3.0");
|
||||
|
||||
// 初始化Logger(在可执行文件同目录下)
|
||||
QString logPath = QCoreApplication::applicationDirPath() + "/d330viewer.log";
|
||||
QString logPath = QCoreApplication::applicationDirPath() + "/viewer.log";
|
||||
Logger::instance()->setLogFile(logPath);
|
||||
Logger::instance()->setMaxLines(10000); // 保留最新10000行
|
||||
|
||||
// 安装消息处理器
|
||||
qInstallMessageHandler(messageHandler);
|
||||
|
||||
qDebug() << "D330Viewer started";
|
||||
qDebug() << "Viewer started";
|
||||
qDebug() << "Log file:" << logPath;
|
||||
|
||||
// 创建并显示主窗口
|
||||
@@ -52,7 +52,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
int result = app.exec();
|
||||
|
||||
qDebug() << "D330Viewer exiting";
|
||||
qDebug() << "Viewer exiting";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user