圆球法线图,图生法线图 图片生成法线图

发布于:2025-03-30 ⋅ 阅读:(24) ⋅ 点赞:(0)

目录

圆球法线图

根据图片生成法线图

深度图计算法线图


圆球法线图

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# 定义球体的参数
radius = 1.0
resolution = 100

# 生成球体表面的点
u = np.linspace(0, 2 * np.pi, resolution)
v = np.linspace(0, np.pi, resolution)
x = radius * np.outer(np.cos(u), np.sin(v))
y = radius * np.outer(np.sin(u), np.sin(v))
z = radius * np.outer(np.ones(np.size(u)), np.cos(v))

# 计算法线
nx = x / radius
ny = y / radius
nz = z / radius

# 创建 3D 图形
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

# 绘制球体
ax.plot_surface(x, y, z, color='b', alpha=0.5)

# 绘制法线
step = 5
for i in range(0, resolution, step):
    for j in range(0, resolution, step):
        ax.quiver(x[i, j], y[i, j], z[i, j], nx[i, j], ny[i, j], nz[i, j], length=0.2, color='r')

# 设置坐标轴
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('Sphere Normal Map')

# 显示图形
plt.show()

根据图片生成法线图

【游戏开发进阶】带你玩转模型法线,实验一下大胆的想法(法线贴图 | shader | Unity | python | 爬虫)-CSDN博客【游戏开发进阶】带你玩转模型法线,实验一下大胆的想法(法线贴图 | shader | Unity | python | 爬虫)-CSDN博客

import numpy as np
import scipy.ndimage
import scipy.misc
from scipy import ndimage
import argparse
import imageio
import shutil
import os


# 高斯平滑函数,用于对图像进行平滑处理
def smooth_gaussian(im, sigma):
    if sigma == 0:
        return im

    im_smooth = im.astype(float)
    kernel_x = np.arange(-3 * sigma, 3 * sigma + 1).astype(float)  # 生成高斯核的横向坐标
    kernel_x = np.exp((-(kernel_x ** 2)) / (2 * (sigma ** 2)))  # 计算高斯核

    # 对图像进行卷积,先沿横向进行卷积,再沿纵向进行卷积
    im_smooth = scipy.ndimage.convolve(im_smooth, kernel_x[np.newaxis])
    im_smooth = scipy.ndimage.convolve(im_smooth, kernel_x[np.newaxis].T)

    return im_smooth


# 使用Sobel算子计算梯度
def sobel(im_smooth):
    gradient_x = im_smooth.astype(float)
    gradient_y = im_smooth.astype(float)

    kernel = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])  # Sobel核

    # 对图像进行卷积,分别计算X和Y方向的梯度
    gradient_x = scipy.ndimage.convolve(gradient_x, kernel)
    gradient_y = scipy.ndimage.convolve(gradient_y, kernel.T)

    return gradient_x, gradient_y

# 根据计算的梯度值生成法线图
def compute_normal_map(gradient_x, gradient_y, intensity=1):
    width = gradient_x.shape[1]
    height = gradient_x.shape[0]
    max_x = np.max(gradient_x)  # 计算X方向的最大值
    max_y = np.max(gradient_y)  # 计算Y方向的最大值

    max_value = max(max_x,max_y) # 默认取X方向的最大值

    normal_map = np.zeros((height, width, 3), dtype=np.float32)  # 初始化法线图(3通道)

    intensity = 1 / intensity  # 法线强度调整
    strength = max_value / (max_value * intensity)  # 强度缩放

    # 计算法线图的X、Y、Z分量
    normal_map[..., 0] = gradient_x / max_value
    normal_map[..., 1] = gradient_y / max_value
    normal_map[..., 2] = 1 / strength

    # 对法线图进行归一化
    norm = np.sqrt(np.power(normal_map[..., 0], 2) + np.power(normal_map[..., 1], 2) + np.power(normal_map[..., 2], 2))
    normal_map[..., 0] /= norm
    normal_map[..., 1] /= norm
    normal_map[..., 2] /= norm

    # 调整法线图到0-1范围内
    normal_map *= 0.5
    normal_map += 0.5

    return normal_map


