mirror of
https://github.com/opencv/opencv.git
synced 2026-07-21 19:33:03 +04:00
Merge pull request #28747 from LHOOL1109:fix/python-zero-channel-crash
python: fix segfault on 0-channel numpy array input #28747 ### Pull Request Readiness Checklist - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable N/A: a Python unit test is added in `modules/python/test/test_mat.py`. No external test data required. - [x] The feature is well documented and sample code can be built with the project CMake N/A: this is a bug fix, not a new feature. No documentation update needed. ### Problem Passing a numpy array with shape `(H, W, 0)` (0 channels) to any OpenCV function that accepts a `Mat` argument (e.g. `cv2.resize`, `cv2.warpAffine`, `cv2.blur`) causes a **segfault**. **Reproducer:** ```python import cv2 import numpy as np arr = np.zeros((100, 100, 0), np.uint8) cv2.resize(arr, (200, 200)) # segfault ``` ### Root Cause In `modules/python/src2/cv2_convert.cpp`, the numpy→Mat conversion checks channel validity only against `CV_CN_MAX` (upper bound): ```cpp if (channels > CV_CN_MAX) // channels=0 passes this check ``` With `channels=0`, `CV_MAKETYPE(0, 0)` produces `type=-8`, which corrupts the Mat's internal type field and causes undefined behavior downstream. ### Fix Extend the check to also reject `channels < 1`: ```cpp if (channels < 1 || channels > CV_CN_MAX) ``` **After fix:** ``` cv2.error: src unable to wrap channels, invalid count (0, must be in [1, 512]) ``` ### Notes - This affects all functions that accept a `Mat` input, not just `cv2.resize` - The same bug exists in the `5.x` branch - A 0-channel array has no valid OpenCV Mat representation; rejecting it with a clear error is the correct behavior and poses no backward-compatibility risk (the previous behavior was a crash)
This commit is contained in:
@@ -206,9 +206,9 @@ bool pyopencv_to(PyObject* o, Mat& m, const ArgInfo& info)
|
||||
if (ismultichannel)
|
||||
{
|
||||
int channels = ndims >= 1 ? (int)_sizes[ndims - 1] : 1;
|
||||
if (channels > CV_CN_MAX)
|
||||
if (channels < 1 || channels > CV_CN_MAX)
|
||||
{
|
||||
failmsg("%s unable to wrap channels, too high (%d > CV_CN_MAX=%d)", info.name, (int)channels, (int)CV_CN_MAX);
|
||||
failmsg("%s unable to wrap channels, invalid count (%d, must be in [1, %d])", info.name, (int)channels, (int)CV_CN_MAX);
|
||||
return false;
|
||||
}
|
||||
ndims--;
|
||||
|
||||
@@ -89,6 +89,23 @@ try:
|
||||
print(res1)
|
||||
|
||||
|
||||
def test_mat_wrap_channels_zero(self):
|
||||
# Passing a 0-channel array must raise cv.error, not segfault.
|
||||
data = np.zeros((100, 100, 0), dtype=np.uint8)
|
||||
|
||||
with self.assertRaises(cv.error):
|
||||
cv.resize(data, (200, 200)) # channels=0 -> invalid, must not segfault
|
||||
|
||||
with self.assertRaises(cv.error):
|
||||
mat_data = cv.Mat(data, wrap_channels=True) # unable to wrap channels, invalid count (0)
|
||||
cv.utils.dumpInputArray(mat_data)
|
||||
|
||||
# Verify that channels=1 (lower bound) still works correctly
|
||||
data_1ch = np.zeros((100, 100, 1), dtype=np.uint8)
|
||||
mat_1ch = cv.Mat(data_1ch, wrap_channels=True)
|
||||
res = cv.utils.dumpInputArray(mat_1ch)
|
||||
self.assertIn("CV_8UC1", res)
|
||||
|
||||
def test_ufuncs(self):
|
||||
data = np.arange(10)
|
||||
mat_data = cv.Mat(data)
|
||||
|
||||
Reference in New Issue
Block a user