-1
$\begingroup$

I have a 1920x1440 jpg image. It has been binarized. I want to sample the values at an nxm grid of points say 50 points x 40 points. What is the most computationally efficient way to do this? Thanks.

$\endgroup$
19
  • 3
    $\begingroup$ img[[;; ;; 50, ;; ;; 40]] $\endgroup$
    – Domen
    Aug 30, 2021 at 21:32
  • 1
    $\begingroup$ Domen's answer should work, although you might need ImageData[img][[;; ;; 50, ;; ;; 40]]. This should give you back exactly what you need. $\endgroup$
    – Carl Lange
    Aug 30, 2021 at 21:59
  • 1
    $\begingroup$ Ummm... why do you care about computational efficiency? Everything is SOOO fast you can apply this to every image on your computer in less time it takes to post your question and get an answer. $\endgroup$ Aug 30, 2021 at 22:21
  • 1
    $\begingroup$ Processing tens of thousands of images on a Raspberry Pi?!? That's what you'd need to see any difference in speed between the slowest and the fastest methods. 0.0015 versus 0.0017. So 0.0002 seconds. If you do 500 such calls, the difference is less than 1 second. I ask again: who gives a damn about efficiency here? (Let's hear from the OP.) $\endgroup$ Aug 30, 2021 at 23:03
  • 1
    $\begingroup$ Hmm, not quite—Downsample[img, {n,k}] will extract a pixel every n pixels horizontally and k pixels vertically. So for instance, if img is 10x10, DownSample[img, {5,5}] will give you a 2x2 image. Likewise DownSample[img, {1,1}] will give you back img instead of a 1x1 image. Is that what you want? $\endgroup$
    – thorimur
    Sep 2, 2021 at 21:25

2 Answers 2

2
$\begingroup$

If you want an nxm subset of pixels of a 1920x1440 image, you can try doing Downsample[img, {Floor[1920/n], Floor[1440/m]}]—but this doesn't guarantee an n x m grid will result, due to the floor function being used.

Let's assume you have a list of images imgs which are all known to be the same dimensions.

There are two ways: ImageResize[#, {n, m}, Resampling -> "NearestLeft"] & /@ imgs, or a custom function. If we go the custom function route, we can exploit the fact that we have the same dimensions for each image:

ImageSample[imgs_List, {x_Integer, y_Integer}] :=
 With[{img1 = First[imgs]},
  With[{i = ImageDimensions[img1], 
        type = ImageType[img1],
        info = Sequence @@ FilterRules[Normal@First@Information[img1], Options[Image]]}, 
   With[{xparts = Floor@Rescale[Range[x], {1, x}, {1, First[i]}], 
         yparts = Floor@Rescale[Range[y], {1, y}, {1, Last[i]}]}, 
    Image[#, type, info] & /@
     ((ImageData[#, type] & /@ imgs)[[All, yparts, xparts]])
   ]
  ]
 ]

Note that this function is not "safe" and doesn't check that all image dimensions are the same (or even that the list is made of all images). If you want to do that, you can change imgs_List to imgs : {___Image} and append /; Equal @@ (ImageDimensions /@ imgs) to the definition.

This is guaranteed to get you an xxy-sized grid of pixels, if that's essential for you.

Surprisingly, this can be an order of magnitude faster than the built-in ImageResize, depending on the parameters x,y, if you really need it. It's best to time it yourself and see how it goes, though. The speed difference might also be negligible, depending on the dimensions and the number of frames. It can also be much slower sometimes, so watch out!

Note what we're doing with the ranges, as well: we're scaling them so that the first and last pixels of the sampling grid are right in the corners. You can fiddle with the last argument of Rescale, which tells you the range to rescale it to, if you want. You can also replace Floor with Round or Ceiling. Note that ImageResize chooses a slightly different convention: it apparently tries to sample from the center of rectangles that are i[[1]]/x wide by i[[2]]/y high. (Check out ImageResize[Image[Array[{#1, #2, 0} &, {100, 100}], "Byte"], {5, 5}, Resampling -> "NearestLeft"] to see it sampling from the middle of 20x20 squares). This could be replicated here with a small adjustment to the formula.

It also converts all the ImageData back to Images. If you don't need that, and are happy with the array, simply remove info = ... and Image[#, info] & /@.


The above assumes you're trying to get exactly an nxm grid of points, and that being evenly spaced in "real" space is more important than being evenly spaced in pixel values. It also assumes you want to avoid sampling close to one edge but not the opposite one; one downside of using Downsample or simply ImageData[img][[ ;; ;; m, ;; ;; n]] is that you'll introduce edge artifacts or not get quite an n by m grid, but they are far faster. There are a couple of ways to modify them to be better:

To deal with edge artifacts:

  1. Approximate solution: Calculate an appropriate offset for the sampling, so that the sampled pixels are approximately "centered"

  2. More exact solution: Restrict the allowed downsampling spacing: for sampling right up to the edges, only use divisors of (effective dimension) - 1; for sampling in "pixel block centers", only use divisors of (effective dimension). For this, we can crop our image dimensions slightly from (dimension) to (effective dimension), which might have better divisors. (what if (dimension) is prime?)

There are also other ways we could sample the image: randomly, from a uniform distribution on locations; randomly within each pixel block; according to different rigid patterns; etc.

I'll come back to this to see if I have anything explicit to add! :)

Hope this helps, let me know if you have any questions!

$\endgroup$
1
  • 1
    $\begingroup$ Yes, this is all very helpful and informational and will help a lot during the investigation. The part about getting exactly n x m samples is also not very critical as what I am attempting to do is to get a sufficiently distributed set of samples from all over the image. However as I am new to Mathematica syntax all of this is helpful if/when I need to use these idioms, which is likely to be sooner rather than later.. $\endgroup$
    – Nitin
    Sep 2, 2021 at 23:35
0
$\begingroup$

I think Downsample[img, {50, 40}] does it. Not sure if it's the most efficient.

$\endgroup$
4
  • 3
    $\begingroup$ That should be the most efficient! in general, built-ins win. contrast ImageData[img][[;; ;; 40, ;; ;; 50]] // Image // RepeatedTiming with Downsample[img, {50, 40}] // RepeatedTiming. (Note that image dimensions are in reverse order to their ImageData array dimensions, hence the flip of 40 and 50.) $\endgroup$
    – thorimur
    Aug 30, 2021 at 22:13
  • $\begingroup$ Thanks, so much! $\endgroup$
    – Nitin
    Sep 2, 2021 at 20:23
  • 1
    $\begingroup$ Weirdly enough, I'm trying it again now, and Downsample is losing?? I could've sworn when i tried it the first time it won! Maybe it did, with different parameters? The lesson, I guess, is to time it yourself with your parameters, and then use the best choice! $\endgroup$
    – thorimur
    Sep 2, 2021 at 21:41
  • 1
    $\begingroup$ (Note that if you have a list of images imgs all of the same dimension, you can do Image /@ (ImageData /@ imgs)[[All, ;; ;; 40, ;; ;; 50]].) $\endgroup$
    – thorimur
    Sep 2, 2021 at 21:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.