Qiao

自建RustDesk服务

以往的工作以开发为主,几乎不关心怎么部署和运维。现在由于需要在部署的机器上更新软件、获取数据等等,经常遇到远程访问机器的需求。对于简单的任务,SSH就可以了;但还有一些情况,没有GUI实在不方便操作,远程指挥同事次数少还好,次数一多对两边都是折磨。 由于已经有跳板机可以访问远程设备,故考虑在跳板机上搭建一个内网的远程桌面服务,综合考察下来,RustDesk似乎是个不错的选择。 安装RustDesk Server OSS RustDesk的自托管服务分RustDesk Server OSS和RustDesk Server Pro。前者免费,但只提供基础的ID服务器、中级服务器实现;后者付费,提供设备管理、2FA、Web后台管理等功能。这里选择RustDesk Server OSS。 在跳板机上安装Docker Engine和Docker Compose,随后执行 mkdir rustdesk-server-oss && cd rustdesk-server-oss mkdir data touch compose.yml Docker Compose的compose.yml参考官方文档给出的示例: services: hbbs: container_name: hbbs image: rustdesk/rustdesk-server:latest command: hbbs volumes: - ./data:/root network_mode: "host" depends_on: - hbbr restart: unless-stopped hbbr: container_name: hbbr image: rustdesk/rustdesk-server:latest command: hbbr volumes: - ./data:/root network_mode: "host" restart: unless-stopped 执行docker compose up -d即可启动服务,随后通过docker compose logs查看日志: ...

2026-06-29 · Qiao

OpenCV鱼眼模型理解

鱼眼模型介绍 这里直接截取鱼眼镜头的成像原理到畸变矫正的原文作为此文的引子: 鱼眼镜头一般是由十几个不同的透镜组合而成的。在成像的过程中,入射光线经过不同程度的折射,投影到尺寸有限的成像平面上,使得鱼眼镜头与普通镜头相比起来拥有了更大的视野范围。 在研究鱼眼相机成像时,可以将上面的镜头组简化为一个球面: 图中,$O_1-X_cY_cZ_c$是相机坐标系,$O_2-xy$是成像平面。世界中有一点$P$,入射角为$\theta$。如果按照针孔相机模型,入射光线$PO_1$经过镜头后不改变路线,$p’$为$P$的成像点。但对于鱼眼相机,入射光线经过镜头后会发生折射,因此$p$才是$P$的成像点,极坐标表示为$(r, \varphi)$ 。 可以用投影函数来对光线的折射建模。根据投影函数的不同,鱼眼相机的传统模型大致能被分为五种:透视投影(即针孔相机模型)、等积投影、等距投影、体视投影、正交投影。 投影模型 投影函数 特征 i. 透视投影 (perspective projection) $r = ftan\theta$ 针孔相机模型 ii. 体视投影 (stereographic projection) $r = 2ftan\frac \theta 2$​ 任何直线相交的角度,在变换后保持不变 iii. 等距投影 (equidistance projection) $r = f\theta$ 物体成像面上距离画面中心的距离与入射角成正比 iv. 等积投影 (equisolid angle projection) $r = 2fsin\frac \theta 2$​ 在变换前后,物体所占的立体角大小不变 v. 正交投影 (orthogonal projection) $r = fsin\theta$ 投影畸变最大,而且最大视场角不能大于180° OpenCV所用模型 先看OpenCV文档的原文: Let $P$ be a point in 3D of coordinates X in the world reference frame (stored in the matrix X) The coordinate vector of $P$ in the camera reference frame is: ...

2023-05-10 · Qiao

AirSim中针孔相机畸变的实现

