3

I have an array of the form a = np.array([1], dtype='uint8').
Now if I add 255 to a it will overflow and be np.array([0]).

Is there a built-in way to "clip" to value to 255?

NumPy has the function np.clip but it doesn't works under overflow condition.

4 Answers 4

2

Numpy has np.clip(), taking a single input, so you'd have to ensure that the preceding addition hasn't already overflowed. You see what's needed in the other answers (widening).

OpenCV's math operation cv.add() (and others) has implemented saturating arithmetic. Just give it the arrays.

With OpenCV, you have to pay attention to the input shapes. The Python bindings turn the numpy input into either a cv::Mat (if 2/3-dimensional shape) or a cv::Scalar (if 1-dimensional shape). If both become cv::Mats then their shapes have to match. If both arguments become cv::Scalars, the result is also a cv::Scalar, and the arithmetic semantics change in that case. You don't want those semantics (always 4-length, elements are wider than uint8). Zero-dimensional arrays (np.array(42)) are only accepted if not both arguments are such a thing. The semantics are even more complex than that, but this shall be enough for now.

Stick with at least one argument becoming a cv::Mat.

a = np.array([[1]], dtype=np.uint8) # 2-d => cv::Mat

res = cv.add(a, 255) # array([[255]], dtype=uint8)

If both are cv::Mats, the shapes have to match, or else you get an exception:

>>> cv.add(np.uint8([[255, 254, 253]]), np.uint8([[1]]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
cv2.error: OpenCV(4.11.0) D:\a\opencv-python\opencv-python\opencv\modules\core\src\arithm.cpp:667: error: (-215:Assertion failed) type2 == CV_64F && (sz2.height == 1 || sz2.height == 4) in function 'cv::arithm_op'

Here I made one be a cv::Scalar, which is broadcast (by OpenCV) over the other input, which is a cv::Mat.

>>> cv.add(np.uint8([[255, 254, 253]]), np.uint8([1]))
array([[255, 255, 254]], dtype=uint8)

There is more to how OpenCV does its broadcasting and how cv::Scalars work. I think this shall suffice for now.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks a lot, now I got it! I was doing a toy example and didn't enough care on the input data!
... but now still curious on what is behind the "[...]this shall be enough for now.":)
this goes into corners I use so rarely that I haven't formed a thorough mental model of it. Mats are 2D images, having 1-512 channels of color data. Scalars are basically 4-element vectors, or "pixels" for most purposes. the element type of a Scalar is a template. usually if an argument is just a numerical value, it might be promoted to Scalar. in some functions there is extra logic to make this behave differently. it's complicated... uninitialized elements of a Scalar are 0. if you added a Scalar(1) to an image, that won't add 1 to every channel, only to the first.
1

You can use opencv cv2.add() to add a value to an ndarray while clamping to the relevant element value (0..255 for uint8).

There is a specific note about the difference between opencv and numpy in this case:

Note
There is a difference between OpenCV addition and Numpy addition. OpenCV addition is a saturated operation while Numpy addition is a modulo operation.

(emphasis is mine)

Saturated operation means the values are clamped, unlike numpy that uses a modulo operation which means the values are wrapped around.

Similar methods exists for other arithmetic operations (see the link above).

However note that opencv create a 2D array, so I used numpy's reshape to make it a 1D array array.
I assume that in your real case your array is not a 1D, 1 element array and so you might need to change the reshape() call or drop it altogether.

import cv2 as cv2
import numpy as np

a = np.array([1], dtype='uint8')
a[0] = 5
print(a)
a = cv2.add(a, 255).reshape(1)
print(a)

Output:

[5]
[255]

Note:
This works with opencv 4.5.5.
As you can see in the comments below it does not longer work with newer opencv versions (e.g. 4.10) due to a change in the library that treats a numpy 1D array with 1 element like here as a cv::Scalar, and not a cv::Mat.

12 Comments

I got the following error ValueError: cannot reshape array of size 4 into shape (1,) with numpy2.3.1 and openCV4.11.0
I am using older versions of the libraries. Can you check what is the shape of the array before and after calling cv2.add() (but before the reshape) ? You can use print(a.shape) for that.
before (1,), after (4, 1) and the final output is [[260.] [255.] [255.] [255.]]
It seem like opencv messes with the shape. Not sure why. I guess you can always take a sub-array from the result of cv2.add(). But I assume in your real code the array has a shape bigger than (1,) isn't it ?
Strange result, but please add the version of the libraries to your answer it is always valuable. Yes, I am working with images
It looks like a bug in opencv. E.g. This is the documentation for opencv 4.10. But the code example does not give the output mentioned in the documentation (the array is rehspaed/grown to (4,1)), whereas in the documentation it does not grow. See demo.
this behavior has been explained before: stackoverflow.com/questions/77897622/…
Interesring. However in your answer there you suggested to make sure the shape is set correctly. But as you can see here the result of add has the wrong shape even if both input arrays have the same and proper shape.
|
1

There was an answer, now deleted, based on casting. Since NumPy does not provides any saturated arithmetic operations a method based on explicit data type casting & clipping still does the job.

"Automatic" saturation with respect to boundaries of the data type with NumPy

a = np.array([1], dtype='uint8')

a_new = np.clip(
        a.astype(int) + 255,
        a_min=np.iinfo(a.dtype).min,
        a_max=np.iinfo(a.dtype).max
    ).astype(a.dtype)


print(a_new)
#255

Comments

0

Since a is of type uint8 (range 0-255), adding 255 to a = [1] would normally cause an overflow (resulting in 0 due to modulo-256 arithmetic). To prevent this and instead clip the value to stay within 0-255, you can use np.clip() with explicit a_min and a_max bounds, and store the result back in a using the out parameter.

import numpy as np

a = np.array([1], dtype='uint8')
np.clip(a + 255, a_min=0, a_max=255, out=a)  
print(a)  # Output: [0]

Explanation:

  1. a + 255 would normally compute 256, but since a is uint8, it overflows to 0.

  2. np.clip(..., a_min=0, a_max=255, out=a) forces the result to stay within bounds before storing it back in a.

  3. out=a writes the result directly into a (memory-efficient).

4 Comments

thanks for remind me the out parameter but I want 255 at the end, not 0
I think np.clip(a.astype('uint16') + 255, a_min=0, a_max=255, out=a).astype('uint16') should work to return 255 instead of 0.
I agree but that's the casting based way of Sakshi Sharma's answer
this answer does not answer the question. it does not clip/saturate. it only overflows. the clip operation is pointless because it's already being given the value of a + 255 as an input value, where the overflow already occurred.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.