从 python 中的函数返回后,指向浮点数的 C 类型指针发生变化
C-type pointer to floats changes after returning from function in python
我目前正在开发一个 Darknet/YOLO 项目,该项目使用 python 中的 opencv 从实时流接收的图像中检测对象。为了检测物体,opencv 图像,它只是一个形状为 (height, width, color_channels)
的 numpy 数组,必须转换为 Darknet(用 c 编写)可以读取的格式(在 Darknet 中定义的图像 class *float 类型的数据属性)。为此,我在 python 中编写了以下代码:
h, w, c = input_frame.shape
# create a flattened image and normalize by devision by 255.
# NOTE transpose(2, 0, 1) permutes the axes from 0,1,2 to 2,0,1 (clockwise cycle)
flattened_image = input_frame.transpose(2, 0, 1).flatten().astype(np.float32)/255.
# create a C type pointer
c_float_p = ctypes.POINTER(ctypes.c_float) # define LP_c_float type
c_float_p_frame = flattened_image.ctypes.data_as(c_float_p) # cast to LP_c_float
# create empty C_IMAGE type object and then set data to c_float_p_frame
C_IMAGE_frame = dn.make_image(w, h, c)
C_IMAGE_frame.data = c_float_p_frame
(注意dn
是darknet接口,上面某处导入了,但这不是问题所以不是很重要)
然后将 C_IMAGE_frame 对象传递到网络。 重要提示:此代码有效。然而,这里是踢球者,如果我将完全相同的代码打包到一个函数中,我会在图像传递到 Darknet 后出现访问冲突错误(即段错误)。我最初在测试脚本中内联编写了这段代码并且一切正常,所以当我开始清理我的代码时,我将上面的代码块打包到以下函数中:
def np_image_to_c_IMAGE(input_frame):
"""
parameters
==========
input_frame: ndarray (opencv image)
returns
==========
C_IMAGE_frame: C IMAGE object (implimented in darknet)
converts a numpy image (w x h x c dim ndarray) to a C type IMAGE
defined in darknet. Returns a pointer.
"""
h, w, c = input_frame.shape
# create a flattened image and normalize by devision by 255.
# NOTE transpose(2, 0, 1) permutes the axes from 0,1,2 to 2,0,1 (clockwise cycle)
flattened_image = input_frame.transpose(2, 0, 1).flatten().astype(np.float32)/255.
# create a C type pointer
c_float_p = ctypes.POINTER(ctypes.c_float) # define LP_c_float type
c_float_p_frame = flattened_image.ctypes.data_as(c_float_p) # cast to LP_c_float
# create empty C_IMAGE type object and then set data to c_float_p_frame
C_IMAGE_frame = dn.make_image(w, h, c)
C_IMAGE_frame.data = c_float_p_frame
return C_IMAGE_frame
最初我很困惑为什么我的代码会产生段错误,但我 运行 进行了一些调试测试并发现了以下问题:访问 C_IMAGE_frame.data[0]
时(即只读出第一个值)在函数中,我得到了一个浮点数,就像人们期望的那样,但是如果我在 returning C_IMAGE_frame 之后像这样做同样的事情:
#opencv get image and other code...
C_IMAGE = np_image_to_C_IMAGE(opencv_image)
print(C_IMAGE.data[0])
python 引发段错误。我检查了是否所有指针都正确地“returned”,我看到发生了一些指针重新分配魔法。
def np_image_to_C_IMAGE(input_frame):
# rest of function...
print(C_IMAGE_frame) # output: <lib.darknet.IMAGE object at 0x0000021F24F6EDC0>
print(C_IMAGE_frame.data) # output: <lib.darknet.LP_c_float object at 0x0000021F24F6EBC0>
print(C_IMAGE_frame.data[0]) # output: 0.0
return C_IMAGE_frame
# after C_IMAGE is returned in script
C_IMAGE = np_image_to_C_IMAGE(opencv_image)
print(C_IMAGE) # output: <lib.darknet.IMAGE object at 0x0000021F24F6EDC0>
print(C_IMAGE.data) # output: <lib.darknet.LP_c_float object at 0x0000021F24F6BAC0>
print(C_IMAGE.data[0] # raises Segmentation fault
注意data
指针0x0000021F24F6EBC0
变为0x0000021F24F6BAC0
所以当然会出现段错误,但为什么会这样呢?我怎样才能避免这种情况?这只是一些内部 python 诡计还是其他原因?我的意思是,如果我 return python 中的某些东西,我 期望 它是我传递给 return
的确切对象,但也许 [=53] =] ctypes 破坏了某些东西或有一些需要解决方法的有趣实现?
现在我将代码内联粘贴到我的分析脚本中,所以我的脚本又能工作了,但我很想知道为什么会首先发生这种情况以及如何解决它。
编辑 我添加了一个最小可重现的例子:
from ctypes import *
import numpy as np
class IMAGE(Structure):
_fields_ = [("w", c_int),
("h", c_int),
("c", c_int),
("data", POINTER(c_float))]
img = np.zeros((1080, 1920, 3)) # h, w, c array = opencv image analogon
def np_image_to_c_IMAGE(input_frame):
h, w, c = input_frame.shape
flattened_image = input_frame.transpose(2, 0, 1).flatten().astype(np.float32)/255.
c_float_p = POINTER(c_float) # define LP_c_float type
c_float_p_frame = flattened_image.ctypes.data_as(c_float_p) # cast to LP_c_float
C_IMAGE_frame = IMAGE(w, h, c, c_float_p_frame)
print(C_IMAGE_frame)
print(C_IMAGE_frame.data)
return C_IMAGE_frame
C_IMAGE = np_image_to_c_IMAGE(img)
print(C_IMAGE)
print(C_IMAGE.data)
输出:
# within function
<__main__.IMAGE object at 0x7fc7f618ff40>
<__main__.LP_c_float object at 0x7fc7f49b1040>
# after return
<__main__.IMAGE object at 0x7fc7f618ff40>
<__main__.LP_c_float object at 0x7fc800777f40>
在 IMAGE
中存储数据指针不会保留对图像数据的引用。一旦 flattened_image
和 c_float_p_frame
超出范围,数据就会被释放。在图像中存储额外的引用以防止数据被释放:
from ctypes import *
import numpy as np
class IMAGE(Structure):
_fields_ = [("w", c_int),
("h", c_int),
("c", c_int),
("data", POINTER(c_float))]
img = np.zeros((1080, 1920, 3))
def np_image_to_c_IMAGE(input_frame):
h, w, c = input_frame.shape
flattened_image = input_frame.transpose(2, 0, 1).flatten().astype(np.float32)/255.
c_float_p = POINTER(c_float)
c_float_p_frame = flattened_image.ctypes.data_as(c_float_p)
C_IMAGE_frame = IMAGE(w,h,c,c_float_p_frame)
C_IMAGE_frame.ref = c_float_p_frame # extra reference to data stored
print(C_IMAGE_frame)
print(C_IMAGE_frame.data)
print(cast(C_IMAGE_frame.data,c_void_p)) # the pointer value
print(C_IMAGE_frame.data.contents) # data valid
return C_IMAGE_frame
C_IMAGE = np_image_to_c_IMAGE(img)
print(C_IMAGE)
print(C_IMAGE.data)
print(cast(C_IMAGE.data,c_void_p)) # pointer is the same, but contents freed if no ref.
print(C_IMAGE.data.contents) # crashes here if extra reference not kept.
输出(注意存储的实际指针值是相同的,但如果注释掉 C_IMAGE_frame.ref
行,最终打印将崩溃):
<__main__.IMAGE object at 0x000001A8B8B5CBC0>
<__main__.LP_c_float object ddat 0x000001A8B8B5CC40>
c_void_p(1824215363648)
c_float(0.0)
<__main__.IMAGE object at 0x000001A8B8B5CBC0>
<__main__.LP_c_float object at 0x000001A8B8B5CC40>
c_void_p(1824215363648)
c_float(0.0)
不是很优雅,我不确定为什么将 c_float_p_frame
存储在 IMAGE.data
中不足以保留引用,但将其存储在 IMAGE.ref
中则无需深入研究ctypes
.
的勇气
我目前正在开发一个 Darknet/YOLO 项目,该项目使用 python 中的 opencv 从实时流接收的图像中检测对象。为了检测物体,opencv 图像,它只是一个形状为 (height, width, color_channels)
的 numpy 数组,必须转换为 Darknet(用 c 编写)可以读取的格式(在 Darknet 中定义的图像 class *float 类型的数据属性)。为此,我在 python 中编写了以下代码:
h, w, c = input_frame.shape
# create a flattened image and normalize by devision by 255.
# NOTE transpose(2, 0, 1) permutes the axes from 0,1,2 to 2,0,1 (clockwise cycle)
flattened_image = input_frame.transpose(2, 0, 1).flatten().astype(np.float32)/255.
# create a C type pointer
c_float_p = ctypes.POINTER(ctypes.c_float) # define LP_c_float type
c_float_p_frame = flattened_image.ctypes.data_as(c_float_p) # cast to LP_c_float
# create empty C_IMAGE type object and then set data to c_float_p_frame
C_IMAGE_frame = dn.make_image(w, h, c)
C_IMAGE_frame.data = c_float_p_frame
(注意dn
是darknet接口,上面某处导入了,但这不是问题所以不是很重要)
然后将 C_IMAGE_frame 对象传递到网络。 重要提示:此代码有效。然而,这里是踢球者,如果我将完全相同的代码打包到一个函数中,我会在图像传递到 Darknet 后出现访问冲突错误(即段错误)。我最初在测试脚本中内联编写了这段代码并且一切正常,所以当我开始清理我的代码时,我将上面的代码块打包到以下函数中:
def np_image_to_c_IMAGE(input_frame):
"""
parameters
==========
input_frame: ndarray (opencv image)
returns
==========
C_IMAGE_frame: C IMAGE object (implimented in darknet)
converts a numpy image (w x h x c dim ndarray) to a C type IMAGE
defined in darknet. Returns a pointer.
"""
h, w, c = input_frame.shape
# create a flattened image and normalize by devision by 255.
# NOTE transpose(2, 0, 1) permutes the axes from 0,1,2 to 2,0,1 (clockwise cycle)
flattened_image = input_frame.transpose(2, 0, 1).flatten().astype(np.float32)/255.
# create a C type pointer
c_float_p = ctypes.POINTER(ctypes.c_float) # define LP_c_float type
c_float_p_frame = flattened_image.ctypes.data_as(c_float_p) # cast to LP_c_float
# create empty C_IMAGE type object and then set data to c_float_p_frame
C_IMAGE_frame = dn.make_image(w, h, c)
C_IMAGE_frame.data = c_float_p_frame
return C_IMAGE_frame
最初我很困惑为什么我的代码会产生段错误,但我 运行 进行了一些调试测试并发现了以下问题:访问 C_IMAGE_frame.data[0]
时(即只读出第一个值)在函数中,我得到了一个浮点数,就像人们期望的那样,但是如果我在 returning C_IMAGE_frame 之后像这样做同样的事情:
#opencv get image and other code...
C_IMAGE = np_image_to_C_IMAGE(opencv_image)
print(C_IMAGE.data[0])
python 引发段错误。我检查了是否所有指针都正确地“returned”,我看到发生了一些指针重新分配魔法。
def np_image_to_C_IMAGE(input_frame):
# rest of function...
print(C_IMAGE_frame) # output: <lib.darknet.IMAGE object at 0x0000021F24F6EDC0>
print(C_IMAGE_frame.data) # output: <lib.darknet.LP_c_float object at 0x0000021F24F6EBC0>
print(C_IMAGE_frame.data[0]) # output: 0.0
return C_IMAGE_frame
# after C_IMAGE is returned in script
C_IMAGE = np_image_to_C_IMAGE(opencv_image)
print(C_IMAGE) # output: <lib.darknet.IMAGE object at 0x0000021F24F6EDC0>
print(C_IMAGE.data) # output: <lib.darknet.LP_c_float object at 0x0000021F24F6BAC0>
print(C_IMAGE.data[0] # raises Segmentation fault
注意data
指针0x0000021F24F6EBC0
变为0x0000021F24F6BAC0
所以当然会出现段错误,但为什么会这样呢?我怎样才能避免这种情况?这只是一些内部 python 诡计还是其他原因?我的意思是,如果我 return python 中的某些东西,我 期望 它是我传递给 return
的确切对象,但也许 [=53] =] ctypes 破坏了某些东西或有一些需要解决方法的有趣实现?
现在我将代码内联粘贴到我的分析脚本中,所以我的脚本又能工作了,但我很想知道为什么会首先发生这种情况以及如何解决它。
编辑 我添加了一个最小可重现的例子:
from ctypes import *
import numpy as np
class IMAGE(Structure):
_fields_ = [("w", c_int),
("h", c_int),
("c", c_int),
("data", POINTER(c_float))]
img = np.zeros((1080, 1920, 3)) # h, w, c array = opencv image analogon
def np_image_to_c_IMAGE(input_frame):
h, w, c = input_frame.shape
flattened_image = input_frame.transpose(2, 0, 1).flatten().astype(np.float32)/255.
c_float_p = POINTER(c_float) # define LP_c_float type
c_float_p_frame = flattened_image.ctypes.data_as(c_float_p) # cast to LP_c_float
C_IMAGE_frame = IMAGE(w, h, c, c_float_p_frame)
print(C_IMAGE_frame)
print(C_IMAGE_frame.data)
return C_IMAGE_frame
C_IMAGE = np_image_to_c_IMAGE(img)
print(C_IMAGE)
print(C_IMAGE.data)
输出:
# within function
<__main__.IMAGE object at 0x7fc7f618ff40>
<__main__.LP_c_float object at 0x7fc7f49b1040>
# after return
<__main__.IMAGE object at 0x7fc7f618ff40>
<__main__.LP_c_float object at 0x7fc800777f40>
在 IMAGE
中存储数据指针不会保留对图像数据的引用。一旦 flattened_image
和 c_float_p_frame
超出范围,数据就会被释放。在图像中存储额外的引用以防止数据被释放:
from ctypes import *
import numpy as np
class IMAGE(Structure):
_fields_ = [("w", c_int),
("h", c_int),
("c", c_int),
("data", POINTER(c_float))]
img = np.zeros((1080, 1920, 3))
def np_image_to_c_IMAGE(input_frame):
h, w, c = input_frame.shape
flattened_image = input_frame.transpose(2, 0, 1).flatten().astype(np.float32)/255.
c_float_p = POINTER(c_float)
c_float_p_frame = flattened_image.ctypes.data_as(c_float_p)
C_IMAGE_frame = IMAGE(w,h,c,c_float_p_frame)
C_IMAGE_frame.ref = c_float_p_frame # extra reference to data stored
print(C_IMAGE_frame)
print(C_IMAGE_frame.data)
print(cast(C_IMAGE_frame.data,c_void_p)) # the pointer value
print(C_IMAGE_frame.data.contents) # data valid
return C_IMAGE_frame
C_IMAGE = np_image_to_c_IMAGE(img)
print(C_IMAGE)
print(C_IMAGE.data)
print(cast(C_IMAGE.data,c_void_p)) # pointer is the same, but contents freed if no ref.
print(C_IMAGE.data.contents) # crashes here if extra reference not kept.
输出(注意存储的实际指针值是相同的,但如果注释掉 C_IMAGE_frame.ref
行,最终打印将崩溃):
<__main__.IMAGE object at 0x000001A8B8B5CBC0>
<__main__.LP_c_float object ddat 0x000001A8B8B5CC40>
c_void_p(1824215363648)
c_float(0.0)
<__main__.IMAGE object at 0x000001A8B8B5CBC0>
<__main__.LP_c_float object at 0x000001A8B8B5CC40>
c_void_p(1824215363648)
c_float(0.0)
不是很优雅,我不确定为什么将 c_float_p_frame
存储在 IMAGE.data
中不足以保留引用,但将其存储在 IMAGE.ref
中则无需深入研究ctypes
.