0712
- 关于如何配置log头文件
- 头文件的撰写
#define LI_LOG_INFO_STREAM(args) \
do { \
std::ostringstream ostr; \
ostr << FILE_LINE << " " << args; \
FormatLiLog::LogInfo(ostr.str().c_str()); \
ostr.str(""); \
} while (0);
这是一个宏定义,用于实现日志信息的打印和记录。
具体含义如下:
LI_LOG_INFO_STREAM(args)
是宏的名称,你可以通过调用该宏来输出日志信息。- 宏定义使用了
do { ... } while (0)
结构,这是为了确保宏在使用时表现得像单个语句,避免出现错误。这种技巧通常被称为“do-while 零次循环”或“封装技巧”。 - 在宏的定义中,首先创建了一个
std::ostringstream
对象ostr
,它用于构建日志信息。 <<
运算符用于将文件名、行号和传递给宏的参数(args
)连接起来,并将结果写入到ostr
中。- 接下来,使用
FormatLiLog::LogInfo()
函数将ostr.str().c_str()
转换为 C 风格字符串并打印输出。 - 最后,清空
ostr
的内容以便下一条日志的构建和记录。
通过调用 LI_LOG_INFO_STREAM(args)
宏,你可以将指定的信息与文件名和行号一起记录在日志中,方便进行调试和问题追踪。
- 函数接收可变参数
函数是可以接受(FMT, ...)
作为形参的。这种形式的参数通常用于实现可变参数函数。
在 C++ 中,可变参数函数可以使用 std::va_list
和 std::va_start
、std::va_arg
、std::va_end
等宏和函数来处理可变参数列表。这些是 C 标准库中的功能,在 C++ 中也可用。
以下是一个示例,展示了如何使用 (FMT, ...)
形参来定义一个可变参数函数:
#include <cstdarg>
#include <iostream>
void printFormatted(const char* format, ...) {
std::va_list args;
va_start(args, format);
// 使用 va_arg 处理可变参数
while (*format != '\0') {
if (*format == '%') {
if (*(format + 1) == 'd') {
int value = va_arg(args, int);
std::cout << value << " ";
format += 2;
continue;
}
// 处理其他类型的参数
}
std::cout << *format;
++format;
}
va_end(args);
}
int main() {
printFormatted("The values are: %d, %d, %d", 10, 20, 30);
return 0;
}
在上面的例子中,我们定义了一个名为 printFormatted
的函数,它接受一个格式化字符串和可变参数列表。通过使用 std::va_list
和相关的宏和函数,我们可以在函数内部对可变参数进行处理和打印。
Tips
- 请注意,可变参数函数需要处理不同类型的参数时,需要根据具体的需求进行相应的类型检查和类型转换。
#define FMT_HEADER_ONLY
是一个宏定义,用于启用 fmt 库的头文件实现。通过定义此宏,可以将 fmt 库的函数和实现直接嵌入到编译单元中,而无需链接 fmt 库。spdlog的使用时需要注意
0717
- 李群视角下如何推导旋转矩阵的中值积分?
y n + 1 = y n + Δ t ⋅ k n k n = y ′ ( t + 1 2 Δ t ) R ′ ( t ) = R ( t ) w ∧ R ( t + 1 2 Δ t ) = R ( t ) e x p ( w ∧ 1 2 Δ t ) R ′ ( t + 1 2 Δ t ) = R ( t + 1 2 Δ t ) w ∧ = R ( t ) e x p ( w ∧ 1 2 Δ t ) ⋅ w ∧ R ( t + Δ t ) = R ( t ) + R ′ ( t + 1 2 Δ t ) ⋅ Δ t = R ( t ) + R ( t + 1 2 Δ t ) w ∧ Δ t = R ( t ) + R ( t ) e x p ( w ∧ 1 2 Δ t ) ⋅ w ∧ Δ t \begin{align} y_{n+1} & = y_n + \Delta{t} \cdot k_n \\ k_n & = y^{'}(t+\frac{1}{2}\Delta{t}) \\ R^{'}(t) & = R(t)w^{\wedge} \\ R(t+\frac{1}{2}\Delta{t}) & = R(t)exp(w^{\wedge}\frac{1}{2}\Delta{t}) \\ R^{'}(t+\frac{1}{2}\Delta{t}) & = R(t+\frac{1}{2}\Delta{t})w^{\wedge} \\ &= R(t)exp(w^{\wedge}\frac{1}{2}\Delta{t}) \cdot w^{\wedge}\\ R(t+\Delta{t}) &= R(t) + R^{'}(t+\frac{1}{2}\Delta{t}) \cdot \Delta{t} \\ &=R(t) + R(t+\frac{1}{2}\Delta{t})w^{\wedge} \Delta{t} \\ &=R(t) + R(t)exp(w^{\wedge}\frac{1}{2}\Delta{t}) \cdot w^{\wedge}\Delta{t} \\ \end{align} yn+1knR′(t)R(t+21Δt)R′(t+21Δt)R(t+Δt)=yn+Δt⋅kn=y′(t+21Δt)=R(t)w∧=R(t)exp(w∧21Δt)=R(t+21Δt)w∧=R(t)exp(w∧21Δt)⋅w∧=R(t)+R′(t+21Δt)⋅Δt=R(t)+R(t+21Δt)w∧Δt=R(t)+R(t)exp(w∧21Δt)⋅w∧Δt
最终结果
R
(
t
+
Δ
t
)
=
R
(
t
)
⋅
E
x
p
(
w
t
+
w
t
+
Δ
t
2
⋅
Δ
t
)
\begin{align} R(t+\Delta{t}) &= R(t) \cdot Exp(\frac{w_{t}+w_{t+\Delta{t}}}{2}\cdot \Delta{t}) \end{align}
R(t+Δt)=R(t)⋅Exp(2wt+wt+Δt⋅Δt)
0719
- 在声明函数时有
override
可以不用再添加virual
;
0721
- 如果使用了
spdlog::basic_file_sink
作为日志记录器的输出目标,并且程序非正常结束(例如崩溃或被强制终止),则未刷新的日志可能不会被写入文件。这是因为默认情况下,spdlog::basic_file_sink
在析构函数中进行日志文件的刷新操作。
如果希望在程序非正常结束时也能保证将未刷新的日志写入文件,可以使用spdlog::flush_on()
函数来设置日志记录器的刷新策略。例如,可以使用以下代码:
spdlog::flush_on(spdlog::level::err); // 设置在错误级别日志之前刷新
这样,在程序遇到错误级别的日志消息之前,就会强制执行刷新操作,确保日志被写入文件。
另外,请确保你在程序的适当位置调用了 spdlog::shutdown()
函数,以确保所有日志都被正确地刷新和关闭。
spdlog::shutdown(); // 在程序结束前调用,确保所有日志刷新和关闭
通过这些措施,可以尽量保证在程序非正常结束时也能将未刷新的日志写入文件。
0724
- aligned_allocator的试用:
STL容器中试用eigen变量,不用内存分配器就会报段错误。
0725
- EIGEN_MAKE_ALIGNED_OPERATOR_NEW的使用:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
是Eigen库中的一个宏,用于在自定义的Eigen类中重载operator new
和operator delete
,以确保内存对齐。
当使用该宏时,它会在类的声明中插入必要的代码,允许该类使用Eigen所需的内存对齐方式进行动态内存分配。这对于需要使用Eigen的动态大小矩阵或向量的自定义类非常有用。
请注意,在使用EIGEN_MAKE_ALIGNED_OPERATOR_NEW
之前,你需要包含Eigen库的Core.h
,并确保已经正确设置了编译环境以支持对齐操作。
0802
- 点云bin转换
#!/usr/bin/python3
import struct
import os
import sys
import numpy as np
from plyfile import PlyData, PlyElement
if len(sys.argv) < 2:
print('Usage:\n ' + 'python3 ' + os.path.basename(sys.argv[0]) + ' ' + '<point_cloud.bin>')
exit(1)
bytes = open(sys.argv[1], "rb").read()
txt_name = os.path.realpath(sys.argv[1]).replace('.bin', '.txt')
ply_name = os.path.realpath(sys.argv[1]).replace('.bin', '.ply')
point_size = 4 * 3 + 2
assert len(bytes) % point_size == 0
pt_count = len(bytes) / point_size
points = []
with open(txt_name, "w") as file:
for pi in range(int(pt_count)):
start = pi * point_size
end = (pi + 1) * point_size
pt_bytes = bytes[start:end]
data = struct.unpack("fffBb", pt_bytes)
# data = list(data)
# data[0] = data[0]+0.2
# data = tuple(data)
# print(data)
file.write(f"{data[0]},{data[1]},{data[2]},{data[3]}\n")
points.append([data[0], data[1],data[2], data[3]])
pc = list(map(tuple, points))
pc = np.array(pc, dtype=[('x', 'f4'),
('y', 'f4'),
('z', 'f4'),
('intensity', 'uint8')])
el = PlyElement.describe(pc, "vertex")
PlyData([el]).write(ply_name)
0809
- spdlog格式控制
-
spdlog::warn("Easy padding in numbers like {:08d}", 12);
这条日志记录使用了格式化字符串,将数字12填充为8位,不足的部分用0补齐。使用{}
来指定需要进行格式化的内容,其中:08d
表示将第一个参数(12)格式化为8位带有前导0的十进制数。 -
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
这条日志记录使用了多种格式化选项,将同一个参数(42)分别格式化为十进制、十六进制、八进制和二进制表示。{0:d}
表示将第一个参数(42)格式化为十进制,{0:x}
表示格式化为十六进制,{0:o}
表示格式化为八进制,{0:b}
表示格式化为二进制。 -
spdlog::info("Support for floats {:03.2f}", 1.23456);
这条日志记录将浮点数 1.23456 格式化为只保留两位小数,并将结果用总宽度为3的字段进行填充。 -
spdlog::info("Positional args are {1} {0}..", "too", "supported");
这条日志记录使用了位置参数,将 “too” 和 “supported” 分别填充到{1}
和{0}
的位置。 -
spdlog::info("{:<30}", "left aligned");
这条日志记录使用了对齐选项,将字符串 “left aligned” 左对齐,并使用30个字符的宽度进行填充
通过使用这些格式化选项,你可以更方便地在日志中输出各种不同类型和格式的数据,并控制输出的对齐、宽度、精度等。spdlog库提供了丰富的格式化选项让你在日志中输出信息更加灵活和可读。
-
0821
- 图像去畸变的计算过程中,float和double的精度对于最终的结果是有较大影响的,要确定一个类型来保证最终的效果;