查阅AirSim的文档可知,AirSim支持通过simSetDistortionParams设置K1, K2, K3, P1, P2这5个畸变系数。UE镜头畸变模拟:OpenCV Lens Distortion一文提及过,Unreal的OpenCV Lens Distortion插件是通过生成的Post Process Material对UV坐标施加偏移来模拟镜头畸变。那么,AirSim又是如何处理的呢? 基本流程探究 打开任意集成了AirSim插件的Unreal工程,用Visual Studio翻阅工程源码,重点是AirSim插件的cpp代码。 在整个项目中搜索simSetDistortionParams,找到RPC客户端和服务端的实现。客户端: // <path-to-project>\Plugins\AirSim\Source\AirLib\src\api\RpcLibClientBase.cpp void RpcLibClientBase::simSetDistortionParam(const std::string& camera_name, const std::string& param_name, float value, const std::string& vehicle_name, bool external) { pimpl_->client.call("simSetDistortionParam", camera_name, param_name, value, vehicle_name, external); } 服务端: // <path-to-project>\Plugins\AirSim\Source\AirLib\src\api\RpcLibServerBase.cpp // 省略其余binding... pimpl_->server.bind("simSetDistortionParam", [&](const std::string& camera_name, const std::string& param_name, float value, const std::string& vehicle_name, bool external) -> void { getWorldSimApi()->setDistortionParam(param_name, value, CameraDetails(camera_name, vehicle_name, external)); }); // ... 顺藤摸瓜,找到WorldSimApi::setDistortionParam: void WorldSimApi::setDistortionParam(const std::string& param_name, float value, const CameraDetails& camera_details) { APIPCamera* camera = simmode_->getCamera(camera_details); UAirBlueprintLib::RunCommandOnGameThread([camera, &param_name, &value]() { camera->setDistortionParam(param_name, value); }, true); } 再看APIPCamera::setDistortionParam: void APIPCamera::setDistortionParam(const std::string& param_name, float value) { distortion_param_instance_->SetScalarParameterValue(FName(param_name.c_str()), value); } 查看APIPCamera类的声明,重点关注带“distortion”字样的成员: // <path-to-project>\Plugins\AirSim\Source\PIPCamera.h UCLASS() class AIRSIM_API APIPCamera : public ACineCameraActor //CinemAirSim { // 省略了其余代码... private: UPROPERTY() TArray<USceneCaptureComponent2D*> captures_; // ... UPROPERTY() UCineCameraComponent* camera_; // ... UPROPERTY() TArray<UMaterialInstanceDynamic*> distortion_materials_; UPROPERTY() UMaterial* distortion_material_static_; // ... }; 可以猜测AirSim也是通过设置Post Process Material来模拟的畸变。查找distortion_materials_和distortion_material_static_的引用,找到: ...

2023-05-04 · Qiao

UE镜头畸变模拟:OpenCV Lens Distortion

出于仿真的需要,希望在Unreal中能模拟鱼眼镜头的畸变效果,看到UE官方提供了Lens Distortion和OpenCV Lens Distortion两款插件用于模拟镜头畸变,但可惜相关资料不多。UE的博文UE中的相机标定、畸变模拟与矫正 - Unreal Engine做了些介绍,但步骤却又不是很详尽,初学者如我使用时不免多绕了些路,遂写下此文。 插件介绍 这里结合UE中的相机标定、畸变模拟与矫正 - Unreal Engine的介绍,总结如下: Lens Distortion和OpenCV Lens Distortion用的camera model是一样的,背后的原理也类似。都是根据相机畸变参数生成displacement map,以此模拟镜头畸变 Lens Distortion没有标定功能,且只包含了畸变系数的前几项 Lens Distortion通过shader生成displacement map,而OpenCV Lens Distortion通过OpenCV的API用CPU计算生成,前者效率更快。不过由于displacement map通常只生成一次,这里的效率差异不太重要 补充:什么是displacement map? A displacement map is a type of texture that is used in 3D computer graphics to create the illusion of depth and detail on a surface without actually changing the geometry of the mesh. A displacement map is typically a grayscale image where the brightness values of each pixel correspond to the amount of displacement that should be applied to the surface at that point. ...

2023-04-28 · Qiao

PySide6 实践

