简体   繁体   中英

Create 2d image from point cloud

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

带 k 填充的彩色版本 Colour version with the k 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.

着色示例

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)

在此处输入图像描述

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()

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM