OpenCV Python与C++版imreconstruct实现结果不一致排查
MATLAB
imreconstruct Python与C++实现结果不一致的原因 我用Python和C分别实现了MATLAB的imreconstruct函数,测试发现Python版本输出与MATLAB一致,但C版本结果不符。测试案例中,Python输出的第一行第四、五列和第四、五行第一列保持mask的原始值3,而C++版本中这些位置的值被错误修改为2.7555513、2.3122778等。
Python实现代码
import numpy as np import cv2 as cv def imReconstruct(marker: np.array, mask: np.array) -> np.array: """ Naive implementation of MatLAB's imReconstruct function works when `mask` consists of mostly background (global minimum) will be slow otherwise """ kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3)) # calculate the extreme values of the mask image min_val, max_val, _, _ = cv.minMaxLoc(mask) # clip the marker by global extrema of mask _, marker = cv.threshold(marker, min_val, max_val, cv.THRESH_TRUNC | cv.THRESH_BINARY_INV) while True: expanded = cv.dilate(marker, kernel) expanded = np.minimum(expanded, mask) # return `expanded` when the difference is small if np.max(np.abs(expanded - marker)) < 1e-5: return expanded # set expanded to marker and repeat marker = expanded D = np.array( [[4.2426405, 3.6055512, 3.1622777, 3. , 3. ], [3.6055512, 2.828427 , 2.236068 , 2. , 2. ], [3.1622777, 2.236068 , 1.4142135, 1. , 1. ], [3. , 2. , 1. , 0. , 0. ], [3. , 2. , 1. , 0. , 0. ]], dtype=np.float32) # 输出结果 print(imReconstruct(D-.85,D)) # array([[3.3926406, 3.3926406, 3.1622777, 3. , 3. ], # [3.3926406, 2.828427 , 2.236068 , 2. , 2. ], # [3.1622777, 2.236068 , 1.4142135, 1. , 1. ], # [3. , 2. , 1. , 0. , 0. ], # [3. , 2. , 1. , 0. , 0. ]], dtype=float32)
C++实现代码
#include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace std; Mat imReconstruct(Mat marker, Mat mask){ /************** * naive implementation of MatLAB's imReconstruct * works when `mask` consist of mostly background (global minimum) * will be slow otherwise *************/ Mat kernel = getStructuringElement(MORPH_RECT, Size(3,3)); // calculate the min and max values from mask double minMask, maxMask; minMaxLoc(mask, &minMask, &maxMask); // clip the marker by global extrema of mask threshold(marker, marker, minMask, maxMask, THRESH_TRUNC|THRESH_BINARY_INV); Mat expanded; // keep filling the holes with `dilate` // until there are no more changes while (1){ dilate(marker, expanded, kernel); expanded = min(expanded, mask); // compute the max difference minMaxLoc(expanded-marker, &minMask, &maxMask); // return image when changes are small if (maxMask<1e-5) return expanded; // set expanded as marker and continue looping marker = expanded; } } int main() { // test case cv::Mat D = (cv::Mat_<float> (5,5) << 4.2426405, 3.6055512, 3.1622777, 3. , 3. , 3.6055512, 2.828427 , 2.236068 , 2. , 2. , 3.1622777, 2.236068 , 1.4142135, 1. , 1. , 3. , 2. , 1. , 0. , 0. , 3. , 2. , 1. , 0. , 0. ); std::cout << imReconstruct(D - .85, D) << std::endl; // 输出结果 // [3.3926406, 3.3926406, 3.1622777, 2.7555513, 2.236068; // 3.3926406, 2.8284271, 2.236068, 2, 2; // 3.1622777, 2.236068, 1.4142135, 1, 1; // 2.7555513, 2, 1, 0, 0; // 2.236068, 2, 1, 0, 0] return 0; }
核心差异原因
1. 阈值操作的非法组合与行为差异
OpenCV的threshold函数的阈值类型(如THRESH_TRUNC、THRESH_BINARY_INV)是互斥的,不能同时指定多个。你试图用THRESH_TRUNC | THRESH_BINARY_INV实现marker的裁剪(限制在mask的min和max之间),但这个组合属于未定义行为:
- Python版本的OpenCV可能忽略了无效的flags组合,仅执行了
THRESH_TRUNC,或巧合得到了类似裁剪的效果; - C++版本的OpenCV对该组合的处理逻辑不同,导致初始marker的数值被错误修改,后续循环的膨胀和取min操作自然偏离预期。
正确的裁剪方式:
- Python中替换为
marker = np.clip(marker, min_val, max_val); - C++中替换为:
marker.setTo(minMask, marker < minMask); marker.setTo(maxMask, marker > maxMask);
2. 循环终止条件的逻辑不一致
- Python中计算的是
np.max(np.abs(expanded - marker)),即差值绝对值的最大值,确保所有像素的变化都小于阈值; - C++中通过
minMaxLoc(expanded - marker, &minMask, &maxMask)仅获取了差值的最大值(正差值),忽略了负差值的绝对值可能超过1e-5的情况,导致循环提前终止,未完成完整的重建过程。
修正方式:C++中先对差值取绝对值,再计算最大值:
Mat diff; absdiff(expanded, marker, diff); minMaxLoc(diff, &minMask, &maxMask); if (maxMask < 1e-5) return expanded;
内容的提问来源于stack exchange,提问作者Quang Hoang
相关产品推荐
相关产品推荐

