考虑一个Image
对象,可以这样创建:
(import 'javax.imageio.ImageIO)
(import 'java.awt.Color)
(require '[clojure.java.io :as io])
(def img (ImageIO/read (io/file "/path/to/img.jpg")))
以下函数从Image
的区域中提取像素序列。每个都由RGB值的矢量表示:
(defn get-pixels [img [x y] [w h]]
(for [x (range x (+ x w))
y (range y (+ y h))]
(let [c (Color. (.getRGB img x y))]
[(.getRed c) (.getGreen c) (.getBlue c)])))
(get-pixels img [0 0] [10 10])
;; ([254 252 240] [254 252 240] [254 252 240] [254 252 240] ...)
根据此结果,可以通过此函数计算平均颜色:
(defn average-color [colors]
(mapv #(/ % (count colors))
(reduce (partial mapv +) colors)))
(使用矢量库可以更优雅地实现)
但是:现在可以这样链接,以获得预期的结果:
(average-color (get-pixels img [0 0] [10 10]))
;; [254 252 240]
问题在于它非常慢,这并不奇怪。我想瓶颈在get-pixels函数中,它为每个像素创建一个Color
对象。
我正在尝试使用此代码段:
(import 'java.awt.Rectangle)
(defn get-data [img [x y] [w h]]
(-> (.getData img (Rectangle. x y w h))
.getDataBuffer
.getData))
使用相同的图像:
(get-data img [0 0] [10 10])
;; #object["[B" 0x502f5b2a "[B@502f5b2a"]
(vec *1)
;; [-1 -16 -4 -2 -1 -16 -4 -2 -1 -16 -4 -2 -1 -16 ...]
我无法弄清楚如何为我的目的进一步处理此输出。
有人知道,如何改善这个?
答案 0 :(得分:2)
您对解决方案的主要瓶颈略有不妥。首先,您的get-pixels
使用getRGB
方法的反射。如果设置*warn-on-reflection*
user> (set! *warn-on-reflection* true)
true
user>
(defn get-pixels [img [x y] [w h]]
(for [x (range x (+ x w))
y (range y (+ y h))]
(let [c (Color. (.getRGB img x y))]
[(.getRed c) (.getGreen c) (.getBlue c)])))
;;Reflection warning, *cider-repl localhost*:2136:21 - call to method getRGB can't be resolved (target class is unknown).
;;Reflection warning, *cider-repl localhost*:2136:21 - call to method getRGB can't be resolved (target class is unknown).
#'user/get-pixels
user> (time (average-color (get-pixels image [0 0] [300 300])))
;;"Elapsed time: 505.637246 msecs"
[4822271/22500 3535699/18000 15749839/90000]
因此添加typehint会使其更快:
user>
(defn get-pixels [^java.awt.image.BufferedImage img [x y] [w h]]
(for [x (range x (+ x w))
y (range y (+ y h))]
(let [c (Color. (.getRGB img x y))]
[(.getRed c) (.getGreen c) (.getBlue c)])))
#'user/get-pixels
user> (time (average-color (get-pixels image [0 0] [300 300])))
;;"Elapsed time: 149.073099 msecs"
[4822271/22500 3535699/18000 15749839/90000]
现在你可以进行一些进一步的优化。首先,我将从简单的组件添加中替换mapv
减少中的average-color
开始:
user>
(defn average-color [colors]
(mapv #(/ % (count colors))
(reduce (fn [[r g b] [r1 g1 b1]]
[(+ r r1) (+ g g1) (+ b b1)])
colors)))
#'user/average-color
user> (time (average-color (get-pixels image [0 0] [300 300])))
"Elapsed time: 42.657254 msecs"
[4822271/22500 3535699/18000 15749839/90000]
确定。现在它比你的第一个版本快10倍以上。但你仍然可以进一步优化它。我会为每个点替换.getRGB
,其重载为矩形,返回整数数组,然后用areduce
减少它:
user>
(defn get-pixels2 ^ints [^java.awt.image.BufferedImage img [x y] [w h]]
(.getRGB img x y w h (int-array (* w h)) 0 w))
#'user/get-pixels2
user>
(defn average-color2 [^ints pixels]
(mapv #(/ % (count pixels))
(areduce pixels idx ret [0 0 0]
(let [[r g b] ret
c (Color. (aget pixels idx))]
[(+ r (.getRed c))
(+ g (.getGreen c))
(+ b (.getBlue c))]))))
#'user/average-color2
user> (time (average-color2 (get-pixels2 image [0 0] [300 300])))
"Elapsed time: 14.601505 msecs"
[4822271/22500 3535699/18000 15749839/90000]
现在我觉得应该可以接受。此外,您可以尝试使用按位运算来获取颜色分量,而不是创建Color
对象,它可以使它更快,但我个人认为不需要。