Add hik_camera_ros2_driver (renamed from camera2_ws to fori_camera_ws)

Hikvision MV-CS016-10UC ROS2 driver used for the dual/triple camera rig:
hardware-triggered acquisition, per-camera exposure/gain/white-balance
params, and self-calibrating device-clock timestamping (anchored to the
shared LiDAR/GPS clock domain via /timeshare) to keep image timestamps
tightly synced across cameras during motion. Includes the vendored
Hikvision MVS SDK (hikSDK/) needed to build.
This commit is contained in:
hjkim
2026-08-07 14:27:24 +09:00
parent 6129d2537b
commit b56d870658
76 changed files with 7544 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
{
"permissions": {
"allow": [
"Bash(colcon build:*)",
"Read(//usr/local/lib/**)",
"Read(//usr/local/include/**)",
"Bash(lsb_release -a)",
"Read(//usr/lib/**)",
"Bash(ls /usr/local/lib/liblivox*)",
"Bash(ls /usr/local/include/livox*)",
"Read(//home/gardentech/camera2_ws/**)",
"Bash(dpkg -l)",
"Bash(dpkg -l libapr1-dev)",
"Read(//opt/MVS/**)",
"Bash(ls /usr/local/lib/libMvCameraControl*)",
"Bash(dpkg -l ros-humble-sophus)",
"Bash(sudo apt:*)",
"Bash(awk '{print $2, $3}')",
"Read(//home/gardentech/Livox_SDK2/**)",
"Bash(ip addr *)",
"Bash(lsusb)",
"Read(//dev/**)",
"Read(//etc/udev/rules.d/**)",
"Bash(lsusb -v -d 2bdf:0001)",
"Bash(find /home/gardentech -name \"*.hpp\" -o -name \"*.h\" | xargs grep -l \"point.*size\\\\|PointSize\\\\|GL_POINT_SIZE\" 2>/dev/null | grep -v \".cmake\" | head -10)",
"Read(//home/gardentech/**)",
"Bash(grep -E \"\\\\.\\(glsl|vert|frag|geom\\)$\")",
"Bash(xargs ls -la)",
"Bash(python3 -c ' *)"
]
}
}
+8
View File
@@ -0,0 +1,8 @@
# colcon build artifacts — regenerate with `colcon build`
build/
install/
log/
__pycache__/
*.pyc
.vscode/
+45
View File
@@ -0,0 +1,45 @@
# 카메라 해상도 변경 작업 (1440×1080 → 720×540)
병목 원인: 카메라가 1440×1080으로 전송 → FAST-LIVO2가 scale=0.5로 소프트웨어 리사이즈
해결 방향: 하드웨어 binning 2×2으로 카메라 자체를 720×540 출력 → 전송량 1/4, 리사이즈 제거
---
## 체크리스트
### 1. MVS 소프트웨어 (카메라 하드웨어 설정)
- [ ] MVS 실행 후 카메라 접속
- [ ] `Image Format Control``BinningHorizontal = 2`
- [ ] `Image Format Control``BinningVertical = 2`
- [ ] `Width = 720`, `Height = 540` 확인
- [ ] `User Set Control``User Set Save` (재시작 후에도 유지)
### 2. Config 파일 (이미 완료)
- [x] `fast_ws/src/FAST-LIVO2/config/camera_mid360s.yaml`
- `cam_width/height`: 1440/1080 → 720/540
- `scale`: 0.5 → 1.0
- intrinsic (fx, fy, cx, cy): 원본값 × 0.5
- [x] `fori_camera_ws/src/hik_camera_ros2_driver/config/camera_info.yaml`
- `image_width/height`: 1440/1080 → 720/540
- camera_matrix, projection_matrix: 원본값 × 0.5
- distortion_coefficients: 변경 없음 (무차원값)
### 3. 동작 확인
- [ ] 카메라 드라이버 재시작
- [ ] `ros2 topic echo /camera/camera_info | grep width` → 720 확인
- [ ] `ros2 topic hz /camera/image` → 10Hz 달성 확인
---
## 재캘리브레이션이 필요 없는 이유
| 파라미터 | 단위 | binning 시 변화 |
|----------|------|-----------------|
| fx, fy | 픽셀 | 픽셀 크기 2배 → 값 ÷ 2 (수학적으로 정확) |
| cx, cy | 픽셀 | 동일하게 ÷ 2 |
| k1, k2, p1, p2 | 무차원 | 렌즈 왜곡은 물리 광학 → 변화 없음 |
렌즈와 센서 광학 자체는 동일하므로 재캘리브레이션 불필요.
+18
View File
@@ -0,0 +1,18 @@
---
Language: Cpp
BasedOnStyle: Google
AccessModifierOffset: -2
AlignAfterOpenBracket: AlwaysBreak
BraceWrapping:
AfterClass: true
AfterFunction: true
AfterNamespace: true
AfterStruct: true
BreakBeforeBraces: Custom
ColumnLimit: 100
ConstructorInitializerIndentWidth: 0
ContinuationIndentWidth: 2
DerivePointerAlignment: false
PointerAlignment: Middle
ReflowComments: false
+62
View File
@@ -0,0 +1,62 @@
---
Checks: '-*,
performance-*,
-performance-unnecessary-value-param,
llvm-namespace-comment,
modernize-redundant-void-arg,
modernize-use-nullptr,
modernize-use-default,
modernize-use-override,
modernize-loop-convert,
modernize-make-shared,
modernize-make-unique,
misc-unused-parameters,
readability-named-parameter,
readability-redundant-smartptr-get,
readability-redundant-string-cstr,
readability-simplify-boolean-expr,
readability-container-size-empty,
readability-identifier-naming,
'
HeaderFilterRegex: ''
CheckOptions:
- key: llvm-namespace-comment.ShortNamespaceLines
value: '10'
- key: llvm-namespace-comment.SpacesBeforeComments
value: '2'
- key: misc-unused-parameters.StrictMode
value: '1'
- key: readability-braces-around-statements.ShortStatementLines
value: '2'
# type names
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.EnumCase
value: CamelCase
- key: readability-identifier-naming.UnionCase
value: CamelCase
# method names
- key: readability-identifier-naming.MethodCase
value: camelBack
# variable names
- key: readability-identifier-naming.VariableCase
value: lower_case
# class member names
- key: readability-identifier-naming.PrivateMemberCase
value: lower_case
- key: readability-identifier-naming.PrivateMemberSuffix
value: '_'
- key: readability-identifier-naming.ProtectedMemberCase
value: lower_case
- key: readability-identifier-naming.ProtectedMemberSuffix
value: '_'
# const static or global variables are UPPER_CASE
- key: readability-identifier-naming.EnumConstantCase
value: UPPER_CASE
- key: readability-identifier-naming.StaticConstantCase
value: UPPER_CASE
- key: readability-identifier-naming.ClassConstantCase
value: UPPER_CASE
- key: readability-identifier-naming.GlobalVariableCase
value: UPPER_CASE
...
+26
View File
@@ -0,0 +1,26 @@
name: Build and Test
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
container:
image: rostooling/setup-ros-docker:ubuntu-jammy-ros-humble-desktop-latest
steps:
- name: Checkout
uses: actions/checkout@v4.2.2
- name: Build hik_camera_ros2_driver
uses: ros-tooling/action-ros-ci@v0.3
with:
package-name: hik_camera_ros2_driver
target-ros2-distro: humble
skip-tests: true
- name: Test hik_camera_ros2_driver
run: |
/usr/bin/bash .github/workflows/colcon_test.sh hik_camera_ros2_driver
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
source /opt/ros/humble/setup.sh
cd ros_ws
colcon test --packages-up-to "$1" --event-handlers console_cohesion+ --return-code-on-test-failure
+20
View File
@@ -0,0 +1,20 @@
build
devel
install
log/*
.catkin_workspace
.vscode
.cache
__pycache__
*~
*.pcd
*.gv
*.pdf
@@ -0,0 +1,97 @@
# To use:
#
# pre-commit run -a
#
# Or:
#
# pre-commit install # (runs every time you commit in git)
#
# To update this file:
#
# pre-commit autoupdate
#
# See https://github.com/pre-commit/pre-commit
repos:
# Standard hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-ast
- id: check-case-conflict
- id: check-docstring-first
- id: check-merge-conflict
- id: check-symlinks
- id: check-xml
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
- id: mixed-line-ending
- id: trailing-whitespace
exclude_types: [rst]
- id: fix-byte-order-marker
# Python hooks
- repo: https://github.com/asottile/pyupgrade
rev: v3.19.1
hooks:
- id: pyupgrade
args: [--py36-plus]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
hooks:
- id: ruff
args: [ --fix ]
- id: ruff-format
# CPP hooks
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v14.0.3
hooks:
- id: clang-format
args: ['-fallback-style=none', '-i']
- repo: local
hooks:
- id: ament_cppcheck
name: ament_cppcheck
description: Static code analysis of C/C++ files.
entry: env AMENT_CPPCHECK_ALLOW_SLOW_VERSIONS=1 ament_cppcheck
language: system
files: \.(h\+\+|h|hh|hxx|hpp|cuh|c|cc|cpp|cu|c\+\+|cxx|tpp|txx)$
# Cmake hooks
- repo: local
hooks:
- id: ament_lint_cmake
name: ament_lint_cmake
description: Check format of CMakeLists.txt files.
entry: ament_lint_cmake
language: system
files: CMakeLists\.txt$
# Docs - RestructuredText hooks
- repo: https://github.com/PyCQA/doc8
rev: v1.1.2
hooks:
- id: doc8
args: ['--max-line-length=100', '--ignore=D001']
exclude: CHANGELOG\.rst$
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: rst-backticks
exclude: CHANGELOG\.rst$
- id: rst-directive-colons
- id: rst-inline-touching-normal
# Spellcheck in comments and docs
# skipping of *.svg files is not working...
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
- id: codespell
args: ['--write-changes']
exclude: CHANGELOG\.rst|\.(svg|pyc)$
+68
View File
@@ -0,0 +1,68 @@
cmake_minimum_required(VERSION 3.8)
project(hik_camera_ros2_driver)
## Use C++14
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
## By adding -Wall and -Werror, the compiler does not ignore warnings anymore,
## enforcing cleaner code.
add_definitions(-Wall -Werror)
## Export compile commands for clangd
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(ament_cmake_auto REQUIRED)
ament_auto_find_build_dependencies()
ament_auto_add_library(${PROJECT_NAME} SHARED
src/hik_camera_node.cpp
)
target_include_directories(${PROJECT_NAME} PUBLIC hikSDK/include)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
target_link_directories(${PROJECT_NAME} PUBLIC hikSDK/lib/amd64)
install(
DIRECTORY hikSDK/lib/amd64/
DESTINATION lib
)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
target_link_directories(${PROJECT_NAME} PUBLIC hikSDK/lib/arm64)
install(
DIRECTORY hikSDK/lib/arm64/
DESTINATION lib
)
else()
message(FATAL_ERROR "Unsupported host system architecture: ${CMAKE_HOST_SYSTEM_PROCESSOR}!")
endif()
target_link_libraries(${PROJECT_NAME}
FormatConversion
MediaProcess
MvCameraControl
MVRender
MvUsb3vTL
)
rclcpp_components_register_node(${PROJECT_NAME}
PLUGIN hik_camera_ros2_driver::HikCameraRos2DriverNode
EXECUTABLE ${PROJECT_NAME}_node
)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
list(APPEND AMENT_LINT_AUTO_EXCLUDE
ament_cmake_copyright
ament_cmake_cpplint
ament_cmake_uncrustify
ament_cmake_flake8
)
ament_lint_auto_find_test_dependencies()
endif()
ament_auto_package(
INSTALL_TO_SHARE
launch
config
)
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+121
View File
@@ -0,0 +1,121 @@
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Build](https://github.com/SMBU-PolarBear-Robotics-Team/hik_camera_ros2_driver/actions/workflows/ci.yml/badge.svg)](https://github.com/SMBU-PolarBear-Robotics-Team/hik_camera_ros2_driver/actions/workflows/ci.yml)
# hik_camera_ros2_driver
## Overview
The `hik_camera_ros2_driver` package provides a ROS 2 driver for controlling and interfacing with Hikvision cameras. It supports functionalities such as camera initialization, parameter configuration, and image publishing. This package is intended for applications requiring reliable and configurable image data acquisition in a ROS 2 environment.
### Executables
The package includes the `hik_camera_node`, which manages the camera and publishes image data along with camera information to ROS 2 topics.
### Subscribed Topics
None.
### Published Topics
- `<camera_topic>` (sensor_msgs/msg/Image)
- The image data captured by the Hikvision camera.
- `<camera_topic>/camera_info` (sensor_msgs/msg/CameraInfo)
- Camera calibration information.
### Parameters
- `exposure_auto` (bool, default: `false`)
- Enable continuous auto exposure. When `true`, the camera controls exposure automatically and `exposure_time` is ignored.
- `exposure_time` (double, default: `5000`)
- Manual exposure time in microseconds. Used only when `exposure_auto` is `false`.
- `exposure_auto_target_brightness` (int, default: `128`, range: `0-255`)
- Target brightness for auto exposure. Active only when `exposure_auto` is `true`. Can be changed at runtime:
```bash
ros2 param set /hik_camera_ros2_driver exposure_auto_target_brightness 100
```
- `exposure_auto_min` (double, default: `100.0`)
- Auto exposure lower limit in microseconds. Active only when `exposure_auto` is `true`. Can be changed at runtime.
- `exposure_auto_max` (double, default: `10000.0`)
- Auto exposure upper limit in microseconds. Active only when `exposure_auto` is `true`. Can be changed at runtime.
- `gain` (double)
- Manual gain. Auto gain is always disabled. Can be changed at runtime:
```bash
ros2 param set /hik_camera_ros2_driver gain 2.0
```
- `acquisition_frame_rate` (double, default: `165`)
- The acquisition frame rate in hz for the camera.
- `pixel_format` (string, default: `RGB8Packed`)
- The pixel format for the image data. Supported values: `Mono8`, `Mono10`, `Mono12`, `RGB8Packed`, `BGR8Packed`, `YUV422_YUYV_Packed`, `YUV422Packed`, `BayerRG8`, `BayerRG10`, `BayerRG10Packed`, `BayerRG12`, `BayerRG12Packed`.
- `adc_bit_depth` (string, default: `Bits_8`)
- The ADC bit depth for the camera. Supported values: `Bits_8`, `Bits_12`.
- `use_sensor_data_qos` (bool, default: true)
- Whether to use the `sensor_data` QoS profile for image topic publication.
- `camera_name` (string, default: `camera`)
- The name of the camera for identification purposes.
- `frame_id` (string, default: `<camera_name>_optical_frame`)
- The frame_id assigned to the published image data.
- `camera_topic` (string, default: `<camera_name>/image`)
- The topic name for publishing image and info data.
- `camera_info_url` (string, default: `package://hik_camera_ros2_driver/config/camera_info.yaml`)
- The URL for the camera calibration information file.
- `trigger_enable` (bool, default: `false`)
- Enable hardware trigger mode (LINE0). When `true`, frame rate is controlled by the trigger signal.
- `use_trigger_timestamp` (bool, default: `false`)
- Use the shared memory timestamp written by the LiDAR driver instead of system time.
- `serial_number` (string, default: `""`)
- Select a specific camera by serial number. If empty, the first detected camera is used.
- `enable_interval_log` (bool, default: `false`)
- Print per-frame timestamp interval logs (`[TS]`). Also prints a rolling summary every 10 frames (avg / min / max / jitter). Can be toggled at runtime:
```bash
ros2 param set /hik_camera_ros2_driver enable_interval_log true
```
### Usage
#### Installation
To use this package, build it from source or include it in your ROS 2 workspace. Ensure that all dependencies are installed. You **don't** need to install the Hikvision camera SDK and include its libraries in your environment.
```bash
mkdir -p ~/ros_ws/src
cd ~/ros_ws/src
```
```bash
git clone https://github.com/SMBU-PolarBear-Robotics-Team/hik_camera_ros2_driver.git
```
```bash
cd ~/ros_ws
rosdep install -r --from-paths src --ignore-src --rosdistro $ROS_DISTRO -y
```
```bash
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release
```
#### Run
You can use the provided launch file for starting the camera node with default or custom parameters:
```bash
ros2 launch hik_camera_ros2_driver hik_camera_launch.py
```
@@ -0,0 +1,83 @@
## 명령어
### cam1 (DA9492688)
ros2 run camera_calibration cameracalibrator \
--size 5x3 --square 0.08 --camera_name cam1 \
--ros-args -r image:=/cam1/image -r camera:=/cam1
### cam2 (DB0174264)
ros2 run camera_calibration cameracalibrator \
--size 5x3 --square 0.08 --camera_name cam2 \
--ros-args -r image:=/cam2/image -r camera:=/cam2
## cam1
**** Calibrating ****
mono pinhole calibration...
D = [-0.09796287373263994, 0.07663475497425366, -0.0007867665071981631, 0.0013564355409627002, 0.0]
K = [1199.9523102931234, 0.0, 734.7325870955278, 0.0, 1203.7334778677036, 555.9843994323732, 0.0, 0.0, 1.0]
R = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
P = [1167.6373291015625, 0.0, 736.6036810131482, 0.0, 0.0, 1182.069580078125, 555.3207828053855, 0.0, 0.0, 0.0, 1.0, 0.0]
None
# oST version 5.0 parameters
[image]
width
1440
height
1080
[cam1]
camera matrix
1199.952310 0.000000 734.732587
0.000000 1203.733478 555.984399
0.000000 0.000000 1.000000
distortion
-0.097963 0.076635 -0.000787 0.001356 0.000000
rectification
1.000000 0.000000 0.000000
0.000000 1.000000 0.000000
0.000000 0.000000 1.000000
projection
1167.637329 0.000000 736.603681 0.000000
0.000000 1182.069580 555.320783 0.000000
0.000000 0.000000 1.000000 0.000000
## cam2
# oST version 5.0 parameters
[image]
width
1440
height
1080
[cam2]
camera matrix
1196.811764 0.000000 722.810928
0.000000 1200.992504 557.254838
0.000000 0.000000 1.000000
distortion
-0.094323 0.074690 -0.000652 0.002626 0.000000
rectification
1.000000 0.000000 0.000000
0.000000 1.000000 0.000000
0.000000 0.000000 1.000000
projection
1165.880859 0.000000 726.090785 0.000000
0.000000 1180.159668 556.709749 0.000000
0.000000 0.000000 1.000000 0.000000
@@ -0,0 +1,73 @@
# Hikrobot cam1
# Base calibration: 2026-05-26, 1440x1080 (8mm lens v4, 초점 링 재조정)
# 현재 설정: 1440x1080, 6mm lens (2026-06-16 재캘리브레이션)
# binning 적용 시: MVS → BinningHorizontal=2, BinningVertical=2 후 아래 주석 참고
image_width: 1440
image_height: 1080
camera_name: camera
camera_matrix:
rows: 3
cols: 3
# --- 6mm lens (cam1, 2026-06-16 재캘리브레이션, 현재 활성) ---
data: [1789.271411, 0.0, 744.170668,
0.0, 1794.848936, 545.346079,
0.0, 0.0, 1.0]
# --- 6mm lens (cam1, 2026-06-09 재캘리브레이션) ---
# data: [2317.833528, 0.0, 657.065936,
# 0.0, 2322.780813, 587.150296,
# 0.0, 0.0, 1.0]
# --- 8mm lens v4 (cam1, 2026-05-26) ---
# data: [2347.038631, 0.0, 791.950958,
# 0.0, 2353.175990, 566.608413,
# 0.0, 0.0, 1.0]
# --- 8mm lens v4, 720x540 (binning 2x2 적용 시, × 0.5) ---
# data: [1173.519316, 0.0, 395.975479,
# 0.0, 1176.587995, 283.304207,
# 0.0, 0.0, 1.0]
# --- 6mm lens, 1440x1080 ---
# data: [1731.285808, 0.0, 713.349417,
# 0.0, 1739.429604, 547.551014,
# 0.0, 0.0, 1.0]
distortion_model: plumb_bob
distortion_coefficients:
rows: 1
cols: 5
# 왜곡 계수는 해상도 변경과 무관 (동일 값)
# --- 6mm lens (cam1, 2026-06-16 재캘리브레이션, 현재 활성) ---
data: [-0.093183, 0.189899, 0.002084, 0.004098, 0.000000]
# --- 6mm lens (cam1, 2026-06-09 재캘리브레이션) ---
# data: [-0.111649, 0.359324, -0.001199, -0.005997, 0.000000]
# --- 8mm lens v4 (cam1, 2026-05-26) ---
# data: [-0.128253, 0.437160, 0.002047, 0.007568, 0.000000]
# --- 6mm lens ---
# data: [-0.091047, 0.101541, -0.001141, -0.001854, 0.000000]
rectification_matrix:
rows: 3
cols: 3
data: [1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0]
projection_matrix:
rows: 3
cols: 4
# --- 6mm lens (cam1, 2026-06-16 재캘리브레이션, 현재 활성) ---
data: [1769.672119, 0.0, 747.725207, 0.0,
0.0, 1781.145020, 545.964295, 0.0,
0.0, 0.0, 1.0, 0.0]
# --- 6mm lens (cam1, 2026-06-09 재캘리브레이션) ---
# data: [2296.523926, 0.0, 652.017278, 0.0,
# 0.0, 2310.680176, 586.399405, 0.0,
# 0.0, 0.0, 1.0, 0.0]
# --- 8mm lens v4 (cam1, 2026-05-26) ---
# data: [2322.917480, 0.0, 797.158539, 0.0,
# 0.0, 2339.252686, 567.138077, 0.0,
# 0.0, 0.0, 1.0, 0.0]
# --- 8mm lens v4, 720x540 (binning 2x2 적용 시, × 0.5) ---
# data: [1161.458740, 0.0, 398.579270, 0.0,
# 0.0, 1169.626343, 283.569039, 0.0,
# 0.0, 0.0, 1.0, 0.0]
# --- 6mm lens, 1440x1080 ---
# data: [1707.569336, 0.0, 710.977090, 0.0,
# 0.0, 1724.032715, 546.560814, 0.0,
# 0.0, 0.0, 1.0, 0.0]
@@ -0,0 +1,42 @@
# Hikrobot cam1 (serial: DA9492688)
# 4mm lens
# Calibration: 2026-08-04, ROS camera_calibration, 3-camera rig mount
# (Documents/intrinsic calibration Aug_3.odt). Supersedes the 2026-07-29 calibrations below.
# --- 2026-07-29 18:44 (3-camera rig, 폐기) ---
# data: [1199.340963, 0.0, 743.375449, 0.0, 1202.587584, 557.776165, 0.0, 0.0, 1.0]
# D: [-0.103276, 0.093289, -0.000926, 0.003536, 0.000000]
# P: [1167.042236, 0.0, 748.410092, 0.0, 0.0, 1180.361572, 557.015178, 0.0, 0.0, 0.0, 1.0, 0.0]
# --- 2026-07-29 오전 (같은 문서 재갱신됨, 폐기) ---
# data: [1211.492261, 0.0, 734.217435, 0.0, 1215.271564, 557.163356, 0.0, 0.0, 1.0]
# D: [-0.105889, 0.101806, -0.001782, 0.003994, 0.000000]
# P: [1179.859741, 0.0, 739.724699, 0.0, 0.0, 1193.134766, 555.713835, 0.0, 0.0, 0.0, 1.0, 0.0]
# --- 2026-07-08 (2-camera rig, 폐기) ---
# data: [1199.952310, 0.0, 734.732587, 0.0, 1203.733478, 555.984399, 0.0, 0.0, 1.0]
# D: [-0.097963, 0.076635, -0.000787, 0.001356, 0.000000]
image_width: 1440
image_height: 1080
camera_name: cam1
camera_matrix:
rows: 3
cols: 3
data: [1194.054103, 0.0, 731.768445,
0.0, 1193.734257, 554.579899,
0.0, 0.0, 1.0]
distortion_model: plumb_bob
distortion_coefficients:
rows: 1
cols: 5
data: [-0.104538, 0.094169, -0.000219, 0.002476, 0.000000]
rectification_matrix:
rows: 3
cols: 3
data: [1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0]
projection_matrix:
rows: 3
cols: 4
data: [1161.683350, 0.0, 735.053448, 0.0,
0.0, 1171.156982, 554.312835, 0.0,
0.0, 0.0, 1.0, 0.0]
@@ -0,0 +1,42 @@
# Hikrobot cam2 (serial: DB0174264)
# 4mm lens
# Calibration: 2026-08-04, ROS camera_calibration, 3-camera rig mount
# (Documents/intrinsic calibration Aug_3.odt). Supersedes the 2026-07-29 calibrations below.
# --- 2026-07-29 18:44 (3-camera rig, 폐기) ---
# data: [1199.655517, 0.0, 700.154366, 0.0, 1203.544082, 570.801288, 0.0, 0.0, 1.0]
# D: [-0.106583, 0.094309, -0.001065, -0.000934, 0.000000]
# P: [1166.505493, 0.0, 697.803404, 0.0, 0.0, 1180.652466, 570.349347, 0.0, 0.0, 0.0, 1.0, 0.0]
# --- 2026-07-29 오전 (같은 문서 재갱신됨, 폐기) ---
# data: [1201.483318, 0.0, 727.502400, 0.0, 1205.139403, 555.107690, 0.0, 0.0, 1.0]
# D: [-0.104569, 0.094512, -0.001664, 0.004386, 0.000000]
# P: [1169.050049, 0.0, 733.334463, 0.0, 0.0, 1182.879761, 553.701235, 0.0, 0.0, 0.0, 1.0, 0.0]
# --- 2026-07-08 (2-camera rig, 폐기) ---
# data: [1196.811764, 0.0, 722.810928, 0.0, 1200.992504, 557.254838, 0.0, 0.0, 1.0]
# D: [-0.094323, 0.074690, -0.000652, 0.002626, 0.000000]
image_width: 1440
image_height: 1080
camera_name: cam2
camera_matrix:
rows: 3
cols: 3
data: [1193.023682, 0.0, 716.099838,
0.0, 1194.022942, 571.401918,
0.0, 0.0, 1.0]
distortion_model: plumb_bob
distortion_coefficients:
rows: 1
cols: 5
data: [-0.102107, 0.089022, 0.003037, 0.002550, 0.000000]
rectification_matrix:
rows: 3
cols: 3
data: [1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0]
projection_matrix:
rows: 3
cols: 4
data: [1161.447266, 0.0, 719.116285, 0.0,
0.0, 1171.102051, 574.126663, 0.0,
0.0, 0.0, 1.0, 0.0]
@@ -0,0 +1,50 @@
# Hikrobot cam3 (serial: DB0159632)
# 4mm lens
# 2026-08-04: 물리적으로 광축(optical axis) 기준 반시계 방향 180도 회전 장착됨.
# 아래 calibration은 회전 이후 상태로 재촬영한 것이라 그대로 유효함 (센서가 180도
# 돌아간 채로 calibration 됐으므로 cx/cy 등은 이미 새 방향 기준). 다만 이미지 자체가
# cam1/cam2 대비 상하좌우 뒤집힌(180도 회전) 상태로 나오니, 외부캘리브레이션/지도 확인 시 주의.
# Calibration: 2026-08-04 (재보정), ROS camera_calibration, 3-camera rig mount.
# Supersedes the 2026-08-04 (Aug_3.odt) and 2026-07-29 calibrations below.
# --- 2026-08-04 Aug_3.odt (같은 날 재갱신됨, 폐기) ---
# data: [1192.416391, 0.0, 708.927258, 0.0, 1191.754041, 537.888235, 0.0, 0.0, 1.0]
# D: [-0.102876, 0.085564, -0.000482, -0.000819, 0.000000]
# P: [1159.438110, 0.0, 706.940888, 0.0, 0.0, 1169.153320, 536.949903, 0.0, 0.0, 0.0, 1.0, 0.0]
# --- 2026-07-29 18:44 (3-camera rig, 180도 회전 이전, 폐기) ---
# data: [1193.562151, 0.0, 700.744724, 0.0, 1197.422324, 555.120434, 0.0, 0.0, 1.0]
# D: [-0.101521, 0.081350, 0.000448, -0.001632, 0.000000]
# P: [1160.276611, 0.0, 697.333794, 0.0, 0.0, 1174.870239, 555.403914, 0.0, 0.0, 0.0, 1.0, 0.0]
# --- 2026-07-29 오전 (같은 문서 재갱신됨, 폐기) ---
# data: [1206.607830, 0.0, 703.507808, 0.0, 1209.432700, 549.058561, 0.0, 0.0, 1.0]
# D: [-0.106353, 0.087198, -0.000903, -0.000505, 0.000000]
# P: [1172.345459, 0.0, 701.774482, 0.0, 0.0, 1186.244995, 548.128388, 0.0, 0.0, 0.0, 1.0, 0.0]
# --- 2026-06-18 (2-camera-rig-era placeholder, 폐기) ---
# data: [1203.078148, 0.0, 699.186863, 0.0, 1206.096396, 565.715472, 0.0, 0.0, 1.0]
# D: [-0.102740, 0.093985, -0.000759, -0.001804, 0.000000]
image_width: 1440
image_height: 1080
camera_name: cam3
camera_matrix:
rows: 3
cols: 3
data: [1195.670418, 0.0, 708.482568,
0.0, 1194.671092, 536.506534,
0.0, 0.0, 1.0]
distortion_model: plumb_bob
distortion_coefficients:
rows: 1
cols: 5
data: [-0.108922, 0.102295, -0.000250, -0.000830, 0.000000]
rectification_matrix:
rows: 3
cols: 3
data: [1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0]
projection_matrix:
rows: 3
cols: 4
data: [1162.871338, 0.0, 706.540702, 0.0,
0.0, 1171.448730, 535.712904, 0.0,
0.0, 0.0, 1.0, 0.0]
@@ -0,0 +1,28 @@
/hik_camera_ros2_driver:
ros__parameters:
camera_info_url: "package://hik_camera_ros2_driver/config/camera_info.yaml"
pixel_format: "BayerRG8" # Recommended Option: "RGB8Packed", "BayerRG8"
adc_bit_depth: "Bits_8" # If using "BayerRG8", <adc_bit_depth> must be set as "Bits_8"; otherwise, it can be "Bits_8" or "Bits_12"
use_sensor_data_qos: false
camera_name: "camera"
# frame_id: "optical_frame" # If not set, it will be set as <camera_name>_optical_frame
# camera_topic: "image" # If not set, it will be set as <camera_name>/image
# 카메라 선택 (다중 카메라 사용 시 시리얼 번호 지정, 단일 카메라면 빈 문자열)
serial_number: "" # e.g. "DA2099368"
# 하드웨어 트리거 (STM32 PWM → LINE0 핀)
# true: LINE0 트리거 ON, AcquisitionFrameRate 파라미터 무시
# false: 프리런 모드, AcquisitionFrameRate 파라미터 사용
trigger_enable: true
# 공유 메모리 타임스탬프 (FAST-LIVO2 LiDAR-카메라 동기화)
# true: livox_ros_driver2가 기록한 /home/<user>/timeshare 에서 타임스탬프 읽기
# false: ROS 시스템 시간 사용
use_trigger_timestamp: true
# 프리런 모드(trigger_enable: false)일 때만 적용
acquisition_frame_rate: 10.0 # Unit: Hz
exposure_time: 5000 # Unit: us
gain: 8.0 # Range: 0.0 ~ 16.9, Unit: dB
@@ -0,0 +1,55 @@
/hik_camera_cam1:
ros__parameters:
camera_info_url: "package://hik_camera_ros2_driver/config/camera_info_cam1.yaml"
pixel_format: "BayerRG8" # Recommended Option: "RGB8Packed", "BayerRG8"
adc_bit_depth: "Bits_8" # If using "BayerRG8", <adc_bit_depth> must be set as "Bits_8"; otherwise, it can be "Bits_8" or "Bits_12"
use_sensor_data_qos: false
camera_name: "cam1"
# frame_id: "optical_frame" # If not set, it will be set as <camera_name>_optical_frame
# camera_topic: "image" # If not set, it will be set as <camera_name>/image
# 카메라 선택 (다중 카메라 사용 시 시리얼 번호 지정)
serial_number: "DA9492688"
# 하드웨어 트리거 (STM32 PWM → LINE0 핀)
# true: LINE0 트리거 ON, AcquisitionFrameRate 파라미터 무시
# false: 프리런 모드, AcquisitionFrameRate 파라미터 사용
trigger_enable: true
# 공유 메모리 타임스탬프 (FAST-LIVO2 LiDAR-카메라 동기화)
# true (trigger_enable=true일 때): 카메라 자체 하드웨어 클럭(nDevTimeStampHigh/Low)을
# 기준으로 타임스탬프를 계산한다. nTriggerIndex는 이 카메라(MV-CS016-10UC USB3)에서
# 항상 0으로 고정되어 사용 불가로 확인됨 (2026-07-29 실기기 로그로 검증).
# 최근 dev_clock_window_sec 구간의 (dev_ticks, /home/<user>/timeshare 값) 샘플로
# 선형회귀하여 이 클럭의 실제 틱 주파수를 계속 재보정한다 (dev_clock_recalib_interval_sec
# 주기). 한 번만 보정하고 얼리면 ~150ppm 수준의 잔여 오차가 세션 길이에 비례해
# 누적되는 것이 실기기 이동 테스트로 확인되어(2026-07-29), 슬라이딩 윈도우 방식으로
# 변경함 — 오차가 세션 길이와 무관하게 창 하나 수준으로 bounded된다.
# 보정 완료 전(및 보정 실패 시)에는 공유메모리 직접 읽기로 폴백한다.
# false: ROS 시스템 시간 사용
use_trigger_timestamp: true
# 슬라이딩 윈도우 길이 (초). 길수록 주파수 추정이 정밀하지만 시작 후 그만큼 지나야
# 정밀 모드로 전환된다.
dev_clock_window_sec: 10.0
# 재보정 주기 (초). 이 주기마다 위 윈도우로 회귀를 다시 계산해 드리프트를 억제한다.
dev_clock_recalib_interval_sec: 2.0
# 프리런 모드(trigger_enable: false)일 때만 적용
acquisition_frame_rate: 10.0 # Unit: Hz
exposure_auto: true
exposure_auto_target_brightness: 110
exposure_auto_min: 100.0
exposure_auto_max: 5000.0
exposure_time: 5000 # Unit: us
gain: 12.0 # Range: 0.0 ~ 16.9, Unit: dB
# 화이트밸런스 — cam1/cam2 겹치는 영역 색감을 맞추기 위해 두 카메라에 동일한
# 수동 R/G/B 비율을 고정한다 (2026-07-15, 두 카메라 Continuous AWB 수렴값의 평균).
# cam1 실측: R=1414 G=1024 B=2060 / cam2 실측: R=1408 G=1024 B=2184 → 평균 적용
balance_white_auto: false
balance_ratio_red: 1411
balance_ratio_green: 1024
balance_ratio_blue: 2122
@@ -0,0 +1,55 @@
/hik_camera_cam2:
ros__parameters:
camera_info_url: "package://hik_camera_ros2_driver/config/camera_info_cam2.yaml"
pixel_format: "BayerRG8" # Recommended Option: "RGB8Packed", "BayerRG8"
adc_bit_depth: "Bits_8" # If using "BayerRG8", <adc_bit_depth> must be set as "Bits_8"; otherwise, it can be "Bits_8" or "Bits_12"
use_sensor_data_qos: false
camera_name: "cam2"
# frame_id: "optical_frame" # If not set, it will be set as <camera_name>_optical_frame
# camera_topic: "image" # If not set, it will be set as <camera_name>/image
# 카메라 선택 (다중 카메라 사용 시 시리얼 번호 지정)
serial_number: "DB0174264"
# 하드웨어 트리거 (STM32 PWM → LINE0 핀)
# true: LINE0 트리거 ON, AcquisitionFrameRate 파라미터 무시
# false: 프리런 모드, AcquisitionFrameRate 파라미터 사용
trigger_enable: true
# 공유 메모리 타임스탬프 (FAST-LIVO2 LiDAR-카메라 동기화)
# true (trigger_enable=true일 때): 카메라 자체 하드웨어 클럭(nDevTimeStampHigh/Low)을
# 기준으로 타임스탬프를 계산한다. nTriggerIndex는 이 카메라(MV-CS016-10UC USB3)에서
# 항상 0으로 고정되어 사용 불가로 확인됨 (2026-07-29 실기기 로그로 검증).
# 최근 dev_clock_window_sec 구간의 (dev_ticks, /home/<user>/timeshare 값) 샘플로
# 선형회귀하여 이 클럭의 실제 틱 주파수를 계속 재보정한다 (dev_clock_recalib_interval_sec
# 주기). 한 번만 보정하고 얼리면 ~150ppm 수준의 잔여 오차가 세션 길이에 비례해
# 누적되는 것이 실기기 이동 테스트로 확인되어(2026-07-29), 슬라이딩 윈도우 방식으로
# 변경함 — 오차가 세션 길이와 무관하게 창 하나 수준으로 bounded된다.
# 보정 완료 전(및 보정 실패 시)에는 공유메모리 직접 읽기로 폴백한다.
# false: ROS 시스템 시간 사용
use_trigger_timestamp: true
# 슬라이딩 윈도우 길이 (초). 길수록 주파수 추정이 정밀하지만 시작 후 그만큼 지나야
# 정밀 모드로 전환된다.
dev_clock_window_sec: 10.0
# 재보정 주기 (초). 이 주기마다 위 윈도우로 회귀를 다시 계산해 드리프트를 억제한다.
dev_clock_recalib_interval_sec: 2.0
# 프리런 모드(trigger_enable: false)일 때만 적용
acquisition_frame_rate: 10.0 # Unit: Hz
exposure_auto: true
exposure_auto_target_brightness: 128
exposure_auto_min: 100.0
exposure_auto_max: 5000.0
exposure_time: 5000 # Unit: us
gain: 15.0 # Range: 0.0 ~ 16.9, Unit: dB
# 화이트밸런스 — cam1/cam2 겹치는 영역 색감을 맞추기 위해 두 카메라에 동일한
# 수동 R/G/B 비율을 고정한다 (2026-07-15, 두 카메라 Continuous AWB 수렴값의 평균).
# cam1 실측: R=1414 G=1024 B=2060 / cam2 실측: R=1408 G=1024 B=2184 → 평균 적용
balance_white_auto: false
balance_ratio_red: 1411
balance_ratio_green: 1024
balance_ratio_blue: 2122
@@ -0,0 +1,52 @@
/hik_camera_cam3:
ros__parameters:
camera_info_url: "package://hik_camera_ros2_driver/config/camera_info_cam3.yaml"
pixel_format: "BayerRG8" # Recommended Option: "RGB8Packed", "BayerRG8"
adc_bit_depth: "Bits_8" # If using "BayerRG8", <adc_bit_depth> must be set as "Bits_8"; otherwise, it can be "Bits_8" or "Bits_12"
use_sensor_data_qos: false
camera_name: "cam3"
# frame_id: "optical_frame" # If not set, it will be set as <camera_name>_optical_frame
# camera_topic: "image" # If not set, it will be set as <camera_name>/image
# 카메라 선택 (다중 카메라 사용 시 시리얼 번호 지정)
serial_number: "DB0159632"
# 하드웨어 트리거 (STM32 PWM → LINE0 핀, cam1/cam2와 동일 라인 공유 가정)
# true: LINE0 트리거 ON, AcquisitionFrameRate 파라미터 무시
# false: 프리런 모드, AcquisitionFrameRate 파라미터 사용
trigger_enable: true
# 공유 메모리 타임스탬프 (FAST-LIVO2 LiDAR-카메라 동기화)
# true (trigger_enable=true일 때): 카메라 자체 하드웨어 클럭(nDevTimeStampHigh/Low)을
# 기준으로 타임스탬프를 계산한다. nTriggerIndex는 이 카메라(MV-CS016-10UC USB3)에서
# 항상 0으로 고정되어 사용 불가로 확인됨 (2026-07-29 실기기 로그로 검증).
# 최근 dev_clock_window_sec 구간의 (dev_ticks, /home/<user>/timeshare 값) 샘플로
# 선형회귀하여 이 클럭의 실제 틱 주파수를 계속 재보정한다 (dev_clock_recalib_interval_sec
# 주기). 한 번만 보정하고 얼리면 ~150ppm 수준의 잔여 오차가 세션 길이에 비례해
# 누적되는 것이 실기기 이동 테스트로 확인되어(2026-07-29), 슬라이딩 윈도우 방식으로
# 변경함 — 오차가 세션 길이와 무관하게 창 하나 수준으로 bounded된다.
# 보정 완료 전(및 보정 실패 시)에는 공유메모리 직접 읽기로 폴백한다.
# false: ROS 시스템 시간 사용
use_trigger_timestamp: true
# 슬라이딩 윈도우 길이 (초). 길수록 주파수 추정이 정밀하지만 시작 후 그만큼 지나야
# 정밀 모드로 전환된다.
dev_clock_window_sec: 10.0
# 재보정 주기 (초). 이 주기마다 위 윈도우로 회귀를 다시 계산해 드리프트를 억제한다.
dev_clock_recalib_interval_sec: 2.0
# 프리런 모드(trigger_enable: false)일 때만 적용
acquisition_frame_rate: 10.0 # Unit: Hz
# cam1/cam2와 동일하게 맞춤 (밝기 차이 최소화)
exposure_auto: true
exposure_auto_target_brightness: 145
exposure_auto_min: 100.0
exposure_auto_max: 8000.0
exposure_time: 5000 # Unit: us
gain: 12.0 # Range: 0.0 ~ 16.9, Unit: dB
balance_ratio_red: 1411
balance_ratio_green: 1024
balance_ratio_blue: 2122
@@ -0,0 +1,72 @@
# direct_visual_lidar_calibration: SuperGlue(자동) vs Manual
## 공통 단계 (한 번만 하면 됨, 재실행 불필요)
```bash
# 1. bag 5개 녹화 (cam1 또는 cam2, 정지 상태 10~15초씩)
ros2 bag record -o cam1_calib_01 /cam1/image /cam1/camera_info /livox/lidar
# ... 02~05 반복
# 2. preprocess
ros2 run direct_visual_lidar_calibration preprocess \
~/dvlc_data/cam1_bags ~/dvlc_data/cam1_preprocessed -av
```
`cam1_preprocessed/` 안의 `.ply`(포인트클라우드), `.png`(카메라 이미지), `_lidar_intensities.png`(LiDAR 반사강도 이미지)는 초기 추정 방법과 무관하게 그대로 재사용됩니다. **여기를 지우거나 다시 만들 필요 없음.**
---
## 방법 A: SuperGlue 자동 초기 추정
```bash
ros2 run direct_visual_lidar_calibration find_matches_superglue.py \
~/dvlc_data/cam1_preprocessed --superglue indoor --force_cpu --rotate_camera 90
ros2 run direct_visual_lidar_calibration initial_guess_auto ~/dvlc_data/cam1_preprocessed
```
- `calib.json``results.init_T_lidar_camera_auto`에 저장됨
- 결과물: `<bag_name>_superglue.png` (매칭선 시각화), `<bag_name>_matches.json`
- **오늘 확인된 문제**: 체커보드처럼 반복적인 패턴이 있는 장면에서는 SuperGlue/SuperPoint 특징점이 코너를 잘못 매칭하기 쉬움 (스크린샷에서 체커보드 영역 정합이 어긋난 것으로 보임). SuperGlue는 애초에 텍스처가 풍부한 일반 실내 장면(가구, 벽, 문틀 등)을 겨냥한 것이라, 체커보드 같은 인공 패턴엔 오히려 약함.
- 장점: 손 안 대고 5개 bag 한 번에 처리 가능, 여러 번 반복 가능
- 단점: 라이선스 비영리 제한, 반복 패턴/텍스처 부족 장면에서 오매칭 발생
## 방법 B: Manual 초기 추정
```bash
ros2 run direct_visual_lidar_calibration initial_guess_manual ~/dvlc_data/cam1_preprocessed
```
- `calib.json``results.init_T_lidar_camera`(manual)에 저장됨 — **이 키가 있으면 `calibrate`가 무조건 이걸 우선 사용**
- 절차:
1. 포인트클라우드에서 우클릭 → 3D점, 이미지에서 우클릭 → 대응하는 2D점
2. **Add picked points** 클릭
3. 벽 모서리/문틀 코너 등 애매하지 않은 지점 위주로 최소 3쌍, 가능하면 5쌍+
4. **Estimate**`blend_weight` 슬라이더로 투영 정합 확인
5. **Save**
- 장점: 정확한 코너를 직접 골라서 오매칭 위험이 낮음, 라이선스 문제 없음
- 단점: bag마다 수작업, 시간 소요
---
## 재작업 흐름 (지금 상황)
기존에 auto로 진행했던 결과가 마음에 안 들면:
```bash
# manual 초기 추정만 다시
ros2 run direct_visual_lidar_calibration initial_guess_manual ~/dvlc_data/cam1_preprocessed
# 그 다음 fine registration 재실행 (manual 값을 자동으로 우선 사용함)
ros2 run direct_visual_lidar_calibration calibrate ~/dvlc_data/cam1_preprocessed
```
`preprocess`는 다시 할 필요 없고, `find_matches_superglue.py`/`initial_guess_auto`로 만든 auto 결과도 그냥 남겨둬도 무방합니다 (calibrate가 manual을 우선시하므로 방해되지 않음).
## 결과 확인
```bash
ros2 run direct_visual_lidar_calibration viewer ~/dvlc_data/cam1_preprocessed
```
`data selection` 드롭다운에서 `CALIBRATION_RESULT` / `AUTOMATIC_INITIAL_GUESS` / `MANUAL_INITIAL_GUESS`를 바꿔가며 비교 가능 (스크린샷에 나온 화면). `blend_weight`를 조절해 포인트클라우드 색상 투영과 실제 이미지가 얼마나 겹치는지 확인.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,135 @@
#ifndef _MV_ERROR_DEFINE_H_
#define _MV_ERROR_DEFINE_H_
/********************************************************************/
/// \~chinese
/// \name 正确码定义
/// @{
/// \~english
/// \name Definition of correct code
/// @{
#define MV_OK 0x00000000 ///< \~chinese 成功,无错误 \~english Succeeded, no error
/// @}
/********************************************************************/
/// \~chinese
/// \name 通用错误码定义:范围0x80000000-0x800000FF
/// @{
/// \~english
/// \name Definition of General error code
/// @{
#define MV_E_HANDLE \
0x80000000 ///< \~chinese 错误或无效的句柄 \~english Error or invalid handle
#define MV_E_SUPPORT \
0x80000001 ///< \~chinese 不支持的功能 \~english Not supported function
#define MV_E_BUFOVER 0x80000002 ///< \~chinese 缓存已满 \~english Buffer overflow
#define MV_E_CALLORDER \
0x80000003 ///< \~chinese 函数调用顺序错误 \~english Function calling order error
#define MV_E_PARAMETER \
0x80000004 ///< \~chinese 错误的参数 \~english Incorrect parameter
#define MV_E_RESOURCE \
0x80000006 ///< \~chinese 资源申请失败 \~english Applying resource failed
#define MV_E_NODATA 0x80000007 ///< \~chinese 无数据 \~english No data
#define MV_E_PRECONDITION \
0x80000008 ///< \~chinese 前置条件有误,或运行环境已发生变化 \~english Precondition error, or running environment changed
#define MV_E_VERSION \
0x80000009 ///< \~chinese 版本不匹配 \~english Version mismatches
#define MV_E_NOENOUGH_BUF \
0x8000000A ///< \~chinese 传入的内存空间不足 \~english Insufficient memory
#define MV_E_ABNORMAL_IMAGE \
0x8000000B ///< \~chinese 异常图像,可能是丢包导致图像不完整 \~english Abnormal image, maybe incomplete image because of lost packet
#define MV_E_LOAD_LIBRARY \
0x8000000C ///< \~chinese 动态导入DLL失败 \~english Load library failed
#define MV_E_NOOUTBUF \
0x8000000D ///< \~chinese 没有可输出的缓存 \~english No Available Buffer
#define MV_E_UNKNOW 0x800000FF ///< \~chinese 未知的错误 \~english Unknown error
/// @}
/********************************************************************/
/// \~chinese
/// \name GenICam系列错误:范围0x80000100-0x800001FF
/// @{
/// \~english
/// \name GenICam Series Error Codes: Range from 0x80000100 to 0x800001FF
/// @{
#define MV_E_GC_GENERIC 0x80000100 ///< \~chinese 通用错误 \~english General error
#define MV_E_GC_ARGUMENT \
0x80000101 ///< \~chinese 参数非法 \~english Illegal parameters
#define MV_E_GC_RANGE \
0x80000102 ///< \~chinese 值超出范围 \~english The value is out of range
#define MV_E_GC_PROPERTY 0x80000103 ///< \~chinese 属性 \~english Property
#define MV_E_GC_RUNTIME \
0x80000104 ///< \~chinese 运行环境有问题 \~english Running environment error
#define MV_E_GC_LOGICAL 0x80000105 ///< \~chinese 逻辑错误 \~english Logical error
#define MV_E_GC_ACCESS \
0x80000106 ///< \~chinese 节点访问条件有误 \~english Node accessing condition error
#define MV_E_GC_TIMEOUT 0x80000107 ///< \~chinese 超时 \~english Timeout
#define MV_E_GC_DYNAMICCAST \
0x80000108 ///< \~chinese 转换异常 \~english Transformation exception
#define MV_E_GC_UNKNOW \
0x800001FF ///< \~chinese GenICam未知错误 \~english GenICam unknown error
/// @}
/********************************************************************/
/// \~chinese
/// \name GigE_STATUS对应的错误码:范围0x80000200-0x800002FF
/// @{
/// \~english
/// \name GigE_STATUS Error Codes: Range from 0x80000200 to 0x800002FF
/// @{
#define MV_E_NOT_IMPLEMENTED \
0x80000200 ///< \~chinese 命令不被设备支持 \~english The command is not supported by device
#define MV_E_INVALID_ADDRESS \
0x80000201 ///< \~chinese 访问的目标地址不存在 \~english The target address being accessed does not exist
#define MV_E_WRITE_PROTECT \
0x80000202 ///< \~chinese 目标地址不可写 \~english The target address is not writable
#define MV_E_ACCESS_DENIED \
0x80000203 ///< \~chinese 设备无访问权限 \~english No permission
#define MV_E_BUSY \
0x80000204 ///< \~chinese 设备忙,或网络断开 \~english Device is busy, or network disconnected
#define MV_E_PACKET \
0x80000205 ///< \~chinese 网络包数据错误 \~english Network data packet error
#define MV_E_NETER 0x80000206 ///< \~chinese 网络相关错误 \~english Network error
#define MV_E_IP_CONFLICT \
0x80000221 ///< \~chinese 设备IP冲突 \~english Device IP conflict
/// @}
/********************************************************************/
/// \~chinese
/// \name USB_STATUS对应的错误码:范围0x80000300-0x800003FF
/// @{
/// \~english
/// \name USB_STATUS Error Codes: Range from 0x80000300 to 0x800003FF
/// @{
#define MV_E_USB_READ 0x80000300 ///< \~chinese 读usb出错 \~english Reading USB error
#define MV_E_USB_WRITE 0x80000301 ///< \~chinese 写usb出错 \~english Writing USB error
#define MV_E_USB_DEVICE 0x80000302 ///< \~chinese 设备异常 \~english Device exception
#define MV_E_USB_GENICAM 0x80000303 ///< \~chinese GenICam相关错误 \~english GenICam error
#define MV_E_USB_BANDWIDTH \
0x80000304 ///< \~chinese 带宽不足 该错误码新增 \~english Insufficient bandwidth, this error code is newly added
#define MV_E_USB_DRIVER \
0x80000305 ///< \~chinese 驱动不匹配或者未装驱动 \~english Driver mismatch or unmounted drive
#define MV_E_USB_UNKNOW 0x800003FF ///< \~chinese USB未知的错误 \~english USB unknown error
/// @}
/********************************************************************/
/// \~chinese
/// \name 升级时对应的错误码:范围0x80000400-0x800004FF
/// @{
/// \~english
/// \name Upgrade Error Codes: Range from 0x80000400 to 0x800004FF
/// @{
#define MV_E_UPG_FILE_MISMATCH \
0x80000400 ///< \~chinese 升级固件不匹配 \~english Firmware mismatches
#define MV_E_UPG_LANGUSGE_MISMATCH \
0x80000401 ///< \~chinese 升级固件语言不匹配 \~english Firmware language mismatches
#define MV_E_UPG_CONFLICT \
0x80000402 ///< \~chinese 升级冲突(设备已经在升级了再次请求升级即返回此错误) \~english Upgrading conflicted (repeated upgrading requests during device upgrade)
#define MV_E_UPG_INNER_ERR \
0x80000403 ///< \~chinese 升级时相机内部出现错误 \~english Camera internal error during upgrade
#define MV_E_UPG_UNKNOW \
0x800004FF ///< \~chinese 升级时未知错误 \~english Unknown error during upgrade
/// @}
#endif //_MV_ERROR_DEFINE_H_
@@ -0,0 +1,93 @@
#ifndef _MV_ISP_ERROR_DEFINE_H_
#define _MV_ISP_ERROR_DEFINE_H_
/************************************************************************
* 来自ISP算法库的错误码
************************************************************************/
// 通用类型
#define MV_ALG_OK 0x00000000 //处理正确
#define MV_ALG_ERR 0x10000000 //不确定类型错误
// 能力检查
#define MV_ALG_E_ABILITY_ARG 0x10000001 //能力集中存在无效参数
// 内存检查
#define MV_ALG_E_MEM_NULL 0x10000002 //内存地址为空
#define MV_ALG_E_MEM_ALIGN 0x10000003 //内存对齐不满足要求
#define MV_ALG_E_MEM_LACK 0x10000004 //内存空间大小不够
#define MV_ALG_E_MEM_SIZE_ALIGN 0x10000005 //内存空间大小不满足对齐要求
#define MV_ALG_E_MEM_ADDR_ALIGN 0x10000006 //内存地址不满足对齐要求
// 图像检查
#define MV_ALG_E_IMG_FORMAT 0x10000007 //图像格式不正确或者不支持
#define MV_ALG_E_IMG_SIZE 0x10000008 //图像宽高不正确或者超出范围
#define MV_ALG_E_IMG_STEP 0x10000009 //图像宽高与step参数不匹配
#define MV_ALG_E_IMG_DATA_NULL 0x1000000A //图像数据存储地址为空
// 输入输出参数检查
#define MV_ALG_E_CFG_TYPE 0x1000000B //设置或者获取参数类型不正确
#define MV_ALG_E_CFG_SIZE 0x1000000C //设置或者获取参数的输入、输出结构体大小不正确
#define MV_ALG_E_PRC_TYPE 0x1000000D //处理类型不正确
#define MV_ALG_E_PRC_SIZE 0x1000000E //处理时输入、输出参数大小不正确
#define MV_ALG_E_FUNC_TYPE 0x1000000F //子处理类型不正确
#define MV_ALG_E_FUNC_SIZE 0x10000010 //子处理时输入、输出参数大小不正确
// 运行参数检查
#define MV_ALG_E_PARAM_INDEX 0x10000011 //index参数不正确
#define MV_ALG_E_PARAM_VALUE 0x10000012 //value参数不正确或者超出范围
#define MV_ALG_E_PARAM_NUM 0x10000013 //param_num参数不正确
// 接口调用检查
#define MV_ALG_E_NULL_PTR 0x10000014 //函数参数指针为空
#define MV_ALG_E_OVER_MAX_MEM 0x10000015 //超过限定的最大内存
#define MV_ALG_E_CALL_BACK 0x10000016 //回调函数出错
// 算法库加密相关检查
#define MV_ALG_E_ENCRYPT 0x10000017 //加密错误
#define MV_ALG_E_EXPIRE 0x10000018 //算法库使用期限错误
// 内部模块返回的基本错误类型
#define MV_ALG_E_BAD_ARG 0x10000019 //参数范围不正确
#define MV_ALG_E_DATA_SIZE 0x1000001A //数据大小不正确
#define MV_ALG_E_STEP 0x1000001B //数据step不正确
// cpu指令集支持错误码
#define MV_ALG_E_CPUID 0x1000001C //cpu不支持优化代码中的指令集
#define MV_ALG_WARNING 0x1000001D //警告
#define MV_ALG_E_TIME_OUT 0x1000001E //算法库超时
#define MV_ALG_E_LIB_VERSION 0x1000001F //算法版本号出错
#define MV_ALG_E_MODEL_VERSION 0x10000020 //模型版本号出错
#define MV_ALG_E_GPU_MEM_ALLOC 0x10000021 //GPU内存分配错误
#define MV_ALG_E_FILE_NON_EXIST 0x10000022 //文件不存在
#define MV_ALG_E_NONE_STRING 0x10000023 //字符串为空
#define MV_ALG_E_IMAGE_CODEC 0x10000024 //图像解码器错误
#define MV_ALG_E_FILE_OPEN 0x10000025 //打开文件错误
#define MV_ALG_E_FILE_READ 0x10000026 //文件读取错误
#define MV_ALG_E_FILE_WRITE 0x10000027 //文件写错误
#define MV_ALG_E_FILE_READ_SIZE 0x10000028 //文件读取大小错误
#define MV_ALG_E_FILE_TYPE 0x10000029 //文件类型错误
#define MV_ALG_E_MODEL_TYPE 0x1000002A //模型类型错误
#define MV_ALG_E_MALLOC_MEM 0x1000002B //分配内存错误
#define MV_ALG_E_BIND_CORE_FAILED 0x1000002C //线程绑核失败
// 降噪特有错误码
#define MV_ALG_E_DENOISE_NE_IMG_FORMAT 0x10402001 //噪声特性图像格式错误
#define MV_ALG_E_DENOISE_NE_FEATURE_TYPE 0x10402002 //噪声特性类型错误
#define MV_ALG_E_DENOISE_NE_PROFILE_NUM 0x10402003 //噪声特性个数错误
#define MV_ALG_E_DENOISE_NE_GAIN_NUM 0x10402004 //噪声特性增益个数错误
#define MV_ALG_E_DENOISE_NE_GAIN_VAL 0x10402005 //噪声曲线增益值输入错误
#define MV_ALG_E_DENOISE_NE_BIN_NUM 0x10402006 //噪声曲线柱数错误
#define MV_ALG_E_DENOISE_NE_INIT_GAIN 0x10402007 //噪声估计初始化增益设置错误
#define MV_ALG_E_DENOISE_NE_NOT_INIT 0x10402008 //噪声估计未初始化
#define MV_ALG_E_DENOISE_COLOR_MODE 0x10402009 //颜色空间模式错误
#define MV_ALG_E_DENOISE_ROI_NUM 0x1040200a //图像ROI个数错误
#define MV_ALG_E_DENOISE_ROI_ORI_PT 0x1040200b //图像ROI原点错误
#define MV_ALG_E_DENOISE_ROI_SIZE 0x1040200c //图像ROI大小错误
#define MV_ALG_E_DENOISE_GAIN_NOT_EXIST 0x1040200d //输入的相机增益不存在(增益个数已达上限)
#define MV_ALG_E_DENOISE_GAIN_BEYOND_RANGE 0x1040200e //输入的相机增益不在范围内
#define MV_ALG_E_DENOISE_NP_BUF_SIZE 0x1040200f //输入的噪声特性内存大小错误
#endif //_MV_ISP_ERROR_DEFINE_H_
@@ -0,0 +1,247 @@
#ifndef _MV_PIXEL_TYPE_H_
#define _MV_PIXEL_TYPE_H_
//#include "Base/GCTypes.h"
/************************************************************************/
/* GigE Vision (2.0.03) PIXEL FORMATS */
/************************************************************************/
// Indicate if pixel is monochrome or RGB
#define MV_GVSP_PIX_MONO 0x01000000
#define MV_GVSP_PIX_RGB 0x02000000 // deprecated in version 1.1
#define MV_GVSP_PIX_COLOR 0x02000000
#define MV_GVSP_PIX_CUSTOM 0x80000000
#define MV_GVSP_PIX_COLOR_MASK 0xFF000000
// Indicate effective number of bits occupied by the pixel (including padding).
// This can be used to compute amount of memory required to store an image.
#define MV_PIXEL_BIT_COUNT(n) ((n) << 16)
#define MV_GVSP_PIX_EFFECTIVE_PIXEL_SIZE_MASK 0x00FF0000
#define MV_GVSP_PIX_EFFECTIVE_PIXEL_SIZE_SHIFT 16
// Pixel ID: lower 16-bit of the pixel formats
#define MV_GVSP_PIX_ID_MASK 0x0000FFFF
#define MV_GVSP_PIX_COUNT 0x46 // next Pixel ID available
enum MvGvspPixelType {
// Undefined pixel type
#ifdef WIN32
PixelType_Gvsp_Undefined = 0xFFFFFFFF,
#else
PixelType_Gvsp_Undefined = -1,
#endif
// Mono buffer format defines
PixelType_Gvsp_Mono1p = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(1) | 0x0037),
PixelType_Gvsp_Mono2p = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(2) | 0x0038),
PixelType_Gvsp_Mono4p = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(4) | 0x0039),
PixelType_Gvsp_Mono8 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(8) | 0x0001),
PixelType_Gvsp_Mono8_Signed = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(8) | 0x0002),
PixelType_Gvsp_Mono10 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0003),
PixelType_Gvsp_Mono10_Packed = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x0004),
PixelType_Gvsp_Mono12 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0005),
PixelType_Gvsp_Mono12_Packed = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x0006),
PixelType_Gvsp_Mono14 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0025),
PixelType_Gvsp_Mono16 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0007),
// Bayer buffer format defines
PixelType_Gvsp_BayerGR8 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(8) | 0x0008),
PixelType_Gvsp_BayerRG8 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(8) | 0x0009),
PixelType_Gvsp_BayerGB8 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(8) | 0x000A),
PixelType_Gvsp_BayerBG8 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(8) | 0x000B),
PixelType_Gvsp_BayerGR10 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x000C),
PixelType_Gvsp_BayerRG10 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x000D),
PixelType_Gvsp_BayerGB10 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x000E),
PixelType_Gvsp_BayerBG10 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x000F),
PixelType_Gvsp_BayerGR12 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0010),
PixelType_Gvsp_BayerRG12 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0011),
PixelType_Gvsp_BayerGB12 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0012),
PixelType_Gvsp_BayerBG12 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0013),
PixelType_Gvsp_BayerGR10_Packed = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x0026),
PixelType_Gvsp_BayerRG10_Packed = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x0027),
PixelType_Gvsp_BayerGB10_Packed = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x0028),
PixelType_Gvsp_BayerBG10_Packed = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x0029),
PixelType_Gvsp_BayerGR12_Packed = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x002A),
PixelType_Gvsp_BayerRG12_Packed = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x002B),
PixelType_Gvsp_BayerGB12_Packed = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x002C),
PixelType_Gvsp_BayerBG12_Packed = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x002D),
PixelType_Gvsp_BayerGR16 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x002E),
PixelType_Gvsp_BayerRG16 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x002F),
PixelType_Gvsp_BayerGB16 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0030),
PixelType_Gvsp_BayerBG16 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0031),
// RGB Packed buffer format defines
PixelType_Gvsp_RGB8_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(24) | 0x0014),
PixelType_Gvsp_BGR8_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(24) | 0x0015),
PixelType_Gvsp_RGBA8_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(32) | 0x0016),
PixelType_Gvsp_BGRA8_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(32) | 0x0017),
PixelType_Gvsp_RGB10_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(48) | 0x0018),
PixelType_Gvsp_BGR10_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(48) | 0x0019),
PixelType_Gvsp_RGB12_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(48) | 0x001A),
PixelType_Gvsp_BGR12_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(48) | 0x001B),
PixelType_Gvsp_RGB16_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(48) | 0x0033),
PixelType_Gvsp_BGR16_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(48) | 0x004B),
PixelType_Gvsp_RGBA16_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(64) | 0x0064),
PixelType_Gvsp_BGRA16_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(64) | 0x0051),
PixelType_Gvsp_RGB10V1_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(32) | 0x001C),
PixelType_Gvsp_RGB10V2_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(32) | 0x001D),
PixelType_Gvsp_RGB12V1_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(36) | 0X0034),
PixelType_Gvsp_RGB565_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(16) | 0x0035),
PixelType_Gvsp_BGR565_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(16) | 0X0036),
// YUV Packed buffer format defines
PixelType_Gvsp_YUV411_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(12) | 0x001E),
PixelType_Gvsp_YUV422_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(16) | 0x001F),
PixelType_Gvsp_YUV422_YUYV_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(16) | 0x0032),
PixelType_Gvsp_YUV444_Packed = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(24) | 0x0020),
PixelType_Gvsp_YCBCR8_CBYCR = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(24) | 0x003A),
PixelType_Gvsp_YCBCR422_8 = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(16) | 0x003B),
PixelType_Gvsp_YCBCR422_8_CBYCRY = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(16) | 0x0043),
PixelType_Gvsp_YCBCR411_8_CBYYCRYY = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(12) | 0x003C),
PixelType_Gvsp_YCBCR601_8_CBYCR = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(24) | 0x003D),
PixelType_Gvsp_YCBCR601_422_8 = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(16) | 0x003E),
PixelType_Gvsp_YCBCR601_422_8_CBYCRY = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(16) | 0x0044),
PixelType_Gvsp_YCBCR601_411_8_CBYYCRYY = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(12) | 0x003F),
PixelType_Gvsp_YCBCR709_8_CBYCR = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(24) | 0x0040),
PixelType_Gvsp_YCBCR709_422_8 = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(16) | 0x0041),
PixelType_Gvsp_YCBCR709_422_8_CBYCRY = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(16) | 0x0045),
PixelType_Gvsp_YCBCR709_411_8_CBYYCRYY = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(12) | 0x0042),
// RGB Planar buffer format defines
PixelType_Gvsp_RGB8_Planar = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(24) | 0x0021),
PixelType_Gvsp_RGB10_Planar = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(48) | 0x0022),
PixelType_Gvsp_RGB12_Planar = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(48) | 0x0023),
PixelType_Gvsp_RGB16_Planar = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(48) | 0x0024),
// 自定义的图片格式
PixelType_Gvsp_Jpeg = (MV_GVSP_PIX_CUSTOM | MV_PIXEL_BIT_COUNT(24) | 0x0001),
PixelType_Gvsp_Coord3D_ABC32f =
(MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(96) | 0x00C0), //0x026000C0
PixelType_Gvsp_Coord3D_ABC32f_Planar =
(MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(96) | 0x00C1), //0x026000C1
// 该值被废弃,请参考PixelType_Gvsp_Coord3D_AC32f_64; the value is discarded
PixelType_Gvsp_Coord3D_AC32f = (MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(40) | 0x00C2),
// 该值被废弃; the value is discarded (已放入Chunkdata)
PixelType_Gvsp_COORD3D_DEPTH_PLUS_MASK =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(28) | 0x0001),
PixelType_Gvsp_Coord3D_ABC32 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(96) | 0x3001), //0x82603001
PixelType_Gvsp_Coord3D_AB32f =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(64) | 0x3002), //0x82403002
PixelType_Gvsp_Coord3D_AB32 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(64) | 0x3003), //0x82403003
PixelType_Gvsp_Coord3D_AC32f_64 =
(MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(64) | 0x00C2), //0x024000C2
PixelType_Gvsp_Coord3D_AC32f_Planar =
(MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(64) | 0x00C3), //0x024000C3
PixelType_Gvsp_Coord3D_AC32 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(64) | 0x3004), //0x82403004
PixelType_Gvsp_Coord3D_A32f = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(32) | 0x00BD), //0x012000BD
PixelType_Gvsp_Coord3D_A32 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(32) | 0x3005), //0x81203005
PixelType_Gvsp_Coord3D_C32f = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(32) | 0x00BF), //0x012000BF
PixelType_Gvsp_Coord3D_C32 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(32) | 0x3006), //0x81203006
PixelType_Gvsp_Coord3D_ABC16 =
(MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(48) | 0x00B9), //0x023000B9
PixelType_Gvsp_Coord3D_C16 = (MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x00B8), //0x011000B8
//无损压缩像素格式定义
PixelType_Gvsp_HB_Mono8 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(8) | 0x0001),
PixelType_Gvsp_HB_Mono10 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0003),
PixelType_Gvsp_HB_Mono10_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x0004),
PixelType_Gvsp_HB_Mono12 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0005),
PixelType_Gvsp_HB_Mono12_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x0006),
PixelType_Gvsp_HB_Mono16 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0007),
PixelType_Gvsp_HB_BayerGR8 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(8) | 0x0008),
PixelType_Gvsp_HB_BayerRG8 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(8) | 0x0009),
PixelType_Gvsp_HB_BayerGB8 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(8) | 0x000A),
PixelType_Gvsp_HB_BayerBG8 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(8) | 0x000B),
PixelType_Gvsp_HB_BayerRBGG8 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(8) | 0x0046),
PixelType_Gvsp_HB_BayerGR10 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x000C),
PixelType_Gvsp_HB_BayerRG10 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x000D),
PixelType_Gvsp_HB_BayerGB10 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x000E),
PixelType_Gvsp_HB_BayerBG10 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x000F),
PixelType_Gvsp_HB_BayerGR12 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0010),
PixelType_Gvsp_HB_BayerRG12 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0011),
PixelType_Gvsp_HB_BayerGB12 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0012),
PixelType_Gvsp_HB_BayerBG12 =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(16) | 0x0013),
PixelType_Gvsp_HB_BayerGR10_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x0026),
PixelType_Gvsp_HB_BayerRG10_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x0027),
PixelType_Gvsp_HB_BayerGB10_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x0028),
PixelType_Gvsp_HB_BayerBG10_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x0029),
PixelType_Gvsp_HB_BayerGR12_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x002A),
PixelType_Gvsp_HB_BayerRG12_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x002B),
PixelType_Gvsp_HB_BayerGB12_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x002C),
PixelType_Gvsp_HB_BayerBG12_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_MONO | MV_PIXEL_BIT_COUNT(12) | 0x002D),
PixelType_Gvsp_HB_YUV422_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(16) | 0x001F),
PixelType_Gvsp_HB_YUV422_YUYV_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(16) | 0x0032),
PixelType_Gvsp_HB_RGB8_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(24) | 0x0014),
PixelType_Gvsp_HB_BGR8_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(24) | 0x0015),
PixelType_Gvsp_HB_RGBA8_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(32) | 0x0016),
PixelType_Gvsp_HB_BGRA8_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(32) | 0x0017),
PixelType_Gvsp_HB_RGB16_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(48) | 0x0033),
PixelType_Gvsp_HB_BGR16_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(48) | 0x004B),
PixelType_Gvsp_HB_RGBA16_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(64) | 0x0064),
PixelType_Gvsp_HB_BGRA16_Packed =
(MV_GVSP_PIX_CUSTOM | MV_GVSP_PIX_COLOR | MV_PIXEL_BIT_COUNT(64) | 0x0051),
};
//enum MvUsbPixelType
//{
//
//};
//跨平台定义
//Cross Platform Definition
#ifdef WIN32
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
#else
#include <stdint.h>
#endif
#endif /* _MV_PIXEL_TYPE_H_ */
@@ -0,0 +1,80 @@
;@~chinese
;该配置文件列出了部分可配置的参数,其他可配置参数请参考软件安装路径下 /opt/(软件名)/doc/工业相机SDK可配置化参数表.xlsx
;修改配置后上层应用程序要重新启动
;不分设备类型的通用参数
;@~english
;The configuration file lists some configurable parameters, other configurable parameters please refer to the software installation path /opt/(Software)/doc/Camera SDK configurable parameter table.xlsx
;When modifying the configuration, the upper application need restarted
;Generic parameters that do not distinguish device types
[COMMON]
;@~chinese
;设置SDK内部图像缓存节点个数,若调用接口(MV_CC_SetImageNodeNum)主动设置,则此参数无效;若是外部分配缓存模式(即调用MV_CC_RegisterBuffer)此参数也无效;不支持MV_CAMERALINK_DEVICE 类型的设备
;SDK实际分配的节点个数 = SDK内部预分配的个数 + ImageNodeNum;其中SDK内部预分配的个数仅供内部使用,比如双U内部会多分配2个节点
;不同相机因为取流方式不同,SDK内部预分配的个数不同
;@~english
;Set the number of image cache nodes within the SDK, and if you call the interface (MV_CC_SetImageNodeNum or MV_CC_RegisterBuffer), the parameter is invalid; not support MV_CAMERALINK_DEVICE device
;The actual number of image cache nodes allocated by the SDK = the number of pre allocated nodes within the SDK + ImageNodeNum;
;The internally pre-allocated nodes are reserved for internal use only, such as the dual-U configuration which allocates an additional 2 nodes internally.
;Different cameras default to different pre allocated nodes.
ImageNodeNum=1
;@~chinese
;网口相机相关参数
;@~english
;The parameters of Gige camera
[GIGE]
;@~chinese
;设置GVCP命令超时时间,默认500ms,范围:0-10000ms
;@~english
;Set GVCP command timeout time, the default value is 500ms, range: 0-10000ms
GvcpTimeout=500
;@~chinese
;U口相机相关参数
;@~english
;The parameters of U3V camera
[U3V]
;@~chinese
;设置U3V的传输包大小,Byte,默认为1Mrang>=0x400
;@~english
;Set transfer size of U3V device, the unit is Byte, Default 1Mrang: >=0x400
TransferSize=1048576
;@~chinese
;设置流包间隔超时时间,默认50ms,当超时时间>1000ms会关闭断流恢复机制
;@~english
;Set stream payload interval timeout, Default 50ms;
StreamPayloadTimeout=50
;@~chinese
;设置出流寄存器读写超时时间,默认30ms
;@~english
;Set stream control register timeout, Default 30ms
SIControlRegTimeout=30
;@~chinese
;设置控制寄存器读写超时时间,默认1000ms
;除SI寄存器外
;@~english
;Set control Reg timeout ms, Default 1000ms
;Except SI Reg
SyncTimeout=1000
;@~chinese
;CameraLink相机相关参数
;@~english
;The parameters of CameraLink camera
[CAML]
;@~chinese
;图像处理相关的参数
;@~english
;The parameters of image processing
[IMG_PROCESSING]
;@~chinese
;设置插值算法类型,0-快速 1-均衡 2-最优 3-最优+(默认为均衡)
;@~english
;Interpolation algorithm type setting, 0-Fast 1-Equilibrium 2-Optimal 3-Optimal+(the default value is 1-Equilibrium)
BayerCvtQuality=1
;@~chinese
;设置插值算法处理线程个数,0-自适应 其他-具体线程个数(1,2,3,...)(默认线程个数为4
;@~english
;Set the interpolation algorithm of thread handle count, 0-self-adapting, other-number of specific thread count(1,2,3,...) (the default thread count is 4)
BayerCvtThreadNum=4
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,48 @@
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, SetEnvironmentVariable
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
bringup_dir = get_package_share_directory("hik_camera_ros2_driver")
params_file = LaunchConfiguration("params_file")
log_level = LaunchConfiguration("log_level")
stdout_linebuf_envvar = SetEnvironmentVariable(
"RCUTILS_LOGGING_BUFFERED_STREAM", "1"
)
colorized_output_envvar = SetEnvironmentVariable("RCUTILS_COLORIZED_OUTPUT", "1")
declare_params_file_cmd = DeclareLaunchArgument(
"params_file",
default_value=os.path.join(bringup_dir, "config", "camera_params_cam1.yaml"),
description="Parameter file path for cam1 (serial: DA9492688)",
)
declare_log_level_cmd = DeclareLaunchArgument(
"log_level", default_value="info", description="log level"
)
start_hik_camera_cmd = Node(
name="hik_camera_cam1",
package="hik_camera_ros2_driver",
executable="hik_camera_ros2_driver_node",
parameters=[params_file],
arguments=["--ros-args", "--log-level", log_level],
output="screen",
)
ld = LaunchDescription()
ld.add_action(stdout_linebuf_envvar)
ld.add_action(colorized_output_envvar)
ld.add_action(declare_params_file_cmd)
ld.add_action(declare_log_level_cmd)
ld.add_action(start_hik_camera_cmd)
return ld
@@ -0,0 +1,48 @@
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, SetEnvironmentVariable
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
bringup_dir = get_package_share_directory("hik_camera_ros2_driver")
params_file = LaunchConfiguration("params_file")
log_level = LaunchConfiguration("log_level")
stdout_linebuf_envvar = SetEnvironmentVariable(
"RCUTILS_LOGGING_BUFFERED_STREAM", "1"
)
colorized_output_envvar = SetEnvironmentVariable("RCUTILS_COLORIZED_OUTPUT", "1")
declare_params_file_cmd = DeclareLaunchArgument(
"params_file",
default_value=os.path.join(bringup_dir, "config", "camera_params_cam2.yaml"),
description="Parameter file path for cam2 (serial: DB074264)",
)
declare_log_level_cmd = DeclareLaunchArgument(
"log_level", default_value="info", description="log level"
)
start_hik_camera_cmd = Node(
name="hik_camera_cam2",
package="hik_camera_ros2_driver",
executable="hik_camera_ros2_driver_node",
parameters=[params_file],
arguments=["--ros-args", "--log-level", log_level],
output="screen",
)
ld = LaunchDescription()
ld.add_action(stdout_linebuf_envvar)
ld.add_action(colorized_output_envvar)
ld.add_action(declare_params_file_cmd)
ld.add_action(declare_log_level_cmd)
ld.add_action(start_hik_camera_cmd)
return ld
@@ -0,0 +1,48 @@
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, SetEnvironmentVariable
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
bringup_dir = get_package_share_directory("hik_camera_ros2_driver")
params_file = LaunchConfiguration("params_file")
log_level = LaunchConfiguration("log_level")
stdout_linebuf_envvar = SetEnvironmentVariable(
"RCUTILS_LOGGING_BUFFERED_STREAM", "1"
)
colorized_output_envvar = SetEnvironmentVariable("RCUTILS_COLORIZED_OUTPUT", "1")
declare_params_file_cmd = DeclareLaunchArgument(
"params_file",
default_value=os.path.join(bringup_dir, "config", "camera_params_cam3.yaml"),
description="Parameter file path for cam3 (serial: DB0159632)",
)
declare_log_level_cmd = DeclareLaunchArgument(
"log_level", default_value="info", description="log level"
)
start_hik_camera_cmd = Node(
name="hik_camera_cam3",
package="hik_camera_ros2_driver",
executable="hik_camera_ros2_driver_node",
parameters=[params_file],
arguments=["--ros-args", "--log-level", log_level],
output="screen",
)
ld = LaunchDescription()
ld.add_action(stdout_linebuf_envvar)
ld.add_action(colorized_output_envvar)
ld.add_action(declare_params_file_cmd)
ld.add_action(declare_log_level_cmd)
ld.add_action(start_hik_camera_cmd)
return ld
@@ -0,0 +1,66 @@
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, SetEnvironmentVariable
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
bringup_dir = get_package_share_directory("hik_camera_ros2_driver")
cam1_params_file = LaunchConfiguration("cam1_params_file")
cam2_params_file = LaunchConfiguration("cam2_params_file")
log_level = LaunchConfiguration("log_level")
stdout_linebuf_envvar = SetEnvironmentVariable(
"RCUTILS_LOGGING_BUFFERED_STREAM", "1"
)
colorized_output_envvar = SetEnvironmentVariable("RCUTILS_COLORIZED_OUTPUT", "1")
declare_cam1_params_file_cmd = DeclareLaunchArgument(
"cam1_params_file",
default_value=os.path.join(bringup_dir, "config", "camera_params_cam1.yaml"),
description="Parameter file path for cam1 (serial: DA9492688)",
)
declare_cam2_params_file_cmd = DeclareLaunchArgument(
"cam2_params_file",
default_value=os.path.join(bringup_dir, "config", "camera_params_cam2.yaml"),
description="Parameter file path for cam2 (serial: DB074264)",
)
declare_log_level_cmd = DeclareLaunchArgument(
"log_level", default_value="info", description="log level"
)
start_hik_camera1_cmd = Node(
name="hik_camera_cam1",
package="hik_camera_ros2_driver",
executable="hik_camera_ros2_driver_node",
parameters=[cam1_params_file],
arguments=["--ros-args", "--log-level", log_level],
output="screen",
)
start_hik_camera2_cmd = Node(
name="hik_camera_cam2",
package="hik_camera_ros2_driver",
executable="hik_camera_ros2_driver_node",
parameters=[cam2_params_file],
arguments=["--ros-args", "--log-level", log_level],
output="screen",
)
ld = LaunchDescription()
ld.add_action(stdout_linebuf_envvar)
ld.add_action(colorized_output_envvar)
ld.add_action(declare_cam1_params_file_cmd)
ld.add_action(declare_cam2_params_file_cmd)
ld.add_action(declare_log_level_cmd)
ld.add_action(start_hik_camera1_cmd)
ld.add_action(start_hik_camera2_cmd)
return ld
@@ -0,0 +1,57 @@
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, SetEnvironmentVariable
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
# Get the package directory
bringup_dir = get_package_share_directory("hik_camera_ros2_driver")
params_file = LaunchConfiguration("params_file")
log_level = LaunchConfiguration("log_level")
# Create the launch configuration variables
stdout_linebuf_envvar = SetEnvironmentVariable(
"RCUTILS_LOGGING_BUFFERED_STREAM", "1"
)
colorized_output_envvar = SetEnvironmentVariable("RCUTILS_COLORIZED_OUTPUT", "1")
# Declare the launch arguments
declare_params_file_cmd = DeclareLaunchArgument(
"params_file",
default_value=os.path.join(bringup_dir, "config", "camera_params.yaml"),
description="The joystick configuration file path",
)
declare_log_level_cmd = DeclareLaunchArgument(
"log_level", default_value="info", description="log level"
)
start_hik_camera_cmd = Node(
name="hik_camera_ros2_driver",
package="hik_camera_ros2_driver",
executable="hik_camera_ros2_driver_node",
parameters=[params_file],
arguments=["--ros-args", "--log-level", log_level],
output="screen",
)
# Create the launch description and populate
ld = LaunchDescription()
# Set environment variables
ld.add_action(stdout_linebuf_envvar)
ld.add_action(colorized_output_envvar)
# Declare the launch arguments
ld.add_action(declare_params_file_cmd)
ld.add_action(declare_log_level_cmd)
# Add the actions to launch the nodes
ld.add_action(start_hik_camera_cmd)
return ld
@@ -0,0 +1,84 @@
import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, SetEnvironmentVariable
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
bringup_dir = get_package_share_directory("hik_camera_ros2_driver")
cam1_params_file = LaunchConfiguration("cam1_params_file")
cam2_params_file = LaunchConfiguration("cam2_params_file")
cam3_params_file = LaunchConfiguration("cam3_params_file")
log_level = LaunchConfiguration("log_level")
stdout_linebuf_envvar = SetEnvironmentVariable(
"RCUTILS_LOGGING_BUFFERED_STREAM", "1"
)
colorized_output_envvar = SetEnvironmentVariable("RCUTILS_COLORIZED_OUTPUT", "1")
declare_cam1_params_file_cmd = DeclareLaunchArgument(
"cam1_params_file",
default_value=os.path.join(bringup_dir, "config", "camera_params_cam1.yaml"),
description="Parameter file path for cam1 (serial: DA9492688)",
)
declare_cam2_params_file_cmd = DeclareLaunchArgument(
"cam2_params_file",
default_value=os.path.join(bringup_dir, "config", "camera_params_cam2.yaml"),
description="Parameter file path for cam2 (serial: DB0174264)",
)
declare_cam3_params_file_cmd = DeclareLaunchArgument(
"cam3_params_file",
default_value=os.path.join(bringup_dir, "config", "camera_params_cam3.yaml"),
description="Parameter file path for cam3 (serial: TODO — fill in camera_params_cam3.yaml)",
)
declare_log_level_cmd = DeclareLaunchArgument(
"log_level", default_value="info", description="log level"
)
start_hik_camera1_cmd = Node(
name="hik_camera_cam1",
package="hik_camera_ros2_driver",
executable="hik_camera_ros2_driver_node",
parameters=[cam1_params_file],
arguments=["--ros-args", "--log-level", log_level],
output="screen",
)
start_hik_camera2_cmd = Node(
name="hik_camera_cam2",
package="hik_camera_ros2_driver",
executable="hik_camera_ros2_driver_node",
parameters=[cam2_params_file],
arguments=["--ros-args", "--log-level", log_level],
output="screen",
)
start_hik_camera3_cmd = Node(
name="hik_camera_cam3",
package="hik_camera_ros2_driver",
executable="hik_camera_ros2_driver_node",
parameters=[cam3_params_file],
arguments=["--ros-args", "--log-level", log_level],
output="screen",
)
ld = LaunchDescription()
ld.add_action(stdout_linebuf_envvar)
ld.add_action(colorized_output_envvar)
ld.add_action(declare_cam1_params_file_cmd)
ld.add_action(declare_cam2_params_file_cmd)
ld.add_action(declare_cam3_params_file_cmd)
ld.add_action(declare_log_level_cmd)
ld.add_action(start_hik_camera1_cmd)
ld.add_action(start_hik_camera2_cmd)
ld.add_action(start_hik_camera3_cmd)
return ld
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>hik_camera_ros2_driver</name>
<version>1.0.0</version>
<description>hik-robot industrial camera driver ros2</description>
<maintainer email="lihanchen2004@163.com">Lihan Chen</maintainer>
<maintainer email="xie13318782539@163.com">Zikang Xie</maintainer>
<license>Apache-2.0</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>rclcpp_components</depend>
<depend>sensor_msgs</depend>
<depend>image_transport</depend>
<depend>image_transport_plugins</depend>
<depend>camera_info_manager</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
@@ -0,0 +1,714 @@
#include <cmath>
#include <deque>
#include <string>
#include <utility>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "MvCameraControl.h"
#include "camera_info_manager/camera_info_manager.hpp"
#include "image_transport/image_transport.hpp"
#include "rclcpp/logging.hpp"
#include "rclcpp/utilities.hpp"
namespace hik_camera_ros2_driver
{
struct TimeStamp {
int64_t high;
int64_t low;
};
class HikCameraRos2DriverNode : public rclcpp::Node
{
public:
explicit HikCameraRos2DriverNode(const rclcpp::NodeOptions & options)
: Node("hik_camera_ros2_driver", options)
{
RCLCPP_INFO(this->get_logger(), "Starting HikCameraRos2DriverNode!");
initializeCamera();
declareParameters();
startCamera();
initSharedMemory();
params_callback_handle_ = this->add_on_set_parameters_callback(
std::bind(&HikCameraRos2DriverNode::dynamicParametersCallback, this, std::placeholders::_1));
capture_thread_ = std::thread(&HikCameraRos2DriverNode::captureLoop, this);
}
~HikCameraRos2DriverNode() override
{
if (capture_thread_.joinable()) {
capture_thread_.join();
}
if (camera_handle_) {
MV_CC_StopGrabbing(camera_handle_);
MV_CC_CloseDevice(camera_handle_);
MV_CC_DestroyHandle(&camera_handle_);
}
if (pointt_ != nullptr && pointt_ != MAP_FAILED) {
munmap(pointt_, sizeof(TimeStamp));
pointt_ = nullptr;
}
RCLCPP_INFO(this->get_logger(), "HikCameraRos2DriverNode destroyed!");
}
private:
bool initializeCamera()
{
// serial_number을 먼저 선언하여 카메라 선택에 사용
serial_number_ = this->declare_parameter("serial_number", std::string(""));
MV_CC_DEVICE_INFO_LIST device_list;
// USB + GigE 동시 열거
while (rclcpp::ok()) {
n_ret_ = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &device_list);
if (n_ret_ != MV_OK) {
RCLCPP_ERROR(this->get_logger(), "Failed to enumerate devices, retrying...");
std::this_thread::sleep_for(std::chrono::seconds(1));
} else if (device_list.nDeviceNum == 0) {
RCLCPP_ERROR(this->get_logger(), "No camera found, retrying...");
std::this_thread::sleep_for(std::chrono::seconds(1));
} else {
RCLCPP_INFO(this->get_logger(), "Found camera count = %d", device_list.nDeviceNum);
break;
}
}
// 시리얼 번호 기반 카메라 선택
unsigned int target_index = 0;
if (!serial_number_.empty()) {
bool found = false;
for (unsigned int i = 0; i < device_list.nDeviceNum; i++) {
MV_CC_DEVICE_INFO * info = device_list.pDeviceInfo[i];
std::string serial;
if (info->nTLayerType == MV_USB_DEVICE) {
serial = std::string(
reinterpret_cast<char *>(info->SpecialInfo.stUsb3VInfo.chSerialNumber));
} else if (info->nTLayerType == MV_GIGE_DEVICE) {
serial = std::string(
reinterpret_cast<char *>(info->SpecialInfo.stGigEInfo.chSerialNumber));
}
if (serial == serial_number_) {
target_index = i;
found = true;
RCLCPP_INFO(this->get_logger(), "Matched camera serial: %s", serial.c_str());
break;
}
}
if (!found) {
RCLCPP_FATAL(
this->get_logger(), "Camera with serial [%s] not found!", serial_number_.c_str());
rclcpp::shutdown();
return false;
}
}
n_ret_ = MV_CC_CreateHandle(&camera_handle_, device_list.pDeviceInfo[target_index]);
if (n_ret_ != MV_OK) {
RCLCPP_ERROR(this->get_logger(), "Failed to create camera handle!");
return false;
}
n_ret_ = MV_CC_OpenDevice(camera_handle_);
if (n_ret_ != MV_OK) {
RCLCPP_ERROR(this->get_logger(), "Failed to open camera device!");
return false;
}
// Get camera information
n_ret_ = MV_CC_GetImageInfo(camera_handle_, &img_info_);
if (n_ret_ != MV_OK) {
RCLCPP_ERROR(this->get_logger(), "Failed to get camera image info!");
return false;
}
// Init convert param
image_msg_.data.reserve(img_info_.nHeightMax * img_info_.nWidthMax * 3);
convert_param_.nWidth = img_info_.nWidthValue;
convert_param_.nHeight = img_info_.nHeightValue;
convert_param_.enDstPixelType = PixelType_Gvsp_RGB8_Packed;
return true;
}
void declareParameters()
{
rcl_interfaces::msg::ParameterDescriptor param_desc;
MVCC_FLOATVALUE f_value;
param_desc.integer_range.resize(1);
param_desc.integer_range[0].step = 1;
// Trigger mode
trigger_enable_ = this->declare_parameter("trigger_enable", false);
use_trigger_timestamp_ = this->declare_parameter("use_trigger_timestamp", false);
enable_interval_log_ = this->declare_parameter("enable_interval_log", false);
dev_clock_window_sec_ = this->declare_parameter("dev_clock_window_sec", 10.0);
dev_clock_recalib_interval_sec_ =
this->declare_parameter("dev_clock_recalib_interval_sec", 2.0);
if (trigger_enable_) {
// 하드웨어 트리거: 프레임률은 트리거 신호가 제어
MV_CC_SetBoolValue(camera_handle_, "AcquisitionFrameRateEnable", false);
MV_CC_SetEnumValue(camera_handle_, "TriggerMode", 1); // On
MV_CC_SetEnumValue(camera_handle_, "TriggerSource", MV_TRIGGER_SOURCE_LINE0);
RCLCPP_INFO(this->get_logger(), "Hardware trigger enabled (LINE0)");
} else {
MV_CC_SetEnumValue(camera_handle_, "TriggerMode", 0); // Off
// Acquisition frame rate (프리런 모드에서만 사용)
param_desc.description = "Acquisition frame rate in Hz";
MV_CC_GetFloatValue(camera_handle_, "AcquisitionFrameRate", &f_value);
param_desc.integer_range[0].from_value = static_cast<int64_t>(f_value.fMin);
param_desc.integer_range[0].to_value = static_cast<int64_t>(f_value.fMax);
double acquisition_frame_rate =
this->declare_parameter("acquisition_frame_rate", 165.0, param_desc);
MV_CC_SetBoolValue(camera_handle_, "AcquisitionFrameRateEnable", true);
MV_CC_SetFloatValue(camera_handle_, "AcquisitionFrameRate", acquisition_frame_rate);
RCLCPP_INFO(this->get_logger(), "Acquisition frame rate: %f", acquisition_frame_rate);
}
// Exposure — 모든 파라미터를 항상 선언하여 런타임 모드 전환 지원
MV_CC_GetFloatValue(camera_handle_, "ExposureTime", &f_value);
exposure_auto_ = this->declare_parameter("exposure_auto", false);
param_desc.description = "Auto exposure target brightness (0-255)";
param_desc.integer_range[0].from_value = 0;
param_desc.integer_range[0].to_value = 255;
exposure_auto_target_brightness_ =
this->declare_parameter("exposure_auto_target_brightness", 128, param_desc);
param_desc.description = "Auto exposure lower limit in microseconds";
param_desc.integer_range[0].from_value = static_cast<int64_t>(f_value.fMin);
param_desc.integer_range[0].to_value = static_cast<int64_t>(f_value.fMax);
exposure_auto_min_ = this->declare_parameter("exposure_auto_min", 100.0, param_desc);
param_desc.description = "Auto exposure upper limit in microseconds";
exposure_auto_max_ = this->declare_parameter("exposure_auto_max", 10000.0, param_desc);
param_desc.description = "Manual exposure time in microseconds";
exposure_time_ = this->declare_parameter("exposure_time", 5000, param_desc);
applyExposureMode();
// Gain (manual only — GainAuto=Off)
MV_CC_SetEnumValue(camera_handle_, "GainAuto", 0);
param_desc.description = "Gain";
MV_CC_GetFloatValue(camera_handle_, "Gain", &f_value);
param_desc.integer_range[0].from_value = static_cast<int64_t>(f_value.fMin);
param_desc.integer_range[0].to_value = static_cast<int64_t>(f_value.fMax);
double gain = this->declare_parameter("gain", f_value.fCurValue, param_desc);
MV_CC_SetFloatValue(camera_handle_, "Gain", gain);
RCLCPP_INFO(this->get_logger(), "Gain: %f", gain);
// White balance — 기본은 수동(Off) + 고정 R/G/B 비율.
// 이유: 카메라별로 독립 Continuous AWB를 켜두면 두 카메라가 서로 다른 화각을 보고
// 각자 다른 색으로 수렴해서, 겹치는 영역의 색감이 안 맞게 된다. 두 카메라에
// 동일한 balance_ratio_red/green/blue 값을 넣어야 겹치는 부분 색이 일치한다.
param_desc.description = "Auto white balance (Continuous). false면 수동 R/G/B 비율 사용";
balance_white_auto_ = this->declare_parameter("balance_white_auto", false);
MVCC_INTVALUE wb_range;
MV_CC_GetBalanceRatioRed(camera_handle_, &wb_range);
param_desc.integer_range[0].from_value = static_cast<int64_t>(wb_range.nMin);
param_desc.integer_range[0].to_value = static_cast<int64_t>(wb_range.nMax);
param_desc.description = "Manual white balance ratio - Red (balance_white_auto=false일 때만 적용)";
int balance_ratio_red = this->declare_parameter("balance_ratio_red", 1024, param_desc);
param_desc.description = "Manual white balance ratio - Green (balance_white_auto=false일 때만 적용)";
int balance_ratio_green = this->declare_parameter("balance_ratio_green", 1024, param_desc);
param_desc.description = "Manual white balance ratio - Blue (balance_white_auto=false일 때만 적용)";
int balance_ratio_blue = this->declare_parameter("balance_ratio_blue", 1024, param_desc);
if (balance_white_auto_) {
MV_CC_SetEnumValue(camera_handle_, "BalanceWhiteAuto", MV_BALANCEWHITE_AUTO_CONTINUOUS);
RCLCPP_INFO(this->get_logger(), "White balance: Continuous auto");
} else {
MV_CC_SetEnumValue(camera_handle_, "BalanceWhiteAuto", MV_BALANCEWHITE_AUTO_OFF);
MV_CC_SetBalanceRatioRed(camera_handle_, static_cast<unsigned int>(balance_ratio_red));
MV_CC_SetBalanceRatioGreen(camera_handle_, static_cast<unsigned int>(balance_ratio_green));
MV_CC_SetBalanceRatioBlue(camera_handle_, static_cast<unsigned int>(balance_ratio_blue));
RCLCPP_INFO(
this->get_logger(), "White balance: Manual R=%d G=%d B=%d",
balance_ratio_red, balance_ratio_green, balance_ratio_blue);
}
int status;
// ADC Bit Depth
param_desc.description = "ADC Bit Depth";
param_desc.additional_constraints = "Supported values: Bits_8, Bits_12";
std::string adc_bit_depth = this->declare_parameter("adc_bit_depth", "Bits_8", param_desc);
status = MV_CC_SetEnumValueByString(camera_handle_, "ADCBitDepth", adc_bit_depth.c_str());
if (status == MV_OK) {
RCLCPP_INFO(this->get_logger(), "ADC Bit Depth set to %s", adc_bit_depth.c_str());
} else {
RCLCPP_ERROR(this->get_logger(), "Failed to set ADC Bit Depth, status = %d", status);
}
// Pixel format
param_desc.description = "Pixel Format";
std::string pixel_format = this->declare_parameter("pixel_format", "RGB8Packed", param_desc);
status = MV_CC_SetEnumValueByString(camera_handle_, "PixelFormat", pixel_format.c_str());
if (status == MV_OK) {
RCLCPP_INFO(this->get_logger(), "Pixel Format set to %s", pixel_format.c_str());
} else {
RCLCPP_ERROR(this->get_logger(), "Failed to set Pixel Format, status = %d", status);
}
}
void startCamera()
{
bool use_sensor_data_qos = this->declare_parameter("use_sensor_data_qos", true);
camera_name_ = this->declare_parameter("camera_name", "camera");
frame_id_ = this->declare_parameter("frame_id", camera_name_ + "_optical_frame");
camera_topic_ = this->declare_parameter("camera_topic", camera_name_ + "/image");
auto qos = use_sensor_data_qos ? rmw_qos_profile_sensor_data : rmw_qos_profile_default;
camera_pub_ = image_transport::create_camera_publisher(this, camera_topic_, qos);
MV_CC_StartGrabbing(camera_handle_);
// Load camera info
camera_info_manager_ =
std::make_unique<camera_info_manager::CameraInfoManager>(this, camera_name_);
auto camera_info_url = this->declare_parameter(
"camera_info_url", "package://hik_camera_ros2_driver/config/camera_info.yaml");
if (camera_info_manager_->validateURL(camera_info_url)) {
camera_info_manager_->loadCameraInfo(camera_info_url);
camera_info_msg_ = camera_info_manager_->getCameraInfo();
} else {
RCLCPP_WARN(this->get_logger(), "Invalid camera info URL: %s", camera_info_url.c_str());
}
}
void initSharedMemory()
{
if (!use_trigger_timestamp_) {
return;
}
const char * user_name = getlogin();
if (user_name == nullptr) {
RCLCPP_WARN(this->get_logger(), "getlogin() failed, falling back to system time");
return;
}
std::string path = "/home/" + std::string(user_name) + "/timeshare";
int fd = open(path.c_str(), O_RDWR);
if (fd == -1) {
RCLCPP_WARN(
this->get_logger(),
"Failed to open shared memory file: %s (LiDAR driver may not be running yet)",
path.c_str());
return;
}
pointt_ = reinterpret_cast<TimeStamp *>(
mmap(nullptr, sizeof(TimeStamp), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
close(fd);
if (pointt_ == MAP_FAILED) {
RCLCPP_WARN(this->get_logger(), "mmap failed, falling back to system time");
pointt_ = nullptr;
return;
}
RCLCPP_INFO(this->get_logger(), "Shared memory timestamp enabled: %s", path.c_str());
}
void applyExposureMode()
{
if (exposure_auto_) {
MV_CC_SetEnumValue(camera_handle_, "ExposureAuto", 2); // Continuous
MV_CC_SetIntValue(camera_handle_, "AutoTargetBrightness",
static_cast<unsigned int>(exposure_auto_target_brightness_));
MV_CC_SetIntValue(camera_handle_, "AutoExposureTimeLowerLimit",
static_cast<unsigned int>(exposure_auto_min_));
MV_CC_SetIntValue(camera_handle_, "AutoExposureTimeUpperLimit",
static_cast<unsigned int>(exposure_auto_max_));
RCLCPP_INFO(this->get_logger(),
"Auto exposure: ON, brightness=%d, range=[%.0f, %.0f] us",
exposure_auto_target_brightness_, exposure_auto_min_, exposure_auto_max_);
} else {
MV_CC_SetEnumValue(camera_handle_, "ExposureAuto", 0); // Off
MV_CC_SetFloatValue(camera_handle_, "ExposureTime", static_cast<float>(exposure_time_));
RCLCPP_INFO(this->get_logger(), "Manual exposure: %d us", exposure_time_);
}
}
// 2026-07-29: nTriggerIndex는 이 카메라(MV-CS016-10UC USB3)에서 항상 0으로 고정되어
// 실사용 불가로 확인됨 (실기기 로그로 검증). 대신 nDevTimeStampHigh/Low(카메라 자체
// 자유구동 하드웨어 클럭)는 프레임마다 정상적으로 증가하는 것을 로그로 확인했다.
//
// 처음엔 시작 후 고정 3초 구간만 보고 회귀를 얼려서(freeze) 썼는데, 실기기 이동
// 테스트(bag: trigger_test__2_moving)에서 카메라-라이다 오프셋이 27초 동안
// -12ms → -16ms로 선형 드리프트하는 것이 확인됨 — 3초짜리 창만으로는 카메라
// 클럭의 실제 틱 주파수를 ~150ppm 정도의 오차로만 추정하게 되고, 이 오차가 세션이
// 길어질수록 그대로 누적된다. 그래서 한 번 얼리는 대신, 최근
// dev_clock_window_sec_ 구간의 슬라이딩 윈도우로 dev_clock_recalib_interval_sec_
// 마다 회귀를 반복 갱신한다 — 오차가 세션 길이에 비례해 누적되지 않고 창 하나의
// 오차 수준으로 항상 bounded 된다.
void recalibrateDevClock()
{
double sum_x = 0.0, sum_y = 0.0;
for (const auto & s : dev_clock_samples_) {
sum_x += s.first;
sum_y += s.second;
}
double n = static_cast<double>(dev_clock_samples_.size());
double mean_x = sum_x / n;
double mean_y = sum_y / n;
double num = 0.0, den = 0.0;
for (const auto & s : dev_clock_samples_) {
double dx = s.first - mean_x;
num += dx * (s.second - mean_y);
den += dx * dx;
}
double slope_ns_per_tick = (den > 0.0) ? (num / den) : 0.0;
if (!(den > 0.0) || !std::isfinite(slope_ns_per_tick) || slope_ns_per_tick <= 0.0) {
dev_clock_calib_fail_count_++;
RCLCPP_ERROR(
this->get_logger(),
"Device-clock timestamp (re)calibration failed (slope=%.6f, %zu samples, fail #%d).",
slope_ns_per_tick, dev_clock_samples_.size(), dev_clock_calib_fail_count_);
if (dev_clock_calib_fail_count_ >= 3) {
dev_clock_permanently_disabled_ = true;
RCLCPP_ERROR(
this->get_logger(),
"Device-clock timestamp calibration failed %d times in a row — this camera/SDK does "
"not usably populate nDevTimeStamp either. Falling back to per-frame shared-memory "
"timestamp for the rest of this session.",
dev_clock_calib_fail_count_);
}
return;
}
dev_clock_calib_fail_count_ = 0;
bool first_calibration = !dev_clock_usable_;
dev_clock_slope_ns_per_tick_ = slope_ns_per_tick;
dev_clock_mean_ticks_ = mean_x;
dev_clock_mean_rel_ns_ = mean_y;
dev_clock_usable_ = true;
if (first_calibration || enable_interval_log_) {
RCLCPP_INFO(
this->get_logger(),
"Device-clock timestamp %scalibrated: %.6f ns/tick (%.3f MHz), %zu samples over %.2f s",
first_calibration ? "" : "re",
slope_ns_per_tick, 1000.0 / slope_ns_per_tick, dev_clock_samples_.size(),
(dev_clock_samples_.back().second - dev_clock_samples_.front().second) / 1e9);
}
}
rclcpp::Time getTimestamp(const MV_FRAME_OUT_INFO_EX & frame_info)
{
bool have_shared_ts = pointt_ != nullptr && pointt_ != MAP_FAILED && pointt_->low != 0;
if (trigger_enable_ && use_trigger_timestamp_ && !dev_clock_permanently_disabled_) {
double dev_ticks = static_cast<double>(
(static_cast<uint64_t>(frame_info.nDevTimeStampHigh) << 32) |
frame_info.nDevTimeStampLow);
if (have_shared_ts) {
if (!dev_clock_ref_set_) {
dev_clock_ref_ns_ = pointt_->low;
dev_clock_ref_set_ = true;
}
double rel_ns = static_cast<double>(pointt_->low - dev_clock_ref_ns_);
dev_clock_samples_.emplace_back(dev_ticks, rel_ns);
// 창 크기 판정을 먼저 하고, 실제로 사용한 뒤에 오래된 샘플을 잘라낸다.
// (이전 버그: pruning을 먼저 하면 창이 window_sec를 절대 넘을 수 없어서
// "10초 이상 쌓였는가" 조건이 영원히 참이 될 수 없었다 — 2026-07-29
// [DEVCLOCK-HB] 로그로 window_span_sec이 9.9~10.0에서 멈춰있는 것으로 확인.)
double window_span_sec =
(dev_clock_samples_.back().second - dev_clock_samples_.front().second) / 1e9;
bool warmed_up = window_span_sec >= dev_clock_window_sec_ &&
dev_clock_samples_.size() >= 10;
bool due = (rel_ns - dev_clock_last_recalib_rel_ns_) >=
dev_clock_recalib_interval_sec_ * 1e9;
if (++dev_clock_heartbeat_count_ % 50 == 0) {
RCLCPP_INFO(
this->get_logger(),
"[DEVCLOCK-HB] rel_ns=%.0f window_span_sec=%.3f samples=%zu usable=%d "
"permanently_disabled=%d",
rel_ns, window_span_sec, dev_clock_samples_.size(),
dev_clock_usable_, dev_clock_permanently_disabled_);
}
if (warmed_up && (!dev_clock_usable_ || due)) {
recalibrateDevClock();
dev_clock_last_recalib_rel_ns_ = rel_ns;
}
while (!dev_clock_samples_.empty() &&
(rel_ns - dev_clock_samples_.front().second) > dev_clock_window_sec_ * 1e9)
{
dev_clock_samples_.pop_front();
}
}
if (dev_clock_usable_) {
double rel_ns = dev_clock_mean_rel_ns_ +
(dev_ticks - dev_clock_mean_ticks_) * dev_clock_slope_ns_per_tick_;
int64_t final_ns = dev_clock_ref_ns_ + static_cast<int64_t>(std::llround(rel_ns));
return rclcpp::Time(final_ns);
}
}
if (enable_interval_log_) {
RCLCPP_INFO(
this->get_logger(),
"[TS-DEBUG] nFrameNum=%u nTriggerIndex=%u nDevTimeStampHigh=%u nDevTimeStampLow=%u "
"nHostTimeStamp=%ld",
frame_info.nFrameNum, frame_info.nTriggerIndex, frame_info.nDevTimeStampHigh,
frame_info.nDevTimeStampLow, frame_info.nHostTimeStamp);
}
if (have_shared_ts) {
return rclcpp::Time(pointt_->low);
}
return this->now();
}
void captureLoop()
{
MV_FRAME_OUT out_frame;
RCLCPP_INFO(this->get_logger(), "Publishing image!");
image_msg_.header.frame_id = frame_id_;
image_msg_.encoding = "rgb8";
// [DEBUG] 타임스탬프 간격 측정용 임시 변수
rclcpp::Time last_stamp{0, 0, RCL_ROS_TIME};
double interval_min_ms = 1e9, interval_max_ms = 0.0, interval_sum_ms = 0.0;
int interval_count = 0;
static constexpr int kLogEveryN = 10;
while (rclcpp::ok()) {
n_ret_ = MV_CC_GetImageBuffer(camera_handle_, &out_frame, 1000);
if (MV_OK == n_ret_) {
convert_param_.pDstBuffer = image_msg_.data.data();
convert_param_.nDstBufferSize = image_msg_.data.size();
convert_param_.pSrcData = out_frame.pBufAddr;
convert_param_.nSrcDataLen = out_frame.stFrameInfo.nFrameLen;
convert_param_.enSrcPixelType = out_frame.stFrameInfo.enPixelType;
MV_CC_ConvertPixelType(camera_handle_, &convert_param_);
image_msg_.header.stamp = getTimestamp(out_frame.stFrameInfo);
image_msg_.height = out_frame.stFrameInfo.nHeight;
image_msg_.width = out_frame.stFrameInfo.nWidth;
image_msg_.step = out_frame.stFrameInfo.nWidth * 3;
image_msg_.data.resize(image_msg_.width * image_msg_.height * 3);
if (enable_interval_log_) {
rclcpp::Time cur_stamp = image_msg_.header.stamp;
if (last_stamp.nanoseconds() != 0) {
double interval_ms = (cur_stamp - last_stamp).seconds() * 1000.0;
if (interval_ms > 0.0) {
interval_min_ms = std::min(interval_min_ms, interval_ms);
interval_max_ms = std::max(interval_max_ms, interval_ms);
interval_sum_ms += interval_ms;
interval_count++;
}
RCLCPP_INFO(this->get_logger(),
"[TS] interval=%.3f ms", interval_ms);
if (interval_count > 0 && interval_count % kLogEveryN == 0) {
RCLCPP_INFO(this->get_logger(),
"[TS] last %d frames: avg=%.3f ms min=%.3f ms max=%.3f ms jitter=%.3f ms",
kLogEveryN,
interval_sum_ms / interval_count,
interval_min_ms, interval_max_ms,
interval_max_ms - interval_min_ms);
interval_min_ms = 1e9; interval_max_ms = 0.0;
interval_sum_ms = 0.0; interval_count = 0;
}
}
last_stamp = cur_stamp;
}
camera_info_msg_.header = image_msg_.header;
camera_pub_.publish(image_msg_, camera_info_msg_);
MV_CC_FreeImageBuffer(camera_handle_, &out_frame);
static auto last_log_time = std::chrono::steady_clock::now();
auto now = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::seconds>(now - last_log_time).count() >= 3) {
MVCC_FLOATVALUE f_value;
MV_CC_GetFloatValue(camera_handle_, "ResultingFrameRate", &f_value);
RCLCPP_DEBUG(this->get_logger(), "ResultingFrameRate: %f Hz", f_value.fCurValue);
if (balance_white_auto_) {
// Continuous AWB로 수렴 중인 R/G/B 값을 주기적으로 노출 — 여러 카메라의 색을
// 맞추려면 이 값을 읽어서 balance_white_auto=false + 동일 값으로 고정시키면 됨.
MVCC_INTVALUE wb_r, wb_g, wb_b;
MV_CC_GetBalanceRatioRed(camera_handle_, &wb_r);
MV_CC_GetBalanceRatioGreen(camera_handle_, &wb_g);
MV_CC_GetBalanceRatioBlue(camera_handle_, &wb_b);
RCLCPP_INFO(
this->get_logger(), "White balance (auto, converging): R=%u G=%u B=%u",
wb_r.nCurValue, wb_g.nCurValue, wb_b.nCurValue);
}
last_log_time = now;
}
} else {
RCLCPP_WARN(this->get_logger(), "Get buffer failed! nRet: [%x]", n_ret_);
MV_CC_StopGrabbing(camera_handle_);
MV_CC_StartGrabbing(camera_handle_);
fail_count_++;
}
if (fail_count_ > 5) {
RCLCPP_FATAL(this->get_logger(), "Camera failed!");
rclcpp::shutdown();
}
}
}
rcl_interfaces::msg::SetParametersResult dynamicParametersCallback(
const std::vector<rclcpp::Parameter> & parameters)
{
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
for (const auto & param : parameters) {
const auto & type = param.get_type();
const auto & name = param.get_name();
int status = MV_OK;
if (type == rclcpp::ParameterType::PARAMETER_BOOL) {
if (name == "enable_interval_log") {
enable_interval_log_ = param.as_bool();
} else if (name == "exposure_auto") {
exposure_auto_ = param.as_bool();
applyExposureMode();
} else {
result.successful = false;
result.reason = "Unknown parameter: " + name;
continue;
}
} else if (type == rclcpp::ParameterType::PARAMETER_DOUBLE) {
if (name == "gain") {
status = MV_CC_SetFloatValue(camera_handle_, "Gain", param.as_double());
} else if (name == "exposure_auto_min") {
exposure_auto_min_ = param.as_double();
if (exposure_auto_) {
status = MV_CC_SetIntValue(camera_handle_, "AutoExposureTimeLowerLimit",
static_cast<unsigned int>(exposure_auto_min_));
}
} else if (name == "exposure_auto_max") {
exposure_auto_max_ = param.as_double();
if (exposure_auto_) {
status = MV_CC_SetIntValue(camera_handle_, "AutoExposureTimeUpperLimit",
static_cast<unsigned int>(exposure_auto_max_));
}
} else {
result.successful = false;
result.reason = "Unknown parameter: " + name;
continue;
}
} else if (type == rclcpp::ParameterType::PARAMETER_INTEGER) {
if (name == "exposure_time") {
exposure_time_ = static_cast<int>(param.as_int());
if (!exposure_auto_) {
status = MV_CC_SetFloatValue(camera_handle_, "ExposureTime",
static_cast<float>(exposure_time_));
}
} else if (name == "exposure_auto_target_brightness") {
exposure_auto_target_brightness_ = static_cast<int>(param.as_int());
if (exposure_auto_) {
status = MV_CC_SetIntValue(camera_handle_, "AutoTargetBrightness",
static_cast<unsigned int>(exposure_auto_target_brightness_));
}
} else {
result.successful = false;
result.reason = "Unknown parameter: " + name;
continue;
}
} else {
result.successful = false;
result.reason = "Unsupported parameter type for: " + name;
continue;
}
if (status != MV_OK) {
result.successful = false;
result.reason = "Failed to set " + name + ", status = " + std::to_string(status);
}
}
return result;
}
// Camera
void * camera_handle_ = nullptr;
int n_ret_ = MV_OK;
MV_IMAGE_BASIC_INFO img_info_;
MV_CC_PIXEL_CONVERT_PARAM convert_param_;
// ROS
sensor_msgs::msg::Image image_msg_;
sensor_msgs::msg::CameraInfo camera_info_msg_;
image_transport::CameraPublisher camera_pub_;
std::unique_ptr<camera_info_manager::CameraInfoManager> camera_info_manager_;
rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr params_callback_handle_;
std::string camera_name_;
std::string frame_id_;
std::string camera_topic_;
std::thread capture_thread_;
int fail_count_ = 0;
// Trigger & shared memory
bool trigger_enable_ = false;
bool use_trigger_timestamp_ = false;
bool enable_interval_log_ = false;
// Device-clock-based timestamp (see recalibrateDevClock() for design notes).
// Shared-memory nanosecond values (~1.8e18, Unix epoch) exceed double's 2^53 exact-
// integer range, so calibration samples store shared_mem_ns relative to
// dev_clock_ref_ns_ (the first sample ever seen, kept as an exact int64_t) rather
// than the raw epoch value.
double dev_clock_window_sec_ = 10.0;
double dev_clock_recalib_interval_sec_ = 2.0;
bool dev_clock_usable_ = false;
bool dev_clock_ref_set_ = false;
bool dev_clock_permanently_disabled_ = false;
int dev_clock_calib_fail_count_ = 0;
int64_t dev_clock_ref_ns_ = 0;
double dev_clock_last_recalib_rel_ns_ = -1e30;
int dev_clock_heartbeat_count_ = 0;
std::deque<std::pair<double, double>> dev_clock_samples_; // (dev_ticks, rel_shared_mem_ns)
double dev_clock_slope_ns_per_tick_ = 0.0;
double dev_clock_mean_ticks_ = 0.0;
double dev_clock_mean_rel_ns_ = 0.0;
// Exposure
bool exposure_auto_ = false;
int exposure_auto_target_brightness_ = 128;
double exposure_auto_min_ = 100.0;
double exposure_auto_max_ = 10000.0;
int exposure_time_ = 5000;
// White balance
bool balance_white_auto_ = false;
std::string serial_number_;
TimeStamp * pointt_ = nullptr;
};
} // namespace hik_camera_ros2_driver
#include "rclcpp_components/register_node_macro.hpp"
RCLCPP_COMPONENTS_REGISTER_NODE(hik_camera_ros2_driver::HikCameraRos2DriverNode)