从图像中的某个区域获取平均颜色值

时间:2016-09-06 20:44:54

标签: java image colors clojure

一个有效但非常慢的解决方案

考虑一个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 ...]

我无法弄清楚如何为我的目的进一步处理此输出。

有人知道,如何改善这个?

1 个答案:

答案 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对象,它可以使它更快,但我个人认为不需要。