You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
8.2 KiB
230 lines
8.2 KiB
"""Creates an image from a numpy array of floating point depth data.
|
|
|
|
For details about the encoding see:
|
|
https://sites.google.com/site/brainrobotdata/home/depth-image-encoding
|
|
|
|
Examples:
|
|
|
|
depth_array is a 2D numpy array of floating point depth data in meters.
|
|
|
|
depth_rgb = FloatArrayToRgbImage(depth_array)
|
|
depth_rgb is a PIL Image object containing the same data as 24-bit
|
|
integers encoded in the RGB bytes.
|
|
depth_rgb.save('image_file.png') - to save to a file.
|
|
|
|
depth_array2 = ImageToFloatArray(depth_rgb)
|
|
depth_array2 is a 2D numpy array containing the same data as
|
|
depth_array up to the precision of the RGB image (1/256 mm).
|
|
|
|
depth_gray = FloatArrayToGrayImage(depth_array)
|
|
depth_gray is a PIL Image object containing the same data rounded to
|
|
8-bit integers.
|
|
depth_gray.save('image_file.jpg', quality=95) - to save to a file.
|
|
|
|
depth_array3 = ImageToFloatArray(depth_gray)
|
|
depth_array3 is a 2D numpy array containing the same data as
|
|
depth_array up to the precision of the grayscale image (1 cm).
|
|
|
|
The image conversions first scale and round the values and then pack
|
|
them into the desired type in a numpy array before converting the
|
|
array to a PIL Image object. The Image can be saved in any format.
|
|
We are using PNG for RGB and high quality JPEG for grayscale images.
|
|
|
|
You can use different numeric types (e.g. np.uint16, np.int32), but
|
|
not all combinations of numeric type and image format are supported by
|
|
PIL or standard image viewers.
|
|
|
|
"""
|
|
|
|
import numpy as np
|
|
from PIL import Image
|
|
from skimage import img_as_ubyte
|
|
from skimage.color import grey2rgb
|
|
|
|
|
|
def ClipFloatValues(float_array, min_value, max_value):
|
|
"""Clips values to the range [min_value, max_value].
|
|
|
|
First checks if any values are out of range and prints a message.
|
|
Then clips all values to the given range.
|
|
|
|
Args:
|
|
float_array: 2D array of floating point values to be clipped.
|
|
min_value: Minimum value of clip range.
|
|
max_value: Maximum value of clip range.
|
|
|
|
Returns:
|
|
The clipped array.
|
|
|
|
"""
|
|
if float_array.min() < min_value or float_array.max() > max_value:
|
|
print('Image has out-of-range values [%f,%f] not in [%d,%d]',
|
|
float_array.min(), float_array.max(), min_value, max_value)
|
|
float_array = np.clip(float_array, min_value, max_value)
|
|
return float_array
|
|
|
|
|
|
DEFAULT_RGB_SCALE_FACTOR = 256000.0
|
|
|
|
|
|
def FloatArrayToRgbImage(float_array,
|
|
scale_factor=DEFAULT_RGB_SCALE_FACTOR,
|
|
drop_blue=False):
|
|
"""Convert a floating point array of values to an RGB image.
|
|
|
|
Convert floating point values to a fixed point representation where
|
|
the RGB bytes represent a 24-bit integer.
|
|
R is the high order byte.
|
|
B is the low order byte.
|
|
The precision of the depth image is 1/256 mm.
|
|
|
|
Floating point values are scaled so that the integer values cover
|
|
the representable range of depths.
|
|
|
|
This image representation should only use lossless compression.
|
|
|
|
Args:
|
|
float_array: Input array of floating point depth values in meters.
|
|
scale_factor: Scale value applied to all float values.
|
|
drop_blue: Zero out the blue channel to improve compression, results in 1mm
|
|
precision depth values.
|
|
|
|
Returns:
|
|
24-bit RGB PIL Image object representing depth values.
|
|
"""
|
|
float_array = np.squeeze(float_array)
|
|
# Scale the floating point array.
|
|
scaled_array = np.floor(float_array * scale_factor + 0.5)
|
|
# Convert the array to integer type and clip to representable range.
|
|
min_inttype = 0
|
|
max_inttype = 2**24 - 1
|
|
scaled_array = ClipFloatValues(scaled_array, min_inttype, max_inttype)
|
|
int_array = scaled_array.astype(np.uint32)
|
|
# Calculate:
|
|
# r = (f / 256) / 256 high byte
|
|
# g = (f / 256) % 256 middle byte
|
|
# b = f % 256 low byte
|
|
rg = np.divide(int_array, 256)
|
|
r = np.divide(rg, 256)
|
|
g = np.mod(rg, 256)
|
|
image_shape = int_array.shape
|
|
rgb_array = np.zeros((image_shape[0], image_shape[1], 3), dtype=np.uint8)
|
|
rgb_array[..., 0] = r
|
|
rgb_array[..., 1] = g
|
|
if not drop_blue:
|
|
# Calculate the blue channel and add it to the array.
|
|
b = np.mod(int_array, 256)
|
|
rgb_array[..., 2] = b
|
|
image_mode = 'RGB'
|
|
image = Image.fromarray(rgb_array, mode=image_mode)
|
|
return image
|
|
|
|
|
|
DEFAULT_GRAY_SCALE_FACTOR = {np.uint8: 100.0,
|
|
np.uint16: 1000.0,
|
|
np.int32: DEFAULT_RGB_SCALE_FACTOR}
|
|
|
|
|
|
def FloatArrayToGrayImage(float_array, scale_factor=None, image_dtype=np.uint8):
|
|
"""Convert a floating point array of values to an RGB image.
|
|
|
|
Convert floating point values to a fixed point representation with
|
|
the given bit depth.
|
|
|
|
The precision of the depth image with default scale_factor is:
|
|
uint8: 1cm, with a range of [0, 2.55m]
|
|
uint16: 1mm, with a range of [0, 65.5m]
|
|
int32: 1/256mm, with a range of [0, 8388m]
|
|
|
|
Right now, PIL turns uint16 images into a very strange format and
|
|
does not decode int32 images properly. Only uint8 works correctly.
|
|
|
|
Args:
|
|
float_array: Input array of floating point depth values in meters.
|
|
scale_factor: Scale value applied to all float values.
|
|
image_dtype: Image datatype, which controls the bit depth of the grayscale
|
|
image.
|
|
|
|
Returns:
|
|
Grayscale PIL Image object representing depth values.
|
|
|
|
"""
|
|
# Ensure that we have a valid numeric type for the image.
|
|
if image_dtype == np.uint16:
|
|
image_mode = 'I;16'
|
|
elif image_dtype == np.int32:
|
|
image_mode = 'I'
|
|
else:
|
|
image_dtype = np.uint8
|
|
image_mode = 'L'
|
|
if scale_factor is None:
|
|
scale_factor = DEFAULT_GRAY_SCALE_FACTOR[image_dtype]
|
|
# Scale the floating point array.
|
|
scaled_array = np.floor(float_array * scale_factor + 0.5)
|
|
# Convert the array to integer type and clip to representable range.
|
|
min_dtype = np.iinfo(image_dtype).min
|
|
max_dtype = np.iinfo(image_dtype).max
|
|
scaled_array = ClipFloatValues(scaled_array, min_dtype, max_dtype)
|
|
|
|
image_array = scaled_array.astype(image_dtype)
|
|
image = Image.fromarray(image_array, mode=image_mode)
|
|
return image
|
|
|
|
|
|
def ImageToFloatArray(image, scale_factor=None):
|
|
"""Recovers the depth values from an image.
|
|
|
|
Reverses the depth to image conversion performed by FloatArrayToRgbImage or
|
|
FloatArrayToGrayImage.
|
|
|
|
The image is treated as an array of fixed point depth values. Each
|
|
value is converted to float and scaled by the inverse of the factor
|
|
that was used to generate the Image object from depth values. If
|
|
scale_factor is specified, it should be the same value that was
|
|
specified in the original conversion.
|
|
|
|
The result of this function should be equal to the original input
|
|
within the precision of the conversion.
|
|
|
|
For details see https://sites.google.com/site/brainrobotdata/home/depth-image-encoding.
|
|
|
|
Args:
|
|
image: Depth image output of FloatArrayTo[Format]Image.
|
|
scale_factor: Fixed point scale factor.
|
|
|
|
Returns:
|
|
A 2D floating point numpy array representing a depth image.
|
|
|
|
"""
|
|
image_array = np.array(image)
|
|
image_dtype = image_array.dtype
|
|
image_shape = image_array.shape
|
|
|
|
channels = image_shape[2] if len(image_shape) > 2 else 1
|
|
assert 2 <= len(image_shape) <= 3
|
|
if channels == 3:
|
|
# RGB image needs to be converted to 24 bit integer.
|
|
float_array = np.sum(image_array * [65536, 256, 1], axis=2)
|
|
if scale_factor is None:
|
|
scale_factor = DEFAULT_RGB_SCALE_FACTOR
|
|
else:
|
|
if scale_factor is None:
|
|
scale_factor = DEFAULT_GRAY_SCALE_FACTOR[image_dtype.type]
|
|
float_array = image_array.astype(np.float32)
|
|
scaled_array = float_array / scale_factor
|
|
return scaled_array
|
|
|
|
|
|
def FloatArrayToRawRGB(im, min_value=0.0, max_value=1.0):
|
|
"""Convert a grayscale image to rgb, no encoding.
|
|
|
|
For proper display try matplotlib's rendering/conversion instead of this version.
|
|
Please be aware that this does not incorporate a proper color transform.
|
|
http://pillow.readthedocs.io/en/3.4.x/reference/Image.html#PIL.Image.Image.convert
|
|
https://en.wikipedia.org/wiki/Rec._601
|
|
"""
|
|
im = img_as_ubyte(im)
|
|
if im.shape[-1] == 1:
|
|
im = grey2rgb(im)
|
|
return im
|