from natsort import natsorted
def main():
    # 创建输出目录
    os.makedirs(output_dir, exist_ok=True)

    # 解析命令行参数
    parser = argparse.ArgumentParser(description='Compute normal map of an image')

    # 可选参数:高斯平滑的标准差,法线图的强度
    parser.add_argument('-s', '--smooth', default=0., type=float, help='smooth gaussian blur applied on the image')
    parser.add_argument('-it', '--intensity', default=1., type=float, help='intensity of the normal map')

    args = parser.parse_args()

    sigma = args.smooth  # 获取平滑度参数
    intensity = args.intensity  # 获取法线图强度参数

    # 获取输入目录中的所有图片文件(按自然排序)
    img_files = ['%s/%s' % (i[0].replace("\\", "/"), j) for i in os.walk(input_dir) for j in i[-1] if j.lower().endswith(('jpg', 'png', 'jpeg'))]
    img_files = natsorted(img_files)

    # 遍历所有图片文件,生成法线图并保存
    for input_file in img_files:
        print(input_file)
        (_, output_file_name) = os.path.split(input_file)
        output_file = output_dir + '/_normal_' + output_file_name

        # 读取图片
        im = np.array(imageio.imread(input_file))
        if im.ndim == 3:  # 如果是彩色图像,转为灰度图像
            im_grey = np.zeros((im.shape[0], im.shape[1])).astype(float)
            im_grey = (im[..., 0] * 0.3 + im[..., 1] * 0.6 + im[..., 2] * 0.1)  # RGB加权平均
            im = im_grey

        # 对图像进行高斯平滑
        im_smooth = smooth_gaussian(im, sigma)

        # 计算Sobel梯度
        sobel_x, sobel_y = sobel(im_smooth)

        # 生成法线图
        normal_map = compute_normal_map(sobel_x, sobel_y, intensity)
        normal_map = np.uint8(normal_map * 255)  # 将法线图的值转换到0-255的范围内

        # 保存法线图
        imageio.imsave(output_file, normal_map)


# 主程序入口
if __name__ == "__main__":
    input_dir = r'B:\360MoveData\Users\Administrator\Pictures\pinije\imgs'  # 输入目录路径
    output_dir = r'B:\360MoveData\Users\Administrator\Pictures\pinije\out'  # 输出目录路径
    main()

深度图计算法线图

from glob import glob

import cv2
import numpy as np
import scipy.ndimage
import scipy.misc
from scipy import ndimage
import argparse
import imageio
import shutil
import os


# 高斯平滑函数,用于对图像进行平滑处理
def smooth_gaussian(im, sigma):
    if sigma == 0:
        return im

    im_smooth = im.astype(float)
    kernel_x = np.arange(-3 * sigma, 3 * sigma + 1).astype(float)  # 生成高斯核的横向坐标
    kernel_x = np.exp((-(kernel_x ** 2)) / (2 * (sigma ** 2)))  # 计算高斯核

    # 对图像进行卷积,先沿横向进行卷积,再沿纵向进行卷积
    im_smooth = scipy.ndimage.convolve(im_smooth, kernel_x[np.newaxis])
    im_smooth = scipy.ndimage.convolve(im_smooth, kernel_x[np.newaxis].T)

    return im_smooth


# 使用Sobel算子计算梯度
def sobel(im_smooth):
    gradient_x = im_smooth.astype(float)
    gradient_y = im_smooth.astype(float)

    kernel = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])  # Sobel核

    # 对图像进行卷积,分别计算X和Y方向的梯度
    gradient_x = scipy.ndimage.convolve(gradient_x, kernel)
    gradient_y = scipy.ndimage.convolve(gradient_y, kernel.T)

    return gradient_x, gradient_y


def compute_depth_normals(depth_map):
    # 计算X和Y方向的深度梯度
    gradient_x = scipy.ndimage.sobel(depth_map.astype(float), axis=1)
    gradient_y = scipy.ndimage.sobel(depth_map.astype(float), axis=0)

    # 计算Z分量(Z轴的梯度可以近似为常数)
    normal_x = -gradient_x
    normal_y = -gradient_y
    normal_z = np.ones_like(depth_map)  # 这里假设Z方向是1

    # 归一化法线向量
    norm = np.sqrt(normal_x ** 2 + normal_y ** 2 + normal_z ** 2)
    normal_x /= norm
    normal_y /= norm
    normal_z /= norm

    # 转换到RGB格式(0-255范围)
    normal_map = np.stack((normal_x, normal_y, normal_z), axis=-1)
    normal_map = (normal_map + 1) / 2  # 将值从 [-1,1] 归一化到 [0,1]
    normal_map = (normal_map * 255).astype(np.uint8)

    return normal_map

