2016-09-06 46 views
0

一个工作,但速度很慢的解决方案,从一个区域的平均颜色值

考虑一个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 average-color [colors] 
    (mapv #(/ % (count colors)) 
     (reduce (partial mapv +) colors))) 

(这将被更优雅的载体实现的:

(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] ...) 

从这个结果,平均颜色可以通过该函数来计算库)

不过:这个现在可以捆绑,像这样,才能得到想要的结果:

(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)) 

在相同的图像:

A和错误的结果

我试图用这个片段的工作更有前途的方法

(get-data img [0 0] [10 10]) 
;; #object["[B" 0x502f5b2a "[[email protected]"] 
(vec *1) 
;; [-1 -16 -4 -2 -1 -16 -4 -2 -1 -16 -4 -2 -1 -16 ...] 

我无法弄清楚如何进一步处理这个输出以达到我的目的。

有谁知道,如何改善?

回答

2

您对解决方案的主要瓶颈略有误解。首先,您的get-pixels使用反射方法getRGB

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] 

现在你可以做一些进一步的优化:可以,如果你设置*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应该让它更快一点很容易地看到。首先,我会在average-color减少与简单的逐分量除了更换mapv开始:

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与它的过载为矩形的每一个点,返回一个int数组去,然后只需用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对象,它可以使它更快,但是我个人认为它不是必需的。

+0

很好的答案!人们可以从中学到很多... –