信号槽问题 Python中的任意函数都可以作为槽函数使用而无需使用@Slot装饰器,只要它符合与信号连接的规则(参数对应上),使用@Slot显示地声明可以提高效率,而不需要Python去隐式地转换类型。 python - Is the PySide Slot Decorator Necessary? - Stack Overflow 另一篇文章Should I decorate slots in Pyside2 and if so how?的回答: As to whether you should decorate the slots, it’s a little trickier to answer – but generally speaking no, you don’t need to. The only place I know the slot decorator is needed is when a) using threads, as it ensures the decorated method is started in the correct thread, or b) when you want to explicitly map a given slot to a specific call signature (types) in C++. ...

2023-04-25 · Qiao

从Hexo到Hugo

从Hexo到Hugo 背景 促使我从WordPress搬迁到Hexo,再决定从Hexo搬迁到Hugo的动机是:想尽可能简化写博客的流程,减少除文章撰写以外一切无关事务的精力消耗。 这一追求源于我对自身的观察。一天的精力里往往绝大部分都投入在工作之上,在闲暇中再挤出时间投入写作对意志力是个考验。以往使用WordPress时,一旦VPS的访问速度不佳,那登录后台、打开编辑器、调整样式过程中等待耗费的时间,就足以将不多的意志力消磨干净。 后续切到Hexo+Github Pages后,不得不说,这让博客的发布顺畅不少,不再需要登录后台,几乎不需要考虑排版,迁移站点时也不再有数据库的顾虑。唯一的麻烦是,每次修改文件后,需要调用Hexo CLI重新生成站点并推送到Github Pages仓库。这也意味着本地总得准备一份Node.js环境。思考一番后,前面通过使用Github Actions部署Hexo把生成和部署也自动化了,只需要写文章并推送即可。 这么愉快地用了些时日,但在和Obsidian的配合使用中,又发现了新的矛盾: Hexo如果需要用相对路径引用图片,图片应置于文章同名的文件夹下。而在Obsidian中,我所采取的方式是图片集中放置于media目录下 笔记现在总是用Obsidian创建,但需要为Hexo复制一份需要发布的文章及资源,而重复总是不利于维护的 如果将Hexo仓库置于Obsidian Vault中,再将需要发布的文章直接放到Hexo仓库的posts目录下,虽然不用复制文章,但Obsidian Vault中会带入Hexo/Node.js相关的文件。这些文件和笔记无关,在整理笔记时无异于噪音 这些问题都可以通过修改笔记来迁就Hexo,但这就是前文所说的,文章撰写以外的事务。我现在相信:应当让博客工具迁就写作习惯,而不是调整写作习惯来适应工具。 方法 为解决以上矛盾,想到的方法是: 在Vault中新建一个文件夹(这里记作Publish),在其中初始化Git仓库,存放供发布的文章 新建一个静态站点文件夹,同样初始话Git仓库,Publish仓库作为Git子模块加入其中 静态站点生成器应支持相对路径,且对文件夹的名称无要求 这样Obsidian Vault中只需要存放笔记,至于静态站点生成相关的内容则不再其中。为了方便博客发布的流程,还应该做到: 推送静态站点仓库时,应自动生成和部署站点,免去本地生成站点的麻烦 在Publish仓库中推送时,应自动更新静态站点仓库,让子模块引用Publish仓库的最新提交,免去需要在两个仓库中提交和推送的麻烦 做到这些后,应当可以: 不需要为了发布而改变记录笔记的方式 只维护一份需发布的资源 只需推送文章到远端,站点即能自动更新,本地不需要配置环境或手动生成站点 本想看看有没有办法让Hexo支持任意文件夹名的相对路径,但惊讶地发现并没有轻松的方式实现(我原以为这是非常常见的需求)。Gatsby/Jekyll/Hugo等一众工具也不原生支持此需求,这似乎与文件名到URL的映射有关。不过 GitHub - zoni/obsidian-export: Rust library and CLI to export an Obsidian vault to regular Markdown 提供了一个解决方式,遂决定切换到Hugo。 实施 支持相对路径 在Obsidian Vault中新建一个文件夹(这里记为Publish),在其中初始化Git仓库,存放供发布的文章。在Github上创建远端仓库并推送 用hugo new site命令创建站点文件夹(这里记为Site),同样初始化Git仓库。在Github上创建xxx.github.io名称的仓库并推送 与Hexo一样,Hugo同样支持主题,这里选用了PaperMod | Hugo Themes 在主题的layouts/_default/_markup/render-image.html中(若无该文件则新建),加入以下代码片段以支持相对路径引用图片: {{- $url := urls.Parse .Destination -}} {{- $scheme := $url.Scheme -}} <a href=" {{- if eq $scheme "" -}} {{- if strings.HasSuffix $url.Path ".md" -}} {{- relref .Page .Destination | safeURL -}} {{- else -}} {{- .Destination | safeURL -}} {{- end -}} {{- else -}} {{- .Destination | safeURL -}} {{- end -}}" {{- with .Title }} title="{{ . | safeHTML }}"{{- end -}}> {{- .Text | safeHTML -}} </a> {{- /* whitespace stripped here to avoid trailing newline in rendered result caused by file EOL */ -}} 在主题的layouts/_default/_markup/render-link.html中,加入以下片段以支持相对路径引用笔记: {{- $url := urls.Parse .Destination -}} {{- $scheme := $url.Scheme -}} <a href=" {{- if eq $scheme "" -}} {{- if strings.HasSuffix $url.Path ".md" -}} {{- relref .Page .Destination | safeURL -}} {{- else -}} {{- .Destination | safeURL -}} {{- end -}} {{- else -}} {{- .Destination | safeURL -}} {{- end -}}" {{- with .Title }} title="{{ . | safeHTML }}"{{- end -}}> {{- .Text | safeHTML -}} </a> {{- /* whitespace stripped here to avoid trailing newline in rendered result caused by file EOL */ -}} 运行hugo server -D,在本地搭建站点服务器,发现站点图片显示正常 CI/CD配置 现在考虑站点生成和部署的CI流程。 ...

