目录
圆球法线图
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)