Fill The Probability

source code in this page lies here

6-pub.pngGenerative-Art常常借助概率分布来构建画面的形式与秩序。

其中最常用的有Gaussian、Uniform和PowerLaw分布函数了,Processing中甚至专门提供了randomGaussian这个函数方法来返回符合标准正态分布的随机数。

最近在翻阅一本概率书籍的时候, 当中提到一种简单的算法来生成符合某概率密度函数的连续随机数(假设下图函数是我们想要的概率分布):

  1. 生成一个随机数x,y = F(x)。
  2. 再生成一个随机数m。
  3. 如果y>m,则该随机数是符合概率分布的,则返回 (如下图的0.1),反之丢弃(如下图的0.3)并重复第一步直到找到符合该条件的x。

💡 注意自定义函数作为概率密度函数不是数学严谨的,概率也只有相对的意义,函数值相对越大越容易被返回。

这篇帖子主要记录了用这种方法来自定义概率分布并“填充”它们,使用Clojure编程语言绑定的Processing框架Quil

(defn custom-distribution [pdf dimension]
  #(loop []
     (let [ts (repeatedly dimension rand)
           v (apply pdf ts)]
       (if (< (rand) v)
         (flatten [v ts])
         (recur)))))

custom-distribution接受两个参数,第一个参数为自定义的概率分布函数F,第二个参数则是该分布函数的维度。上图的函数实际上常被称为Impulse,因为它很像一次脉搏跳动,将它用代码定义出来,然后调用custom-distribution, 即可生成符合impuse概率分布的函数即下面的impuse-distribute,每次调用impuse-distribute会返回一个二元组,二元组第二个元素即为符合impuse分布的连续随机数x,第一个参数即为概率密度值F(x),这个值后面会有用。


(defn impulse [x]
  (let [h (* 16 x)]
    (* 0.5 h (q/exp (- 1.0 h)))))


(def impulse-distribute (custom-distribution impulse 1))

(impulse-distribute)
;; => (0.4298483097809467 0.10343382658295475) [probability, x]
(impulse-distribute)
;; => (0.4539374966810296 0.0941446695057423)
(impulse-distribute)
;; => (0.35548405980442777 0.12921484983072173)


现在可以去填充impuse-dist了。在此之前我们还需要一个填充物,我希望它是一个透明的点,有着平滑的过渡,就像这样子:


(defn draw-dot
  [[x y] radius alpha]
  (let [inner (* radius 0.3)
        outer (- radius inner)
        step 0.01]
    (q/no-stroke)
    (q/fill 0 (* step alpha 10))
    (loop [r 0]
      (when (<= r 1)
        (let [cr (* 2 (+ inner (* (q/pow r 2) outer)))]
          (q/ellipse x y cr cr))
        (recur (+ r step))))))

abc.jpg

More dots ?


157.jpg

现在我们可以用这些点来Fill The Probability,先填充上面的impuse试试,
直接调用impulse-ditribute获得连续符合impuse分布的随机数,然后用该随机数作为dot的x坐标,y坐标让它随机分布,dot的半径则与该随机数的的概率密度值保持正比。

(defn draw-impuse []
  (dotimes [i 50000]
    (let [[p x] (impulse-distribute)
          x (* (q/width) x)
          y (rand-h)
          radius (* p (/ (q/height) 15))]
      (draw-dot [x y] radius 0.12))))

6-pub.png

通过一个虚拟的画框可以更好的表现生成的细节,左边的“缝隙”以及右边的大片留白正如Impuse定义的概率分布一般。

2维? #

Impulse是一个一维函数,我们可以很容易的拓展成二维函数上去,比如我们想试试 F(x, y) = cos(x*x*y) , 只需要将这个二维函数通过custom-distribution生成一个对应的生成函数即可,正如下面的mcos-distrubte。

(defn mcos [x y]
  (let [[x y] (map #(* 8.0 (- % 0.5)) [x y])] ;; scale the coordinate
    (q/cos (* x y x))))

(def mcos-distribute (custom-distribution mcos 2))

(mcos-distribute) ;; [probability, x, y]
;; => (0.9929706 0.27656461802551047 0.7504551325015157)
(mcos-distribute)
;; => (0.9823179 0.32950521573259084 0.9095162041733289)
(mcos-distribute)
;; => (0.8957972 0.4435248443390798 0.2179570089949886)

有了mcos-distribute,我们就可以直接调用取返回值的后两个元素作为坐标
(x,y) 来绘制dots,返回值的第一个元素一样是概率密度值,用于控制dot的大小。

screen-0034.png

灰度图片? #

灰度图片不也是以个二维函数吗,(x, y) 是图片的像素索引,图片的灰度值作为F(x, y),可以理解为(x, y)的概率密度值,我们可以简单的实现:

(defn image-pixel [x y]
  (let [image (q/state :image)
        w (.-width image)
        h (.-height image)
        [ix iy] (map q/floor [(* w x) (* h y)])
        pixel (q/get-pixel image ix iy)
        p (q/green pixel)]
    (q/map-range p 0 255 1 0)))

(def image-distribute (custom-distribution image-xy 2))

和上面的mcos-distribute一样,只需要不断调用image-distribute来绘制dots即可。

crown-pub.jpg

Have fun : )

 
7
Kudos
 
7
Kudos

Now read this

Play With KDTree

— source code in this page lies here 最近工作中实现了基于KDTree的3D空间划分,用于实时判断Mesh和Ray的相交,我现在依然惊讶于这个算法的优美和高效。 还是先从工作中逃离一会,试试用Clojure实现一下2D-KDtree,然后在quil框架来些与工作无关的东西看看。 先生成3000个点吧: (def pts (repeatedly 3000 #(vec2 (q/random (q/width)) (q/random... Continue →