2023-04-06 · Qiao

Debug Unity Project on Android Device

由于工作需要,在C++/Python的主业外,零零散散地与Unity打了些交道。这里记录下在安卓上调试Unity项目的要点。 修改Build Settings 点击File->Build Settings,在Android build setting里勾选“Development Build”和“Script Debugging”。 开启USB调试 Android设备需开启USB调试选项。通常是在系统设置里查看系统信息,多次点击系统版本以启用开发者选项,再到开发者选项里启用USB调试即可。这一步是安卓开发的基础,略过不谈。 连接设备到PC 有线连接 有线连接即通过USB线将设备连接到PC即可。可以通过adb devices命令确定可用的设备。 无线连接 无线连接需要PC和Android设备连接到同一WIFI。 查看设备IP 可以在设备的系统设置里找到IP,通常是在网络的详情里。也可以用ADB查看,终端执行 adb shell ip addr show wlan0 连接到设备 PC终端执行 adb tcpip 5555 adb connect [AndroidDeviceIP]:5555 Attach Unity Debugger 在Android上运行Unity应用后,在Visual Studio中点击“Debug”->“Attach Unity Debugger”: 随后选择对应的Android设备即可 设置Android SDK路径 如果系统中有多个Android SDK版本,在Select Unity Instance中可能不会看到对应的设备,这是因为Visual Studio使用的SDK工具没有对应。 在Unity中,点击Edit->Preferences->External Tools,复制所用的Android SDK工具路径: 随后在Visual Studio中,点击Tool->Options->Tools for Unity->General,设置Preferred Android Sdk Root: ...

2023-03-24 · Qiao

mesh里的图着色问题

