Suppose I have a RGB image with 3 bytes per pixel stored in a raw memory buffer. In numpy
I am able to create a ndarray
from such raw buffer with the following code:
import ctypes
import numpy as np
# ...
shp = (image.height, image.width, 3)
ptr = ctypes.cast(image.ptr, ctypes.POINTER(ctypes.c_ubyte))
arr = np.ctypeslib.as_array(ptr, shape=shp)
Where image.ptr
is the actual native pointer to the allocated buffer. This appears to work well with a trivial stride/row size, but it's very common to find bitmap memory layouts where the size of a row may be bigger than actually required. For example a Windows GDI PixelFormat24bppRGB bitmap has a row size that is the closest multiple of 4 that can be computed with the formula 4 * (3 * width 3) / 4)
. How I can modify the above code to create a ndarray
that correctly access such custom bitmap memory layout?
CodePudding user response:
You can create a view on the raw buffer using np.lib.stride_tricks.as_strided
. Here is an example to skip the alpha channel of a RGBA-based (H,W,4)
image array:
# Create the example image raw array
arr = np.arange(16).astype(np.uint8).reshape(4,4)
print(arr)
print(arr.strides)
# Create a view so to skip some items (possibly to support padding)
arrView = np.lib.stride_tricks.as_strided(arr, shape=(4, 3), strides=(4, 1))
print(arrView)
print(arrView.strides)
The result is:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]
(4, 1)
[[ 0 1 2]
[ 4 5 6]
[ 8 9 10]
[12 13 14]]
(4, 1)
As the documentation states, this function has to be used with extreme care (be prepared to a crash if not used properly).
CodePudding user response:
@JeromeRichard answer is correct. Here it follows a snippet that simulates exactly the use case in my question, namely the 24bpp strided rgb bitmap:
import ctypes
import numpy as np
width = 50
height = 50
rowsize = 4 * int((3 * 50 3) / 4)
rgb_buff_size = rowsize * height
# Prepare a test byte array and access the native pointer
# https://stackoverflow.com/a/73640146/213871
test_rgb_buff = np.arange(rgb_buff_size).astype(np.uint8).reshape(1, rgb_buff_size)
buff_ptr = test_rgb_buff.ctypes.data
# Create the view from the pointer
rgb_buff_view = np.ctypeslib.as_array(ctypes.cast(buff_ptr, ctypes.POINTER(ctypes.c_ubyte)), shape=(1, rgb_buff_size))
# Create the 3 channel strided view
rgb_strided_view = np.lib.stride_tricks.as_strided(rgb_buff_view, shape=(width, height, 3), strides=(rowsize, 3, 1))
print("type: " str(rgb_strided_view.dtype))
print("shape: " str(rgb_strided_view.shape))
print("strides: " str(rgb_strided_view.strides))
This prints the following:
type: uint8
shape: (50, 50, 3)
strides: (152, 3, 1)