我正在用Python和C++实现MATLAB的imreconstruct.然而,对于我的测试用例,Python实现与MATLAB的输出匹配,而C++则不匹配.

以下是Python的实现:

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 minumum)
    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=float32)

imReconstruct(D-.85,D)

# output
# 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++:

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;
    }
}

// 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;

// output
// [3.3926406, 3.3926406, 3.1622777, 2.7555513, 2.3122778;
//  3.3926406, 2.8284271, 2.236068, 2, 2;
//  3.1622777, 2.236068, 1.4142135, 1, 1;
//  2.7555513, 2, 1, 0, 0;
//  2.3122778, 2, 1, 0, 0]

造成这种差异的原因是什么?我可能忽略了一些简单的事情,但我已经花了几个小时徒劳,没有任何积极的结果.

推荐答案

在您所介绍的while循环体的Python和C++实现之间有一些细微但显著的差异.这些,再加上cv::Mat和许多OpenCV函数工作方式的一些细微差别,这些结合在一起就会咬你一口.

精确匹配C++实现的Python代码变体看起来像下面这样:

expanded = None
while True:
    expanded = cv.dilate(marker, kernel, expanded)
    expanded = cv.min(expanded, mask, expanded)

    _, max_val, _, _ = cv.minMaxLoc(expanded - marker)

    if max_val < 1e-5:
        return expanded

    marker = expanded

然后,此实现将遭遇与C++变体相同的问题,产生相同的结果(错误的结果).但是为什么呢?

让我们逐步了解C++代码的相关部分(while循环)并讨论发生了什么.

分析

我们从包含5x5浮点数组的cv::Mat marker开始,从cv::Mat expanded开始.

第一次迭代

我们打cv::dilate(marker, expanded, kernel);.在内部,在目的地expanded上调用cv::Mat::create.因为它是空的,所以分配了一个新的5x5浮点array.执行扩张并将结果写入expanded`S缓冲器.

下一步是expanded = cv::min(expanded, mask);.

这会调用重载cv::Mat::operator=,而"can reuse already allocated matrix if it has the right size and type to fit the matrix expression result".因此,这相当于调用cv::min,特别是cv::min(expanded, mask, expanded);. 与之前一样,cv::Mat::create在目的地expanded上被调用.这一次它已经包含了一个5x5的浮点数组,所以没有分配,缓冲区被重用.计算是就地完成的,在调用之后,expanded仍然引用同一块内存.

接下来,cv::minMaxLocexpanded - marker的结果上运行.这并不奇怪,这两者是不同的array.maxMask不低于1e-5,所以我们继续.

问题开始了

问题从循环体中的最后一条语句marker = expanded;-ashallow copy开始.这会调用cv::Mat::operator=的不同重载.

"Matrix assignment is an O(1) operation. This means that no data is copied but the data is shared and the reference counter, if any, is incremented."

这意味着在这一点之后,markerexpanded都引用相同的5x5浮点数组,引用计数为2.考虑到这一点,我们继续进行第二次迭代.

第二次迭代

我们打cv::dilate(marker, expanded, kernel);.目标已经是5x5浮点数组,没有分配,结果被写入expanded‘S缓冲区(仍与marker共享).

下一个,expanded = cv::min(expanded, mask);.目标已经是5x5浮点数组,没有分配,结果被写入expanded‘S缓冲区(仍与marker共享).

失败

最后,cv::minMaxLoc分跑expanded - marker分的结果……但这两个实际上指的是相同的数据,所以它相当于expanded - expanded,而这只是全零.因此,测试(maxMask < 1e-5)成功并且循环过早终止.


解决方案1

确保在每次迭代中分配新的expanded,就像Python版本的代码一样.只需将声明expanded移到循环体中即可.

Code:

// ...
while (true) {
    cv::Mat expanded;
    cv::dilate(marker, expanded, kernel);
    expanded = cv::min(expanded, mask);

    cv::minMaxLoc(expanded - marker, nullptr, &maxMask);
    if (maxMask < 1e-5) { return expanded; }

    marker = expanded;
}

Output:

[3.3926406, 3.3926406, 3.1622777, 3, 3;
 3.3926406, 2.8284271, 2.236068, 2, 2;
 3.1622777, 2.236068, 1.4142135, 1, 1;
 3, 2, 1, 0, 0;
 3, 2, 1, 0, 0]

溶液2

执行深层复制,而不是浅层复制.拨打cv::Mat::copyTo就可以做到这一点.

Code:

Mat expanded;
while (true) {
    cv::dilate(marker, expanded, kernel);
    expanded = cv::min(expanded, mask);

    cv::minMaxLoc(expanded - marker, nullptr, &maxMask);
    if (maxMask < 1e-5) { return expanded; }

    expanded.copyTo(marker);
}

输出与解决方案1的输出相同.

注:等效于此的Python应该是np.copyto(expanded, marker).

Python相关问答推荐

分组数据并删除重复数据

当多个值具有相同模式时返回空

Deliveryter Notebook -无法在for循环中更新matplotlib情节(保留之前的情节),也无法使用动画子功能对情节进行动画

连接两个具有不同标题的收件箱

处理(潜在)不断增长的任务队列的并行/并行方法

需要计算60,000个坐标之间的距离

无法定位元素错误404

如何使用Python以编程方式判断和检索Angular网站的动态内容?

用砂箱开发Web统计分析

如何在达到end_time时自动将状态字段从1更改为0

numpy.unique如何消除重复列?

pysnmp—lextudio使用next()和getCmd()生成器导致TypeError:tuple对象不是迭代器''

在Google Drive中获取特定文件夹内的FolderID和文件夹名称

如何按row_id/row_number过滤数据帧

如何使用Azure Function将xlsb转换为xlsx?

Python如何导入类的实例

为什么后跟inplace方法的`.rename(Columns={';b';:';b';},Copy=False)`没有更新原始数据帧?

如何根据一定条件生成段id

如何从数据框列中提取特定部分并将该值填充到其他列中?

TypeError:';Locator';对象无法在PlayWriter中使用.first()调用