Clojure的平均亮度很慢

时间:2017-04-21 06:49:13

标签: java image clojure

成为Clojure的新手我想计算(很多)jpg图像的平均亮度。为此,我使用Java中的ImageIO/read将图像加载到内存中,提取其后面的字节缓冲区并应用平均值。

(defn brightness
  "Computes the average brightness of an image."
  [^File file]
  (-> file
    ImageIO/read
    .getRaster
    .getDataBuffer
    .getData
    byteaverage))

这里,平均值

(defn byteaverage
  [numbers]
  (/ (float
     (->> numbers
        (map bytetoint)
        (apply +)))
     (count numbers))
  )

需要考虑字节是用Java签名的,需要先将其转换为足够大的整数。

(defn bytetoint
   [b]
   (bit-and b 0xFF)
  )

虽然这确实给出了正确的结果,但它非常慢。 20万像素图像需要大约10到20秒。磁盘访问不是问题。从time开始,罪魁祸首似乎是bytetoint转换。只是将此bytetoint映射到字节数组就会占用8 GB的内存,并且不会在REPL中终止。

为什么会这样做呢?

PS:我知道可以使用其他编程语言,库,多线程或更改算法。我的观点是上面的Clojure代码应该要快得多,我想理解它为什么不是。

4 个答案:

答案 0 :(得分:2)

你基本上是在一个非常紧凑的循环中运行大量的管道,例如拳击,转换,使用chuncked懒惰序列等。你从现代cpus中获得的许多好处飞出窗外;例如预加载缓存行,分支预测等。

这种循环(计算总和)在更直接的计算形式方面要好得多,例如clojure loop构造,其形式为:

(defn get-sum [^bytes data]
  (let [m (alength data)]
    (loop [idx 0 sum 0]
      (if (< idx m)
        (recur (inc idx) (unchecked-add sum (bit-and (aget data idx) 0xff)))
        (/ sum m)))))

这是未经测试的,因此您可能需要对其进行调整,但它显示了一些内容:

  1. 使用类型提示数组访问
  2. 使用非常有效的直接循环
  3. 使用&#34;整数&#34; (长)实际循环的数学,并且只在末尾划分
  4. 使用unchecked-math可以在&#34;紧密循环中增加很多性能&#34;
  5. 修改

    你可以使用其他形式,它们可能表现得更好,例如具有内部可变状态的dotimes(比如大小为1的长向量),如果你真的需要挤出性能,但是那么,你不妨在java中写一点方法;)

答案 1 :(得分:1)

除了@ shlomi的回答:

你也可以使用areduce函数减少冗长(并且可能更快):

(defn get-sum-2 [^bytes data]
  (/ (areduce data i res 0 
              (unchecked-add res (bit-and (aget data i) 0xff)))
     (alength data)))

答案 2 :(得分:0)

如果您想在Java中快速完成,那么您可以使用这些选项(最好是使用所有这些选项):

  1. 将libjpeg-turbo的java包装器用作jpeg解压缩库 - 它比ImageIO快30倍...
  2. 不要计算图像中所有像素的平均值,使用1%     10%的像素均匀分布在图像上(使用一些散列函数来选择伪随机像素 - 或者只是跳过for循环多一个像素,具体取决于你想要击中的像素数) - 平均值     以这种方式计算要快得多。您使用的像素越多,获得的结果就越准确 - 但如果您使用5%均匀分布的选定像素,那么获得非常好的结果就足够了。
  3. 多线程。
  4. 避免使用浮点计算,使用整数计算 - 浮点计算速度最慢可达3-4倍。 在可能的情况下
  5. 不要将所有图像加载到内存中,因为图像通常会占用大量内存,因此垃圾收集器几乎无法工作且您的应用程序因此运行缓慢, 在需要时更好地加载它们,然后让它们进行GC编辑 那 - 逐步计算平均值
  6. 关于负字节值...... 不要将颜色值转换为字节,将其直接转换为int,如:

    int rgb = somePixelColor;
    int b = rgb & 0xFF;
    int g = (rgb>>8) & 0xFF;
    int r = (rgb>>16) & 0xFF;
    
    int sillyBrightness = (r + g + b)/3; // because each color should have a weight for calculating brightness, there are some models of that.
    

答案 3 :(得分:0)

除了上述好的信息之外,您可能对HipHip库感兴趣,该库专为处理来自Clojure的原始值数组而设计:https://github.com/plumatic/hiphip

这是自述文件中关于计算平均值的示例。原始数组的标准偏差:

(defn std-dev [xs]
  (let [mean (dbl/amean xs)
        square-diff-sum (dbl/asum [x xs] (Math/pow (- x mean) 2))]
    (/ square-diff-sum (dbl/alength xs))))

(defn covariance [xs ys]
  (let [ys-mean (dbl/amean ys)
        xs-mean (dbl/amean xs)
        diff-sum (dbl/asum [x xs y ys] (* (- x xs-mean) (- y ys-mean)))]
    (/ diff-sum (dec (dbl/alength xs)))))

(defn correlation [xs ys std-dev1 std-dev2]
  (/ (covariance xs ys) (* std-dev1 std-dev2)))