背景 在用trimesh处理模型时,遇到这样一个问题: 给定一个mesh和一张贴图,如何在mesh的每一个face(即每一个三角面片)上贴上此图? 问题 设置贴图关键是设置顶点的uv坐标,最开始的思路是,先设置贴图的4个角落: uv = np.array([ [0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], ]) 再建立一个(len(vertices), 2)的numpy数组,遍历时依次填入各角落对应的uv坐标。 这样尝试后,发现mesh的贴图会出现很多奇怪的纹路——这是uv坐标有问题的表现。那问题出现在哪里? 问题在于,mesh的face间会相邻,顶点可能在多个face中存在。因此遍历时,顶点的uv坐标可能覆盖,且同一个face的顶点可能用了相同的uv坐标。这样渲染时就可能是从texture中提取了点或线,而不是提取一个三角面,这些点/线在mesh的face上展开时,就会有异样的纹路。 思路 之前的实现显然少了约束,思考一番后,重新定义问题如下: 给出n个三角形,以及0、1、2、3共四个数,要求每个三角形的顶点都分配一个值,且三角形各个顶点的值不一致。在这些三角形有可能相邻的情况下,如何实现一个分配算法? 拿纸演算了下,发现问题并不太简单。问题的描述又有些冗长,搜索后也没找到思路的火花。抱着试一试的想法,问了下chatGPT,其回答是: 这是一个图着色问题,可以使用图论中的染色算法来解决。 首先,将每个三角形看作一个节点,如果两个三角形有共同的边,则它们之间连一条边。这样得到的图称为三角形图。 对于这个三角形图,可以使用贪心算法来进行染色。具体步骤如下: 将三角形图中的所有节点按照度数从大到小排序。 依次遍历每个节点,将其周围已经染色的节点的颜色从可选颜色列表中删除。 如果所有可选颜色都已经被用过,为当前节点分配一个新的颜色。 继续处理下一个节点,直到所有节点都被染色。 对于本题中的0、1、2、3四个数,可以将它们看作四个颜色,每个三角形的顶点对应一个节点,然后按照上述方法进行染色即可。 需要注意的是,这种染色算法并不能保证一定能够找到一种可行的染色方案。当图中存在奇环(即环的长度为奇数)时,无论使用何种染色算法都无法找到可行的染色方案。因此,在实际应用中,需要对算法进行改进或者针对特定的问题进行分析,以保证能够得到正确的结果。 本来已经做好了chatGPT又会胡诌一番,煞有其事地编出些“算法”。但看到“图着色”的一刻,记忆又涌了上来——我知道它是对的。我怎么会没想到图着色呢?(好吧,图论在工作里遇到的实在太少了) 再复习下图着色: 简述图着色算法的原理与使用 - 掘金 Vertex Coloring – from Wolfram MathWorld 实现 现在首要问题变成了如何生成vertex的图。查看了下trimesh,颇为惊喜地发现trimesh就带了graph模块,并提供了trimesh.graph.vertex_adjacency_graph 函数,其返回一个networkx.Graph结构。 再看networkx, 和其名称一样,是一个专门做图分析的包。那么应当有对经典着色问题的实现?果真有,Coloring — NetworkX 3.0 documentation 剩下的实现变得颇为简单: mesh = trimesh.load('test.obj') texture = cv2.imread('test.png') # 省略其它处理 graph = trimesh.graph.vertex_adjacency_graph(mesh) coloring = nx.coloring.greedy_color(graph, interchange=True) num_colors = max(coloring.values()) + 1 uv = np.array([ [0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.5], [0.5, 0.0], [0.5, 0.5] ]) # 如所需color的数量大于当前`uv`元素数量,则插入随机坐标以补足 if uv.shape[0] < num_colors: diff = num_colors - uv.shape[0] u = np.random.uniform(low=0.0, high=1.0, size=diff) v = np.random.uniform(low=0.0, high=1.0, size=diff) extra = np.column_stack((u, v)) uv = np.concatenate((uv, extra), axis=0) # 设置顶点的uv坐标(color视为索引,再从`uv`中提取元素作为坐标) num_vertices = mesh.vertices.shape[0] uv_coords = np.zeros((num_vertices, 2)) for i in range(num_vertices): assert i in coloring color = coloring[i] uv_coords[i] = uv[color] # 设置mesh的uv坐标和贴图 mesh.visual = trimesh.visual.texture.TextureVisuals(uv=uv_coords, image=texture)

2023-03-16 · Qiao

Ubuntu 18.04上切换高版本GCC工具链

