pythonnumpyopencvinteger-overflowsaturation-arithmetic

How to clip to max an integer overflow using numpy or opencv


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.


Solution

  • 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.