Fill The Probability
— source code in this page lies here
Generative-Art常常借助概率分布来构建画面的形式与秩序。
其中最常用的有Gaussian、Uniform和PowerLaw分布函数了,Processing中甚至专门提供了randomGaussian这个函数方法来返回符合标准正态分布的随机数。
最近在翻阅一本概率书籍的时候, 当中提到一种简单的算法来生成符合某概率密度函数的连续随机数(假设下图函数是我们想要的概率分布):
- 生成一个随机数x,y = F(x)。
- 再生成一个随机数m。
- 如果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))))))
现在我们可以用这些点来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))))
通过一个虚拟的画框可以更好的表现生成的细节,左边的“缝隙”以及右边的大片留白正如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的大小。
灰度图片? #
灰度图片不也是以个二维函数吗,(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即可。
— Have fun : )



