ブログ書くのめっちゃ久々!まぁ気が向いたときに書こうというブログなので、これで良いのだぁ

タイトルの通り、Pythonを使っててCみたいなハマり方をしました((+_+)) Pythonを使ってだいぶ経ちますが、このハマり方は初めてでした。

最近、ChatGPTにデバッグやコードの整理をしてもらうのですが、今回の問題はChatGPTさんも何が問題かわからなかったみたいです。

ハマったコード

ハマったコードは以下です。インターフェースに使っているコードは関係ないので省略します。何が問題かわかりますか?

コードは、Open3Dに実装されているKDTreeを使って、与えられた点(クエリ)の最近傍点をデータから探すコードです。KDTreeを自分で工夫してみたくて、自分の実装との速度比較用にOpen3Dのものを使って実装したものです。

一見すると問題なく、ChatGPTも問題を特定できず、実行してもエラー無く動きます。しかし、返ってくる結果が最近傍点を返したり、返さなかったりと、変な挙動をします。

このコードの何が問題かわかりますか?

# open3dの実装を使用したkd-treeの実装
from __future__ import annotations

import numpy as np
import open3d as o3d

from kdtree_lib.interface import KDTree


class KDTreeOpen3d(KDTree):
    """Open3DのKDTreeの実装"""

    def __init__(self, points: np.ndarray) -> None:
        """コンストラクタ"""
        if not isinstance(points, np.ndarray):
            raise TypeError("pointsはNumPy配列である必要があります。")
        self.points: np.ndarray = points
        self.tree : o3d.geometry.KDTreeFlann = self.build_tree(self.points)

    def build_tree(self, points: np.ndarray, depth: int = 0) -> o3d.geometry.KDTreeFlann:
        """KDTreeを構築する"""
        pcd = o3d.geometry.PointCloud()
        pcd.points = o3d.utility.Vector3dVector(points)
        return o3d.geometry.KDTreeFlann(pcd)

    def _nearest_neighbor_impl(self, point: np.ndarray | list | tuple, depth: int = 0) -> tuple[float, np.ndarray | None]:
        """最近傍点を探索する"""
        [k, idx, _] = self.tree.search_knn_vector_3d(point, 1)
        print(k, idx, _)
        nearest_point = self.points[idx[0]]
        dist = float(np.linalg.norm(np.array(point) - nearest_point))
        return dist, nearest_point

if __name__ == "__main__":
    # 3次元データ点の例
    points = np.array([(2, 3, 1), (5, 4, 2), (9, 6, 3), (4, 7, 5), (8, 1, 8), (7, 2, 9)])
    tree = KDTreeOpen3d(points)

    # クエリポイント(3次元)
    query_point = np.array((2.5, 3.5, 1.5))
    distance, nearest_point = tree.nearest_neighbor(query_point)
    print(f"Nearest to {query_point} is {nearest_point} with distance {distance}")

このコードの問題点

build_treeメソッド内の変数pcdが原因でした。

pcdはbuild_treeメソッド内のローカル変数なので、build_treeの実行が終わったら__del__によって開放されます。しかし、Open3DのKDTreeFlannで作ったKDTreeのデータの実態はpcdです。

そのため、build_treeの後で実行される近傍探索の実行時には、self.tree内のデータが保証されません。状況によってpcdのデータが記録されていたメモリ上の値が勝手にいじられて、うまくいったりいかなかったりします。

Cを触ってたときのような挙動をPythonで踏むのは初めてでビックリです( ;∀;) まぁ、pybindされてるC++のコードを使ってるわけだからそれはそうなんですが。

この問題の対策

変数pcdがbuild_tree内のローカル変数になってるから、もっと長く生存するようにすれば対策できます。そのため、クラスのインスタンス変数としてself.pcdにすると上手くいきます。

treeがpcdを参照してるってことをPython側に伝えるのってできないのかな。。。

以上です(*’ω’*)

Categories:

No responses yet

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA