Finding the mean value of subsets of an array

Hello,

I am trying to take the mean value of a grid of subregions of an image. The images are from a video, hence the using VideoIO. My question does not actually depend on video-reading functionality

using VideoIO
function analyzeSubregions(filename,rows,cols)
    vid = openvideo(filename) # this function is provided by VideoIO package
    frames = 2000 # this is the number of frames in the video

    data = zeros(length(rows),length(cols),frames) # initialize an empty vector
    k = 0
    while !eof(vid)
        k += 1
        frame = floor.(256 .* Float64.(Gray.(read(vid))))
        data[:,:,k] = frame[rows,cols]
    end
    # some processing of "data" ...
end

In the rows and cols arguments to this function, I pass ranges, e.g.

analyzeSubregions("example.avi",200:25:300,1000:100:1500)

this creates a matrix of size length(200:25:300) x length(1000:100:1500) x frames, giving me a “grid” of pixels sampled over the length of the video. So far, so good!

Now, is there a good way to extend this to a grid of sub-arrays?

For example, I would like to use the same grid spacing as above, but with mxn pixels at each location. so instead of producing an array of 5 x 6 values at the k’th slice:

5Ă—6 Array{Float64,2}:
 0.75848   0.689624  0.759477  0.13272   0.071992   0.922893
 0.499832  0.739883  0.676422  0.224262  0.13593    0.941888
 0.641937  0.842677  0.624592  0.834348  0.0732766  0.17342
 0.177902  0.128434  0.176731  0.85235   0.533129   0.886172
 0.67728   0.450755  0.92391   0.317936  0.286406   0.330416

I would like to get something like this at the k’th slice

5Ă—6 Array{??,2}:
 [m x n]   [m x n]   [m x n]   [m x n]   [m x n]   [m x n]
 [m x n]   [m x n]   [m x n]   [m x n]   [m x n]   [m x n]
 [m x n]   [m x n]   [m x n]   [m x n]   [m x n]   [m x n]
 [m x n]   [m x n]   [m x n]   [m x n]   [m x n]   [m x n]
 [m x n]   [m x n]   [m x n]   [m x n]   [m x n]   [m x n]

Of course, m and n would both have to be sufficiently small integers, say, between 2 and 5.

One more thing – I am not actually interested in the entire sub-arrays of size m x n – I’m actually only interested in the mean value of each sub-array.

  1. Is there a good, Julia-idiomatic way of extracting a grid of sub-arrays of pixels at the k’th slice, similar to how I extract a grid of pixel values? How would I initialize the data array in this case?
  2. Further, I need to take the mean of each of those arrays of pixels – so is there a way to extract the mean of the above without needing to allocate an array of arrays?

Thanks for any advice you can offer!

Could you do something like (pseudocode):

... 
while !eof(vid)
   ...
  for (i, row) in enumerate(rows)
    row_range = row .+ (-div(m, 2):div(m, 2))  # assuming m is even; will need to be tweaked if it's odd
    for (j, col) in enumerate(cols)
      col_range = col .+ (-div(n, 2):div(n, 2))
      sub_region = @view frame[row_range, col_range]
      data[i, j, k] = mean(sub_region)
    end
  end
end

That probably isn’t quite right, but hopefully it helps make a couple of useful points:

  1. Use @view or view to efficiently refer to a sub-part of an array without allocating a brand new copy of that part of the array
  2. Rather than constructing the array of sub-arrays, just compute the mean of each sub-array as you go. There’s no need to store them all at once, so you can make your code simpler and faster by operating on one sub-array at a time.
1 Like

This is really helpful, actually! It does the job essentially verbatim. I particularly like your row .+ (-div(m,2):div(m,2)) idea – worth restricting my m’s and n’s to even numbers.

One question – how exactly does sub_region = @view frame[row_range, col_range] differ from plain old sub_region = frame[row_range, col_range] ?

Doing frame[row_range, col_range] will create a brand new array and copy the contents of that range, which is fairly expensive. @view frame[row_range, col_range] on the other hand just creates a lightweight reference to that section of the original array without copying any of its data.

julia> A = zeros(3, 3)
3Ă—3 Array{Float64,2}:
 0.0  0.0  0.0
 0.0  0.0  0.0
 0.0  0.0  0.0

# B is a *copy* of the data in this region of A
julia> B = A[1:2, 1:2]
2Ă—2 Array{Float64,2}:
 0.0  0.0
 0.0  0.0

# Since B is a copy, changing B does not affect A
julia> B .= 1
2Ă—2 Array{Float64,2}:
 1.0  1.0
 1.0  1.0

julia> A
3Ă—3 Array{Float64,2}:
 0.0  0.0  0.0
 0.0  0.0  0.0
 0.0  0.0  0.0

# C is a view into the *same* data as A
julia> C = @view A[1:2, 1:2]
2Ă—2 view(::Array{Float64,2}, 1:2, 1:2) with eltype Float64:
 0.0  0.0
 0.0  0.0

# Changing C affects A:
julia> C .= 2
2Ă—2 view(::Array{Float64,2}, 1:2, 1:2) with eltype Float64:
 2.0  2.0
 2.0  2.0

julia> A
3Ă—3 Array{Float64,2}:
 2.0  2.0  0.0
 2.0  2.0  0.0
 0.0  0.0  0.0

In your case, the fact that modifying the view affects the original doesn’t matter (since you aren’t modifying either). But the fact that creating a view avoids needing to make a copy of all the data means that your code will avoid wasting time on unnecessary data copies.

1 Like

Got it, thanks! I’ll be implementing this batch-wise on a bunch of files, so any performance improvements / decreases in allocation are pretty useful for me.