def compute_normal_map(gradient_x, gradient_y, intensity=1):
    width = gradient_x.shape[1]
    height = gradient_x.shape[0]
    max_x = np.max(gradient_x)  # 计算X方向的最大值
    max_y = np.max(gradient_y)  # 计算Y方向的最大值

    max_value = max(max_x,max_y) # 默认取X方向的最大值

    normal_map = np.zeros((height, width, 3), dtype=np.float32)  # 初始化法线图(3通道)

    intensity = 1 / intensity  # 法线强度调整
    strength = max_value / (max_value * intensity)  # 强度缩放

    # 计算法线图的X、Y、Z分量
    normal_map[..., 0] = gradient_x / max_value
    normal_map[..., 1] = gradient_y / max_value
    normal_map[..., 2] = 1 / strength

    # 对法线图进行归一化
    norm = np.sqrt(np.power(normal_map[..., 0], 2) + np.power(normal_map[..., 1], 2) + np.power(normal_map[..., 2], 2))
    normal_map[..., 0] /= norm
    normal_map[..., 1] /= norm
    normal_map[..., 2] /= norm

    # 调整法线图到0-1范围内
    normal_map *= 0.5
    normal_map += 0.5

    return normal_map


def compute_normal_map(gradient_x, gradient_y, intensity=1.0):
    height, width = gradient_x.shape
    normal_map = np.zeros((height, width, 3), dtype=np.float32)

    # 应用强度参数并调整方向
    normal_map[..., 0] = -gradient_x * intensity  # X分量取反
    normal_map[..., 1] = -gradient_y * intensity  # Y分量取反
    normal_map[..., 2] = 1.0  # Z分量固定

    # 逐像素归一化
    norm = np.linalg.norm(normal_map, axis=2)
    norm = np.stack([norm] * 3, axis=2)
    norm[norm == 0] = 1e-6  # 避免除以零
    normal_map /= norm

    # 转换到[0,1]范围并缩放为0-255
    normal_map = (normal_map * 0.5) + 0.5
    return np.clip(normal_map, 0.0, 1.0)

from natsort import natsorted

import os


def load_video_frames(video_path):
    frames = []
    if os.path.isdir(video_path):
        files = glob(video_path + '/*.png') + glob(video_path + '/*.jpg')
        for file in files:
            img = cv2.imread(file)
            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            frames.append(img)
        return frames, 25

    video = cv2.VideoCapture(video_path)
    fps = int(video.get(cv2.CAP_PROP_FPS))
    count = video.get(cv2.CAP_PROP_FRAME_COUNT)
    while True:
        ret, frame = video.read()
        if not ret: break
        frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
    if count != len(frames):
        print('video count err', count, len(frames))
    return frames, fps



# 主程序入口
if __name__ == "__main__":
    output_dir='output1'
    os.makedirs(output_dir, exist_ok=True)

    # 解析命令行参数
    parser = argparse.ArgumentParser(description='Compute normal map of an image')

    # 可选参数:高斯平滑的标准差,法线图的强度
    parser.add_argument('-s', '--smooth', default=0., type=float, help='smooth gaussian blur applied on the image')
    parser.add_argument('-it', '--intensity', default=1., type=float, help='intensity of the normal map')

    args = parser.parse_args()

    sigma = args.smooth  # 获取平滑度参数
    intensity = args.intensity  # 获取法线图强度参数
    mp4_path=r"C:\Users\Administrator\Downloads\cc_vis.mp4"
    frames,fps = load_video_frames(mp4_path)

    for fi, frame in enumerate(frames):
    # 获取输入目录中的所有图片文件(按自然排序)
        output_file = output_dir + f'/_normal_{fi}.jpg'

        # 读取图片
        im = frame
        if im.ndim == 3:  # 如果是彩色图像,转为灰度图像
            im_grey = np.zeros((im.shape[0], im.shape[1])).astype(float)
            im_grey = (im[..., 0] * 0.3 + im[..., 1] * 0.6 + im[..., 2] * 0.1)  # RGB加权平均
            im = im_grey

        # 对图像进行高斯平滑
        im_smooth = smooth_gaussian(im, sigma)

        # 计算Sobel梯度
        sobel_x, sobel_y = sobel(im_smooth)

        # 生成法线图
        # normal_map = compute_depth_normals(im_smooth)
        normal_map = compute_normal_map(sobel_x, sobel_y, intensity)
        normal_map = np.uint8(normal_map * 255)  # 将法线图的值转换到0-255的范围内
        # 保存法线图
        imageio.imsave(output_file, normal_map)