2016-09-06 57 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

很好的答案!人們可以從中學到很多... –