Sep 2

练手(附加课题):ACG风格的空间图像边缘查找(Canvas实现)

Lrdcq , 2019/09/02 11:37 , 程序 , 閱讀(1337) , Via 本站原創
上次尝试了空间基础阴影/投影绘制https://lrdcq.com/me/read.php/115.htm的实现尝试,没有提到一个附加课题,对于如那个demo中那样的纯色空间绘制,除去阴影现在更加流行的渲染辅助方式是ACG风格的描边效果。

什么叫ACG风格?可以参考日本画师ideolo的图:
點擊在新視窗中瀏覽此圖片

关注这幅画的色块与边缘,有以下特点:

- 目标对象的外边界粗壮并有力。
- 目标对象的内部描边稍细,但依旧富有层次感,能看出各个色块的前后关系与距离感。


注意到上面这个描述,如果二维作画需要用描边来表示距离感,那有3D空间的渲染当然能更方便的描述这个过程了。

于是本次的Demo:https://lrdcq.com/test/web_canvas_edgefinder/
内嵌:


基本思路

其实上方已经把思路描述清楚了,由于边缘描述的是z轴上的纵深感,因此我们其实需要找到3D空间中模型的边缘或者说大的间隙。

1. 因此比较直接的二次处理方式,我们可以对当前图像的深度图进行处理。
2. 拿到深度图后,假设目标模型的背后深度为无限深,我们对深度图进行边缘查找,就能得到模型z空间意义上的边缘了。
3. 边缘查找获取到的值为边缘的斜率或者叫深度,那我们按照边缘的深度值去绘制不同粗细的线条,即可实现这个效果了。


实现过程

整体分三个步骤

a. 深度图绘制,这个前几个习题里已经涉及过,在此不在重复描述了。

b. 边缘查找

在图像学的领域,有效的边缘查找一直都是一个非常难搞的课题,但是对于已经被我们清晰化的深度图来说,边缘查找易如反掌。深度图的边缘具有良好的特点(只有边缘不连续,不可导)。因此不用做什么降噪,采用最简单的图像卷积直接卷就好。因此这里采用了一个最基础的算子:[0, -1, 0, -1, 4, -1, 0, -1, 0]即原始的Laplace算子。这个值来源于图像卷积的wiki,应该很经典啦:
點擊在新視窗中瀏覽此圖片

3x3Laplace图像卷积的实现当然就很简单了,对图像size-1范围内的所有像素进行一次遍历即可:
const EDGE_MAP_A = [0, -1, 0, -1, 4, -1, 0, -1, 0];
for (let i = 1 ; i < IMG_SIZE - 1; i++) {
  for (let j = 1 ; j < IMG_SIZE - 1; j++) {
    const value =
      getImageValue(canvas_display_light_data, IMG_SIZE, i - 1, j - 1 ,0) * EDGE_MAP_A[0] +
      getImageValue(canvas_display_light_data, IMG_SIZE, i - 0, j - 1 ,0) * EDGE_MAP_A[1] +
      getImageValue(canvas_display_light_data, IMG_SIZE, i + 1, j - 1 ,0) * EDGE_MAP_A[2] +
      getImageValue(canvas_display_light_data, IMG_SIZE, i - 1, j ,0) * EDGE_MAP_A[3] +
      getImageValue(canvas_display_light_data, IMG_SIZE, i - 0, j ,0) * EDGE_MAP_A[4] +
      getImageValue(canvas_display_light_data, IMG_SIZE, i + 1, j ,0) * EDGE_MAP_A[5] +
      getImageValue(canvas_display_light_data, IMG_SIZE, i - 1, j + 1 ,0) * EDGE_MAP_A[6] +
      getImageValue(canvas_display_light_data, IMG_SIZE, i - 0, j + 1 ,0) * EDGE_MAP_A[7] +
      getImageValue(canvas_display_light_data, IMG_SIZE, i + 1, j + 1 ,0) * EDGE_MAP_A[8];
    
      setImageGrey(canvas_display_border_data, IMG_SIZE, i, j, value > 0 ? value : 0);
  }
}

c. 边缘加粗

通过Laplace算子查找出来结果是通过线条的灰度表示的深度,但是我们想通过线宽来表示深度,而线的颜色是一致的,有什么好的转换方法么?

- 基本思路还是滤波,比如我希望把深距离线宽拓展为5,浅距离线宽拓展为3,一个两级的梯度绘制,那我准备一个5x5的圆形滤波器[0, 1, -1, 1, 0, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 1, -1, -1, -1, 1, 0, 1, -1, 1, 0]一个大概这样,对边缘图进行遍历,但是这个滤波器获取的是窗口5x5值的max和min,即可获取到每个像素点5x5区域内有没有边缘与边缘的值,max是5的宽度找到的深度值,-min是3的宽度找到的深度值,再根据边缘的值进行选择性绘制即可。
- 如上这个操作会有大量的图像采样即读操作,写操作则极少。

- 另一个偷懒的方案是如果不考虑在片段着色器中运行,则在边缘查找的同时,直接针对每个边缘点周围进行原始图像的绘制操作即可。在canvas中运行反而效率高一些。

最后得到如demo的效果。

其他

在实现过程中注意到的一些细节:

a. Laplace算子计算的边缘不可避免的会遇到一些噪点,因此在使用Laplace计算结果的时候,还是需要进行过滤。比较直接的方式是阈值过滤,毕竟Laplace算子的噪点的值应该都很小。如果感觉还是有断续的感觉,再过一个大检测范围的中值滤波做一个平滑操作即可,但是相对比较消耗性能。

b. 直接边缘查找出来的数据一定会有边缘平滑问题,这个下个课题解决。
关键词:canvas
logo