Home > other >  Create 2d image from point cloud
Create 2d image from point cloud

Time:08-03

I am trying to project a point cloud into a 2d image as if it were a satellite image.

I have six files I want to project and the point clouds are quite big. For the biggest one, I have len(las.X) = 37_763_608, max(las.X) - min(las.X) = 122_124, and max(las.X) - min(las.X) = 273_683, so sometimes when calculate the size I have an overflow error.

My first try was this, but this was quite slow and took about 28 minutes to run.
Here I added the loops with k_x and k_y because the image I got was mostly black, and I wanted to have colour everywhere. I tried looping around each point/pixel to make them 5 times bigger, but this is the slow part.

see pictures

Colour version with the k padding Colour version with the k padding Black and white version without the padding Black and white version without the padding

Ideally, I would like to have the colour from one point/pixel shift to the colour of their neighbours, so that there is a gradient between them, and no have any black leftover from me initialize the image as np.zeros

import laspy
import numpy as np
from PIL import Image
import cv2
from tqdm import tqdm


las = laspy.read("area1.las")

def las_to_rgb(las):
    x, y = las.X, las.Y
    delta_x = max(x) - min(x)
    delta_y = max(y) - min(y)
    re_x = x - min(x)
    re_y = y - min(y)


    # las.red, green and blue are stored as 16bit
    r, g, b = (las.red/256).astype(np.uint8), (las.green/256).astype(np.uint8), (las.blue/256).astype(np.uint8)

    image = np.zeros((delta_y 1, delta_x 1, 3))

    for i, val in enumerate(zip(tqdm(re_x), re_y)):
        for k_x in range(-5, 6):
            for k_y in range(-5, 6):
                if val[0]   k_x < 0 or val[0]   k_x >= delta_x   1:
                    k_x = 0
                if val[1]   k_y < 0 or val[1]   k_y >= delta_y   1:
                    k_y = 0
                image[val[1] k_y, val[0] k_x] = [b[i], g[i], r[i]]

    cv2.imwrite("test.png", image)
    cv2.waitKey(0)

I found how to do it faster in numpy, but it can only do one colour at a time, so I decided to loop for multiple color but I think I am doing something wrong when I change the type to np.unit8 as python takes up to 50GB of RAM.

With numpy:

One colour:

def nu_pro(las):
    x, y = las.X, las.Y
    delta_x = max(x) - min(x)
    delta_y = max(y) - min(y)
    xs = x - min(x)
    ys = y - min(y)

    img_size = (delta_y 1, delta_x 1) #  1 for ravel_multi_index

    bgr = np.array([(las.blue/256).astype(np.uint8), (las.green/256).astype(np.uint8), (las.red/256).astype(np.uint8)])

    coords = np.stack((ys, xs))
    abs_coords = np.ravel_multi_index(coords, img_size)

    image = np.bincount(abs_coords, weights=color, minlength=img_size[1]*img_size[0])
    image = image.reshape(img_size))

    cv2.imwrite("test.png", image)
    cv2.waitKey(0)

For rgb

def nu_pro_rgb(las):
    x, y = las.X, las.Y
    delta_x = max(x) - min(x)
    delta_y = max(y) - min(y)
    xs = x - min(x)
    ys = y - min(y)

    img_size = (delta_y 1, delta_x 1) #  1 for ravel_multi_index

    rgb = np.array([(las.red/256).astype(np.uint8), (las.green/256).astype(np.uint8), (las.blue/256).astype(np.uint8)])
    image = []

    coords = np.stack((ys, xs))
    abs_coords = np.ravel_multi_index(coords, img_size)

    for i, color in enumerate(tqdm(rgb)):
        img = np.bincount(abs_coords, weights=color, minlength=img_size[1]*img_size[0])
        image.append(img.reshape(img_size))

    image = np.uint8(np.array(image))
    
    # I am probably messing up this transpose but I'll figure it out eventually
    im = Image.fromarray(image.T, "RGB")
    im.save("pil.png")

Any indication would be welcome :)

EDIT for clarification about the colours.

  • When there is overlapping, it should be the point with the highest z coordinates that should be displayed.

  • For the colouring, in the picture below, the points between A and B should be a colour gradient from A to B.
    If it is like the yellow point, then an average of the neighbouring colour (without the black if present)
    I hope I am making some sense.

Colouring example

CodePudding user response:

I do not have access to the format you use, so I show you how to rapidly plot points at x,y coordinates, and enlarge them with a kernel mask, and a color for each point

import numpy as np
import cv2

height, width = 256, 256

# generate a random sample of 1000 (x,y) coordinates and colors
x, y, = np.random.randint(0, 256, size=(2, 1000))
color = np.random.randint(0, 256, size=(1000, 3))

# generate a blank image
# int16 to manage overflow colors when convolving
pointsPlotted = np.zeros((height, width, 3), np.uint16)

# plot x,y,color into blankImage
pointsPlotted[y, x] = color


cv2.imshow("points", pointsPlotted.astype(np.uint8))

# convlove the image with a kernel of ones, size k
k = 5
kernel = np.ones((k, k), np.int16)

largerSquares = cv2.filter2D(src=pointsPlotted, ddepth=-1, kernel=kernel)

# limit max color to 255
largerSquares[largerSquares > 255] = 255

# Convert to uint8
largerSquares = largerSquares.astype(np.uint8)

cv2.imshow("Larger Squares", largerSquares)

Is this what you want?

On the overlaps, adds the colors (capped to 255)

enter image description here

CodePudding user response:

To interpolate, there are lots of libraries.

This uses cubic interpolation, but it only works inside the convex hull, so the points outside the convex hull are taken from the nearest neighbor.

If you are interpolating GIS data, you may look on Krigging interpolation, which should interpolate outside the convex hull.

This code does not check that a point with lower Z is under one with higher Z. You have to delete those points to avoid having them interpolated.

from scipy.interpolate import griddata
import numpy as np
import matplotlib.pyplot as plt
import cv2

# create data
height, width = 256, 256

# generate a random sample of 1000 (x,y) coordinates and colors
x, y, z = np.random.randint(0, 256, size=(3, 1000))
color = np.random.randint(0, 256, size=(1000, 3))

# sort x,y,z by z in ascending order so the highest z is plotted over the lowest z
zSort = z.argsort()
x, y, z, color = x[zSort], y[zSort], z[zSort], color[zSort]


# interpolation
# generate a grid where the interpolation will be calculated
X, Y = np.meshgrid(np.arange(width), np.arange(height))


R = griddata(np.vstack((x, y)).T, color[:, 0], (X, Y), method='cubic')
Rlinear= griddata(np.vstack((x, y)).T, color[:, 0], (X, Y), method='nearest')
G = griddata(np.vstack((x, y)).T, color[:, 1], (X, Y), method='cubic')
Glinear= griddata(np.vstack((x, y)).T, color[:, 1], (X, Y), method='nearest')
B = griddata(np.vstack((x, y)).T, color[:, 2], (X, Y), method='cubic')
Blinear= griddata(np.vstack((x, y)).T, color[:, 2], (X, Y), method='nearest')

#Fill empty values with nearest neighbor
R[np.isnan(R)] = Rlinear[np.isnan(R)]
G[np.isnan(G)] = Glinear[np.isnan(G)]
B[np.isnan(B)] = Blinear[np.isnan(B)]

R = R/np.max(R)
G = G/np.max(G)
B = B/np.max(B)

interpolated = cv2.merge((R, G, B))

plt.imshow(interpolated)
plt.scatter(x, y, c=color/255, marker="s",s=1)
plt.show()

enter image description here

  • Related