[3D CV] viewmatrix, poses_avg, recenter_poses, poses_bounds.npy, generate_spiral_path, generate_ellipse_path
poses.shape: (N, 3, 5)
poses์ shape์ N๊ฐ์ ์ด๋ฏธ์ง์ ๋ํ pose๋ค์ ๋ชจ์์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค.
- camera extrinsics๋ฅผ ๊ตฌ์ฑํ๋ rotation, translation 3x4
- camera intrinsics๋ฅผ ๊ตฌํ ์ ์๋ H, W, focal length 3x1
- ๊ฐ pose๋ camera extrinsics์ camera intrinsics์ ๊ตฌํ๋๋ฐ ํ์ํ parameter๋ค์ ํฌํจํ๊ณ ์์ต๋๋ค.
def normalize(x):
return x / np.linalg.norm(x)
def viewmatrix(lookdir, up, position, subtract_position=False):
"""Construct lookat view matrix."""
vec2 = normalize((lookdir - position) if subtract_position else lookdir)
vec0 = normalize(np.cross(up, vec2))
vec1 = normalize(np.cross(vec2, vec0))
m = np.stack([vec0, vec1, vec2, position], axis=1)
return m
def poses_avg(poses):
"""New pose using average position, z-axis, and up vector of input poses."""
position = poses[:, :3, 3].mean(0)
z_axis = poses[:, :3, 2].mean(0)
up = poses[:, :3, 1].mean(0)
cam2world = viewmatrix(z_axis, up, position)
return cam2world
poses[:, :3, 3] -> translation ๋ฒกํฐ์ด๋ฏ๋ก viewmatrix์์ position
์ผ๋ก ๋ณ์๋ช
์ ์ ์ํฉ๋๋ค.
poses[:, :3, 2] -> z๋ฐฉํฅ ๋ฒกํฐ์ด๋ฏ๋ก viewmatrix์์ z_axis
๋ก ๋ณ์๋ช
์ ์ ์ํฉ๋๋ค.
poses[:, :3, 1] -> y๋ฐฉํฅ ๋ฒกํฐ๋ viewmatrix์์ up
์ผ๋ก ๋ณ์๋ช
์ ์ ์ํฉ๋๋ค.
viewmatrix์์๋ ๊ฒฐ๋ก ์ ์ผ๋ก ๋ค์๊ณผ ๊ฐ์ด ์ ์ํฉ๋๋ค.
poses[:, :3, 3].mean(0)
์ด๋ฏ๋ก ๋ชจ๋ poses๋ค์position
์ ํ๊ท ์ translation ๋ฒกํฐ๋ก ์ ์ํฉ๋๋ค.poses[:, :3, 2].mean(0)
์ด๋ฏ๋ก ๋ชจ๋ poses๋ค์z_axis
์ ํ๊ท ์ z๋ฐฉํฅ ๋ฒกํฐ๋ก ์ ์ํฉ๋๋ค.poses[:, :3, 1].mean(0)
์ด๋ฏ๋ก ๋ชจ๋ poses๋ค์up
์ ํ๊ท ์ up๋ฐฉํฅ ๋ฒกํฐ๋ก ์ ์ํฉ๋๋ค.
viewmatrix(z_axis, up, position)์ ๋ฐ๋๋ฐ, ์ผ๋ฐ์ ์ผ๋ก z_axis
๋ ์นด๋ฉ๋ผ๊ฐ ๋ณด๋ ๋ฐฉํฅ์ ๊ฐ๋ฆฌํค๋ฏ๋ก lookdir
๋ก ๋ฐ์ต๋๋ค.
def normalize(x):
return x / np.linalg.norm(x)
def viewmatrix(lookdir, up, position, subtract_position=False):
"""Construct lookat view matrix."""
vec2 = normalize((lookdir - position) if subtract_position else lookdir)
vec0 = normalize(np.cross(up, vec2))
vec1 = normalize(np.cross(vec2, vec0))
m = np.stack([vec0, vec1, vec2, position], axis=1)
return m
def poses_avg(poses):
"""New pose using average position, z-axis, and up vector of input poses."""
position = poses[:, :3, 3].mean(0)
z_axis = poses[:, :3, 2].mean(0)
up = poses[:, :3, 1].mean(0)
cam2world = viewmatrix(z_axis, up, position)
return cam2world
z_axis
๋lookdir
up
์up
position
์position
lookdir
์ ๋จผ์ normalizeํ๊ณ . normalizeํlookdir
๊ณผup
์ cross product๋กright
๋ฒกํฐ(=vec0
)๋ฅผ ๊ตฌํฉ๋๋ค.- normalizeํ
right
๋ฒกํฐ์ normalizeํlookdir
๋ฒกํฐ๋ฅผ cross productํ์ฌup
๋ฒกํฐ๋ฅผ ์ ๋๋ก ์ง๊ตํ๊ฒ ์ ์ํด์ค๋๋ค. - ์ด๋ฅผ stackํ์ฌ x๋ฐฉํฅ, y๋ฐฉํฅ, z๋ฐฉํฅ ๋ฒกํฐ, translation์ธ
position
์ ์์ cam2world 3x4 matrix๋ก ๋ฐํํฉ๋๋ค.
recenter_poses
def recenter_poses(poses: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
"""Recenter poses around the origin."""
cam2world = poses_avg(poses)
transform = np.linalg.inv(pad_poses(cam2world))
poses = transform @ pad_poses(poses)
return unpad_poses(poses), transform
def pad_poses(p):
"""Pad [..., 3, 4] pose matrices with a homogeneous bottom row [0,0,0,1]."""
bottom = np.broadcast_to([0, 0, 0, 1.], p[..., :1, :4].shape)
return np.concatenate([p[..., :3, :4], bottom], axis=-2)
def unpad_poses(p):
"""Remove the homogeneous bottom row from [..., 4, 4] pose matrices."""
return p[..., :3, :4]
recenter_poses
๋ ์์์ poses_avg๋ก ๊ตฌํ ํ๋์ cam2world์ ์ญ๋ณํ์ ์ทจํ์ฌ world2cam์ ๊ตฌํ๊ณ- ๋ชจ๋ poses์ ๋ํด ์ด world2cam ๋ณํ์ matrix multiplicationํ์ฌ ๋ชจ๋ poese๋ค์ด world coordinate์ ๊ฐ๊น๋๋ก ์ฎ๊น๋๋ค.
pad_poses
,unpad_poses
๋ ๋จ์ํ 4x4 transformation์ธ homogeneous coordinate์์ ์ฐ์ฐํ๊ณ return ํ๊ธฐ ์ํด ์ฌ์ฉํฉ๋๋ค.
backcenter_poses
- ์ด์ฒ๋ผ ๋ชจ๋ poses์ ๋ํ ํ๊ท pose์ธ cam2world๋ฅผ ๊ตฌํ๊ณ , ์ด์ ์ญ๋ณํ์ธ world2cam์ ๋ชจ๋ poses๋ค์ ๋ํด ๋งคํธ๋ฆญ์ค ์ฐ์ฐํ์ฌ, poses๋ฅผ ํ๋ฒ์ ์ํ๋ ์์น๋ก ์ฎ๊ฒจ์ค ์ ์์ต๋๋ค.
- ์์ฉ์ผ๋ก๋ ์๋์ ๊ฐ์ด pose_ref์ cam2world๋ฅผ ๊ตฌํ์ฌ, ์ด์ ์ญ๋ณํ์ธ world2cam์ผ๋ก, poses๋ฅผ ๋ชจ๋ recenterํ ์ ์์ต๋๋ค.
def backcenter_poses(poses, pose_ref):
"""Recenter poses around the origin."""
cam2world = poses_avg(pose_ref)
poses = pad_poses(cam2world) @ pad_poses(poses)
return unpad_poses(poses)
poses_bounds.npy
- poses_bounds.npy๋ (N, 17)๋ก ์ด๋ฏธ์ง N ๊ฐ์๋งํผ pose๊ฐ N๊ฐ ์๊ณ , 3x5=15๊ฐ์ near bound, far bound 2๊ฐ๊ฐ ๋ํด์ 17์ shape์ ๊ฐ์ง๋๋ค.
- poses_bounds.npy๋ฅผ loadํ๊ณ near, far๋ ์ฌ์ฉํ์ง ์๊ธฐ ์ํด poses_arr[:, :-2]๋ก (N,15)์ shape์ ์ทจํ๊ณ reshapeํ์ฌ (N, 3, 5)์ poses๋ก ๋ง๋ญ๋๋ค.
- bounds๋ ๋ฐ๋ก poses[:, -2:]๋ก ์ธ๋ฑ์ฑํ์ฌ (N, 2)๋ก N๊ฐ์ pose์ ๋ํด near, far bound๋ฅผ ์ ์ํด์ค๋๋ค.
poses = poses_arr[:, :-2].reshape([-1, 3, 5])
bounds = poses_arr[:, -2:]
generate_spiral_path
def generate_spiral_path(poses_arr,
n_frames: int = 180,
n_rots: int = 2,
zrate: float = .5) -> np.ndarray:
"""Calculates a forward facing spiral path for rendering."""
poses = poses_arr[:, :-2].reshape([-1, 3, 5])
bounds = poses_arr[:, -2:]
fix_rotation = np.array([
[0, -1, 0, 0],
[1, 0, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
], dtype=np.float32)
poses = poses[:, :3, :4] @ fix_rotation
scale = 1. / (bounds.min() * .75)
poses[:, :3, 3] *= scale
bounds *= scale
poses, transform = recenter_poses(poses)
close_depth, inf_depth = bounds.min() * .9, bounds.max() * 5.
dt = .75
focal = 1 / (((1 - dt) / close_depth + dt / inf_depth))
# Get radii for spiral path using 90th percentile of camera positions.
positions = poses[:, :3, 3]
radii = np.percentile(np.abs(positions), 90, 0)
radii = np.concatenate([radii, [1.]])
# Generate poses for spiral path.
render_poses = []
cam2world = poses_avg(poses)
up = poses[:, :3, 1].mean(0)
for theta in np.linspace(0., 2. * np.pi * n_rots, n_frames, endpoint=False):
t = radii * [np.cos(theta), -np.sin(theta), -np.sin(theta * zrate), 1.]
position = cam2world @ t
lookat = cam2world @ [0, 0, -focal, 1.]
z_axis = position - lookat
render_pose = np.eye(4)
render_pose[:3] = viewmatrix(z_axis, up, position)
render_pose = np.linalg.inv(transform) @ render_pose
render_pose[:3, 1:3] *= -1
render_pose[:3, 3] /= scale
render_poses.append(np.linalg.inv(render_pose))
render_poses = np.stack(render_poses, axis=0)
return render_poses
transform_poses_pca
- ์ด ์ฝ๋๋ ์นด๋ฉ๋ผ์ ์์น์ ๋ฐฉํฅ์ ํํํ๋ poses ๋ฐ์ดํฐ๋ฅผ ์ฃผ์ฑ๋ถ ๋ถ์(PCA, Principal Component Analysis)์ ์ฌ์ฉํด ์ขํ๊ณ๋ฅผ XYZ ์ถ์ ๋ง์ถฐ ๋ณํํ๋ ํจ์์ ๋๋ค.
- poses๋ ๊ฐ ์นด๋ฉ๋ผ์ ์๋ ์ขํ๊ณ์์ ์นด๋ฉ๋ผ ์ขํ๊ณ๋ก์ ๋ณํ์ ๋ํ๋ด๋ ํ๋ ฌ๋ค๋ก, ์ด ํจ์๋ poses์ ํ๊ท ์์น๋ฅผ ์์ ์ผ๋ก ๋ง์ถ ๋ค, ์ฃผ์ฑ๋ถ์ ์ฐพ์์ ํ์ ํ๋ ฌ์ ๊ณ์ฐํ๊ณ , ์นด๋ฉ๋ผ์ ์ขํ๊ณ๋ฅผ ๋ณ๊ฒฝํ๋ ๊ณผ์ ์ ๋๋ค.
def transform_poses_pca(poses):
"""Transforms poses so principal components lie on XYZ axes.
Args:
poses: a (N, 3, 4) array containing the cameras' camera to world transforms.
Returns:
A tuple (poses, transform), with the transformed poses and the applied
camera_to_world transforms.
"""
t = poses[:, :3, 3]
t_mean = t.mean(axis=0)
t = t - t_mean
eigval, eigvec = np.linalg.eig(t.T @ t)
# Sort eigenvectors in order of largest to smallest eigenvalue.
inds = np.argsort(eigval)[::-1]
eigvec = eigvec[:, inds]
rot = eigvec.T
if np.linalg.det(rot) < 0:
rot = np.diag(np.array([1, 1, -1])) @ rot
transform = np.concatenate([rot, rot @ -t_mean[:, None]], -1)
poses_recentered = unpad_poses(transform @ pad_poses(poses))
transform = np.concatenate([transform, np.eye(4)[3:]], axis=0)
# Flip coordinate system if z component of y-axis is negative
if poses_recentered.mean(axis=0)[2, 1] < 0:
poses_recentered = np.diag(np.array([1, -1, -1])) @ poses_recentered
transform = np.diag(np.array([1, -1, -1, 1])) @ transform
# Just make sure it's it in the [-1, 1]^3 cube
scale_factor = 1. / np.max(np.abs(poses_recentered[:, :3, 3]))
poses_recentered[:, :3, 3] *= scale_factor
transform = np.diag(np.array([scale_factor] * 3 + [1])) @ transform
return poses_recentered, transform
- ํ๊ท ์์น ๋ง์ถ๊ธฐ (t_mean): poses์ ์นด๋ฉ๋ผ ์์น๋ฅผ ํ๊ท ๋ด์ด ์์ ์ ๋ง์ถฅ๋๋ค.
- ์ฃผ์ฑ๋ถ ๊ณ์ฐ (eigval, eigvec): ์นด๋ฉ๋ผ ์์น๋ค์ ๊ณต๋ถ์ฐ ํ๋ ฌ์์ ๊ณ ์ ๊ฐ ๋ถํด๋ฅผ ํ์ฌ ์ฃผ์ฑ๋ถ ์ถ์ ์ฐพ์ต๋๋ค. ๊ณ ์ ๊ฐ์ด ํฐ ์ถ์ด ๋ ์ค์ํ ์ถ์ด๋ฏ๋ก, ์ด๋ฅผ ๊ธฐ์ค์ผ๋ก ์ขํ๊ณ๋ฅผ ํ์ ์ํต๋๋ค.
- ์ขํ๊ณ ํ์ (rot): ๊ฐ์ฅ ์ค์ํ ์ฃผ์ฑ๋ถ์ด X์ถ์ ๋ง๋๋ก ํ๊ณ , ๊ทธ ๋ค์ ์ถ๋ค์ด Y์ Z์ถ์ ๋ง๋๋ก ํ์ ํฉ๋๋ค. ๋ง์ฝ ํ์ ํ๋ ฌ์ ํ๋ ฌ์์ด ์์๋ผ๋ฉด, Z์ถ ๋ฐฉํฅ์ ๋ฐ๋๋ก ๋ง์ถฅ๋๋ค.
- ๋ณํ ํ๋ ฌ ์ ์ฉ (transform): ์๋์ poses์ ๋ณํ ํ๋ ฌ์ ์ ์ฉํ์ฌ ์นด๋ฉ๋ผ์ ์๋ก์ด ์ขํ๊ณ๋ก ๋ณํ๋ poses๋ฅผ ๊ณ์ฐํฉ๋๋ค.
- ์ขํ๊ณ ํ๋ฆฝ: ๋ณํ๋ ์ขํ๊ณ์ Y์ถ์ด ์๋์ชฝ์ ํฅํ๊ณ ์๋ค๋ฉด, ์ขํ๊ณ๋ฅผ ๋ค์ ๋ฐ์ ์ํต๋๋ค.
- ํฌ๊ธฐ ์กฐ์ (scale_factor): ๋ณํ๋ ์ขํ๊ณ๋ฅผ ํฌ๊ธฐ ์กฐ์ ํ์ฌ [-1, 1] ๋ฒ์์ ๋ง์ถฅ๋๋ค.
- ์ด ๊ณผ์ ์์ PCA๋ฅผ ํตํด ์นด๋ฉ๋ผ์ ์์น๋ฅผ ๋ณด๋ค ์ง๊ด์ ์ผ๋ก ๋ณผ ์ ์๋ ํํ๋ก ๋ณํํ์ฌ, ์ฃผ์ฑ๋ถ ์ถ์ด XYZ ์ถ์ ๋ง๊ฒ ์ขํ๊ณ๋ฅผ ํ์ ํ๊ณ ์ ๋ ฌํ๋ ๊ฒ์ ๋๋ค.
- ์ด ์ฝ๋๋ ์นด๋ฉ๋ผ์ ๋ฐฐ์น๊ฐ ์ด๋ป๊ฒ ์ด๋ฃจ์ด์ ธ ์๋์ง ์๊ฐ์ ์ผ๋ก ๋ ์ ์ดํดํ๊ณ ์ ํ ๋ ์ ์ฉํ ์ ์์ต๋๋ค. 3D ๊ณต๊ฐ์์ ์นด๋ฉ๋ผ๋ค์ ์์น๊ฐ ์ด๋ป๊ฒ ๋ถํฌํ๋์ง ํ์ ํ ์ ์๊ฒ ๋์์ค๋๋ค.
focus_pt_fn
- ์ด ์ฝ๋๋ poses๋ผ๋ ์นด๋ฉ๋ผ์ ์์น์ ๋ฐฉํฅ ์ ๋ณด๋ฅผ ์ ๋ ฅ๋ฐ์, ๋ชจ๋ ์นด๋ฉ๋ผ์ ์ด์ ์ถ(focal axis)์ ๊ฐ์ฅ ๊ฐ๊น์ด ์ ์ ๊ณ์ฐํ๋ ํจ์์ ๋๋ค.
- ์ด ํจ์๋ ์ฌ๋ฌ ์นด๋ฉ๋ผ๊ฐ ์๋ก ๋ค๋ฅธ ๋ฐฉํฅ์ ๋ณด๊ณ ์์ ๋, ๊ฐ ์นด๋ฉ๋ผ์ ์์ (์ถ)์ ๊ฐ์ฅ ๊ฐ๊น์ด ๊ณตํต์ ํ ์ (์ง์ค์ , focus point)์ ์ฐพ๋ ๊ณผ์ ์ ๋๋ค.
def focus_pt_fn(poses):
"""Calculate nearest point to all focal axes in poses."""
directions, origins = poses[:, :3, 2:3], poses[:, :3, 3:4]
m = np.eye(3) - directions * np.transpose(directions, [0, 2, 1])
mt_m = np.transpose(m, [0, 2, 1]) @ m
focus_pt = np.linalg.inv(mt_m.mean(0)) @ (mt_m @ origins).mean(0)[:, 0]
return focus_pt
์ ๋ ฅ poses:
- poses๋ ์ฌ๋ฌ ์นด๋ฉ๋ผ์ ์์น์ ๋ฐฉํฅ์ ํฌํจํ๋ ํ๋ ฌ์ ๋๋ค. ๊ฐ pose๋ ์นด๋ฉ๋ผ์ ์๋ ์ขํ๊ณ์์ ์นด๋ฉ๋ผ ์ขํ๊ณ๋ก ๋ณํํ๋ ์ ๋ณด๊ฐ ๋ค์ด ์์ต๋๋ค.
- poses[:, :3, 2:3]: ๊ฐ ์นด๋ฉ๋ผ์ z์ถ(์ด์ ์ถ)์ ๋ํ๋ ๋๋ค. ์ด ์ถ์ด ์นด๋ฉ๋ผ๊ฐ ๋ฐ๋ผ๋ณด๋ ๋ฐฉํฅ์ ๋ํ๋ ๋๋ค.
- poses[:, :3, 3:4]: ๊ฐ ์นด๋ฉ๋ผ์ ์์น(์์ )๋ฅผ ๋ํ๋ ๋๋ค.
์นด๋ฉ๋ผ ์ถ ๊ธฐ๋ฐ ๊ณ์ฐ:
- directions: ๊ฐ ์นด๋ฉ๋ผ์ z์ถ ๋ฐฉํฅ(์ฆ, ์ด์ ๋ฐฉํฅ)์ ์ถ์ถํฉ๋๋ค.
- origins: ๊ฐ ์นด๋ฉ๋ผ์ ์์น(์์ )๋ฅผ ์ถ์ถํฉ๋๋ค.
๊ทผ์ฌ์ ์ธ ์นด๋ฉ๋ผ ์ถ๋ค๋ก๋ถํฐ ํ๋ฉด ๊ณ์ฐ:
- m = np.eye(3) - directions * np.transpose(directions, [0, 2, 1]): ๊ฐ ์นด๋ฉ๋ผ์ ์ด์ ์ถ์ ์ด์ฉํ์ฌ ํ๋ฉด์ ์ ์ํฉ๋๋ค.
- ์ฌ๊ธฐ์ np.eye(3)์ ๋จ์ ํ๋ ฌ์ด๊ณ , ์ด์ ์ถ์ ๊ธฐ์ค์ผ๋ก ์นด๋ฉ๋ผ์ ์ถ์ ์์งํ ํ๋ฉด์ ๊ณ์ฐํ๋ ๊ณผ์ ์ ๋๋ค.
ํ๋ฉด์ ์ฌ์ฉํด ์ด์ ์ (focus point) ๊ณ์ฐ:
- mt_m = np.transpose(m, [0, 2, 1]) @ m: ๊ฐ ์นด๋ฉ๋ผ ํ๋ฉด์ ์ ๋ณด์์ ๋ณํ๋ ํ๋ ฌ์ ๊ตฌํฉ๋๋ค.
- focus_pt = np.linalg.inv(mt_m.mean(0)) @ (mt_m @ origins).mean(0)[:, 0]: ์ด ๊ณผ์ ์ ํตํด, ๋ชจ๋ ์นด๋ฉ๋ผ๊ฐ ๊ณตํต์ผ๋ก ๋ฐ๋ผ๋ณด๋ ์ , ์ฆ ์ด์ ์ (focus point)์ ๊ณ์ฐํฉ๋๋ค.
- ๊ฐ ์นด๋ฉ๋ผ์ ์ด์ ์ถ์ด ๊ฐ๋ฆฌํค๋ ํ๋ฉด์์ ๊ฐ์ฅ ๊ฐ๊น์ด ์ ์ ์ฐพ๋ ๊ณผ์ ์ด๋ผ๊ณ ๋ณผ ์ ์์ต๋๋ค.
์ฃผ์ ๊ฐ๋ :
- ์ด์ ์ถ (Focal Axis): ๊ฐ ์นด๋ฉ๋ผ๊ฐ ๋ฐ๋ผ๋ณด๋ ๋ฐฉํฅ์ ๋๋ค. ๊ฐ ์นด๋ฉ๋ผ์ z์ถ์ ๊ธฐ์ค์ผ๋ก ํ์ฌ ์ด๋ฅผ ๊ณ์ฐํฉ๋๋ค.
- ํ๋ฉด ๊ณ์ฐ: ๊ฐ ์นด๋ฉ๋ผ์ z์ถ์ ์์ง์ธ ํ๋ฉด์ ์ ์ํ์ฌ, ์ด ํ๋ฉด๋ค์ด ๊ต์ฐจํ๋ ์ง์ ๋๋ ๊ทผ์ ํ ์ง์ ์ ๊ตฌํ๋ ๋ฐฉ์์ ๋๋ค.
- ์ด์ ์ (Focus Point): ์ฌ๋ฌ ์นด๋ฉ๋ผ์ ์์ ์ด ๋ชจ์ด๋ ์ง์ ๋๋ ๊ทธ ์ง์ ์ ๊ฐ์ฅ ๊ฐ๊น์ด ์ ์ ๊ณ์ฐํฉ๋๋ค.
์์ฝ:
- ์ด ํจ์๋ ์ฌ๋ฌ ์นด๋ฉ๋ผ์ ๋ฐฉํฅ๊ณผ ์์น๋ฅผ ๊ณ ๋ คํ์ฌ, ๋ชจ๋ ์นด๋ฉ๋ผ์ ์์ ์ด ๊ต์ฐจํ๋ ๊ฐ์ฅ ๊ฐ๊น์ด ๊ณตํต์ ํ ์ ์ ๊ณ์ฐํ๋ ํจ์์ ๋๋ค.
- ์ด ๊ณ์ฐ์ ์นด๋ฉ๋ผ ๋ฐฐ์ด์์ ํน์ ํ ํ ์ง์ ์ ๋์์ ๋ฐ๋ผ๋ณด๋๋ก ์๊ฐ์ ์ค์ฌ์ ์ฐพ๋ ๋ฐ ์ ์ฉํ ์ ์์ต๋๋ค.
Leave a comment