Ubuntu 18.04上切换高版本GCC工具链 添加软件源 $ sudo add-apt-repository ppa:ubuntu-toolchain-r/test 如果此前安装过非系统默认版本的python3,这一步有可能出错,产生类似 $ ModuleNotFoundError: No module named 'apt_pkg' 的错误。解决方法是: $ cd /usr/lib/python3/dist-packages $ sudo ln -s apt_pkg.cpython-36m-x86_64-linux-gnu.so apt_pkg.so 随后用update-alternatives将python3改回使用默认的版本: $ sudo update-alternatives --config python3 # 选择默认的python3版本。在Ubuntu 18.04上,这个版本是 安装工具链 可以通过apt search gcc或在https://launchpad.net/~ubuntu-toolchain-r/+archive/ubuntu/test?field.series_filter=bionic 上查看可用的gcc版本。这里选择安装gcc-11 $ sudo apt install gcc-11 g++-11 安装完成后,切换工具链 $ sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 90 \ --slave /usr/bin/g++ g++ /usr/bin/g++-11 \ --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-11 \ --slave /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-11 \ --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-11 $ sudo upate-alternatives --config gcc # 选择gcc-11 检查版本: ...

2023-02-19 · Qiao

为VTK修复vtkOBJReader的一个segfault

Background 测试用PyTorch3D生成的Mesh时,打算用VTK对Mesh做离屏渲染生成图片,结果发现VTK的python binding和C++库都会在vtkOBJReader::Update时崩溃。由于是AI模型生成的OBJ文件,OBJ本身是有可能不太标准的,但用Blender、Open3D、trimesh测试,发现都可以加载该文件。看起来似乎VTK的vtkOBJReader实现不够健壮,遂决定调试一番。 Debug 以导致问题的OBJ文件编写复现demo,目录结构: $ tree . . ├── assets │ ├── rand_0_diffuse.png │ ├── rand_0_normal.png │ ├── rand_0_skin.mtl │ ├── rand_0_skin.obj │ └── rand_0_spec.png ├── CMakeLists.txt └── main.cpp main.cpp: #include <vtkOBJReader.h> int main() { vtkNew<vtkOBJReader> reader; reader->SetFileName("rand_0_skin.obj"); reader->Update(); return 0; } 崩溃堆栈: 1 vtkAOSDataArrayTemplate<float>::GetTuple vtkAOSDataArrayTemplate.txx 275 0x7ffff6ee05e5 2 vtkOBJReader::RequestData vtkOBJReader.cxx 978 0x7ffff7b4c793 3 vtkPolyDataAlgorithm::ProcessRequest vtkPolyDataAlgorithm.cxx 87 0x7ffff28aaec6 4 vtkExecutive::CallAlgorithm vtkExecutive.cxx 734 0x7ffff287faf9 5 vtkDemandDrivenPipeline::ExecuteData vtkDemandDrivenPipeline.cxx 461 0x7ffff2876004 6 vtkCompositeDataPipeline::ExecuteData vtkCompositeDataPipeline.cxx 162 0x7ffff2869321 7 vtkDemandDrivenPipeline::ProcessRequest vtkDemandDrivenPipeline.cxx 260 0x7ffff28755b1 8 vtkStreamingDemandDrivenPipeline::ProcessRequest vtkStreamingDemandDrivenPipeline.cxx 343 0x7ffff2943429 9 vtkDemandDrivenPipeline::UpdateData vtkDemandDrivenPipeline.cxx 418 0x7ffff2875de9 10 vtkStreamingDemandDrivenPipeline::Update vtkStreamingDemandDrivenPipeline.cxx 417 0x7ffff29437c4 11 vtkStreamingDemandDrivenPipeline::Update vtkStreamingDemandDrivenPipeline.cxx 380 0x7ffff294364d 12 vtkAlgorithm::Update vtkAlgorithm.cxx 1406 0x7ffff285f3b4 13 vtkAlgorithm::Update vtkAlgorithm.cxx 1400 0x7ffff285f37f 14 main main.cpp 7 0x555555555297 调试后,发现崩溃的直接原因是vtkAOSDataArrayTemplate<float>的Buffer为null,data实际是野指针: ...

2023-01-18 · Qiao