2010-07-17 33 views
9

Tôi đang cố gắng tính màu trung bình của hình ảnh trong Scala, trong đó "trung bình" được định nghĩa là redSum/numpixels, greenSum/numpixels, blueSum/numpixels.Scala - cách thành ngữ để tính tổng các mảng xen kẽ?

Đây là mã tôi đang sử dụng để tính màu trung bình trong vùng hình chữ nhật của hình ảnh (Raster).

// A raster is an abstraction of a piece of an image and the underlying 
// pixel data. 
// For instance, we can get a raster than is of the upper left twenty 
// pixel square of an image 
def calculateColorFromRaster(raster:Raster): Color = { 
    var redSum = 0 
    var greenSum = 0 
    var blueSum = 0 

    val minX = raster.getMinX() 
    val minY = raster.getMinY() 

    val height = raster.getHeight() 
    val width = raster.getWidth() 
    val numPixels = height * width 

    val numChannels = raster.getNumBands() 

    val pixelBuffer = new Array[Int](width*height*numChannels) 
    val pixels = raster.getPixels(minX,minY,width,height,pixelBuffer) 

    // pixelBuffer now filled with r1,g1,b1,r2,g2,b2,... 
    // If there's an alpha channel, it will be r1,g1,b1,a1,r2,... but we skip the alpha 
    for (i <- 0 until numPixels) { 
    val redOffset = numChannels * i 
    val red = pixels(redOffset) 
    val green = pixels(redOffset+1) 
    val blue = pixels(redOffset+2) 

    redSum+=red 
    greenSum+=green 
    blueSum+=blue 
    } 
    new Color(redSum/numPixels, greenSum/numPixels, blueSum/numPixels) 
} 

Có cách Scala thành ngữ tổng hợp hơn các mảng xen kẽ khác nhau không? Một số cách để có được một phép chiếu trên mảng lặp qua tất cả các phần tử thứ 4? Tôi quan tâm đến bất kỳ chuyên môn nào mà cộng đồng Stack Overflow có thể cung cấp.

Trả lời

10

pixels.grouped(3) sẽ trả lại Iterator[Array[Int]] của mảng 3 phần tử. Vì vậy,

val pixelRGBs = pixels.grouped(3) 

val (redSum, greenSum, blueSum) = 
    pixelRGBs.foldLeft((0, 0, 0)) {case ((rSum, gSum, bSum), Array(r, g, b)) => (rSum + r, gSum + g, bSum + b)} 

new Color(redSum/numPixels, greenSum/numPixels, blueSum/numPixels) 

UPDATE: Để đối phó với cả hai kênh 3 và 4, tôi sẽ viết

pixels.grouped(numChannels).foldLeft((0, 0, 0)) {case ((rSum, gSum, bSum), Array(r, g, b, _*)) => (rSum + r, gSum + g, bSum + b)} 

_* đây về cơ bản có nghĩa là "0 hoặc nhiều yếu tố". Xem "So khớp theo chuỗi" trong http://programming-scala.labs.oreilly.com/ch03.html

+0

Perfect câu trả lời. Câu trả lời này cũng giúp tôi hiểu rõ hơn cách foldLeft hoạt động. Việc sử dụng các tuyên bố trường hợp cũng làm cho nó rất dễ đọc. Bravo – I82Much

+0

Cách tốt nhất để xử lý thực tế là giá trị được nhóm không phải lúc nào cũng là 3, nhưng thay vào đó bằng số kênh, có thể là 3 hoặc 4 tùy thuộc vào giá trị alpha? Tôi luôn luôn muốn 0,1,2 chỉ số, nhưng trong trường hợp đó tôi tưởng tượng tôi không thể làm một trường hợp Array (r, g, b) nữa vì tôi không luôn luôn có một mảng 3 phần tử. pixel.giao diện (numChannels) .foldLeft ((0,0,0)) {case ((rsum, gsum, bsum), màu sắc: Array [Int]) => (rsum + colors (0), gsum + colors (1), bsum + colors (2))} – I82Much

+0

@Sandor: Tôi không thấy lý do tại sao bạn muốn kết quả tích lũy (tức là tổng chỉ trên các pixel trong phần đầu). –

6

Đây là quá mức cần thiết cho vấn đề này, nhưng tôi đã giảm nhiều phân đoạn trên tập dữ liệu và đã xây dựng một số chức năng tiện ích cho nó. Nói chung nhất là reduceBy, một bộ sưu tập (thực sự là Traversable), một hàm phân vùng, hàm ánh xạ và chức năng giảm và tạo bản đồ từ các phân vùng đến các giá trị được rút/ánh xạ.

def reduceBy[A, B, C](t: Traversable[A], f: A => B, g: A => C, reducer: (C, C) => C): Map[B, C] = { 
    def reduceInto(map: Map[B, C], key: B, value: C): Map[B, C] = 
     if (map.contains(key)) { 
     map + (key -> reducer(map(key), value)) 
     } 
     else { 
     map + (key -> value) 
     } 
    t.foldLeft(Map.empty[B, C])((m, x) => reduceInto(m, f(x), g(x))) 
    } 

Cho rằng máy móc hạng nặng, vấn đề của bạn trở nên

val sumByColor:Map[Int, Int] = reduceBy(1 until numPixels, (i => i%numChannels), (i=>pixel(i)), (_+_)) 
return Color(sumByColor(0)/numPixels, sumByColor(1)/numPixels, sumByColor(2)/numPixels) 

Đứng câm trước sức mạnh tuyệt vời của lập trình bậc cao.

2

Đây là một câu hỏi hay, vì tôi nghĩ giải pháp bạn đã cung cấp là giải pháp thành ngữ! Mô hình bắt buộc thực sự phù hợp với vấn đề này. Tôi đã cố gắng tìm một giải pháp chức năng đơn giản đọc tốt, nhưng tôi không thể làm được.

Tôi nghĩ người có pixel.grouped (3) khá tốt, nhưng tôi không chắc nó tốt hơn cái bạn có.

"không bắt buộc" My own giải pháp liên quan đến việc xác định một lớp hợp với tử +/Phương pháp:

import java.awt.image.Raster 
import java.awt.Color 

def calculateColorFromRaster(raster:Raster): Color = { 
    val minX = raster.getMinX() 
    val minY = raster.getMinY() 

    val height = raster.getHeight() 
    val width = raster.getWidth() 
    val numPixels = height * width 

    val numChannels = raster.getNumBands() 

    val pixelBuffer = new Array[Int](width*height*numChannels) 
    val pixels = raster.getPixels(minX,minY,width,height,pixelBuffer) 

    // pixelBuffer now filled with r1,g1,b1,r2,g2,b2,... 
    // If there's an alpha channel, it will be r1,g1,b1,a1,r2,... but we skip the alpha 

    // This case class is only used to sum the pixels, a real waste of CPU! 
    case class MyPixelSum(r: Int, g: Int, b: Int){ 
    def +(sum: MyPixelSum) = MyPixelSum(sum.r +r, sum.g + g, sum.b + b) 
    } 

    val pixSumSeq= 0 until numPixels map((i: Int) => { 
    val redOffset = numChannels * i 
    MyPixelSum(pixels(redOffset), pixels(redOffset+1),pixels(redOffset+2)) 
    }) 
    val s = pixSumSeq.reduceLeft(_ + _) 

    new Color(s.r/numPixels, s.g/numPixels, s.b/numPixels) 
} 
Các vấn đề liên quan