0

当我想组合两个计算机图形库,即 OpenCV 和 Vigra 时,我遇到了问题。我想使用 OpenCVs k-means 聚类算法进行灰度图像二值化。我的图像处理框架是较早构建的,并且强烈依赖于 Vigra,这就是我必须结合这两个库的原因。

所以基本上,我正在使用 Vigra 功能加载图像,而不是将 Vigra 对象转换为 OpenCV 矩阵,运行 k-means 聚类,将矩阵对象重新转换为 vigra 对象,最后通过使用 Vigra 功能再次保存图像。这是一个代码示例:

#include <vigra/impex.hxx>
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>

int main()
{
    
    std::string InputFilePath = "path/to/image/image_name.tif";
    
    // Vigra functionality to load an image from path
    vigra::FImage InputImg;
    const char* cFile = InputFilePath.c_str();
    vigra::ImageImportInfo info(cFile);                 
    int b = info.width();
    int h = info.height();
    InputImg.resize(b,h);
    vigra::importImage(info, destImage(InputImg));

    vigra::FImage OutputImg(InputImg.width(), InputImg.height());

    // Setting up OCV Matrix as an one channel, 32bit float grayscale image
    cv::Mat InputMat(InputImg.width(), InputImg.height(), CV_32FC1);

    // my workaround to convert vigra::FImage to cv::Mat
    for(unsigned int i=0; i<InputImg.width(); i++){
        for(unsigned int j=0; j<InputImg.height(); j++){
            InputMat.at<float>(j,i) = InputImg(i,j);
        }
    }

    // OCVs k-means clustering
    const unsigned int singleLineSize = InputMat.rows*InputMat.cols;
    const unsigned int k=2;

    cv::Mat data = InputMat.reshape(1, singleLineSize);
    std::vector<int> labels;
    data.convertTo(data, CV_32FC1);
    cv::Mat1f centers;

    cv::kmeans(data, k, labels, cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 10, 1.0), 2, cv::KMEANS_RANDOM_CENTERS, centers);

    for (unsigned int i = 0; i < singleLineSize; i++) {
        data.at<float>(i) = centers(labels[i]);
    }

    cv::Mat OutputMat = data.reshape(1, InputMat.rows);
    OutputMat.convertTo(OutputMat, CV_8UC1);

    // re-convert cv::Mat to vigra::FImage
    for(unsigned int i=0; i<InputImg.width(); i++){
        for(unsigned int j=0; j<InputImg.height(); j++){
             OutputImg(i,j) = OutputMat.at<float>(j,i);
        }
    }

    std::string SaveFileName = "path/to/save_location/save_img_name.tif";

    // vigra functionality to save the image
    const char* cFile = SaveFileName.c_str();
    vigra::ImageExportInfo exinfo(cFile);
    vigra::exportImage(srcImageRange(OutputImg), exinfo.setPixelType("FLOAT")); // pixel type could also be "UINT8"

    // for the sake of comparability
    std::string SaveFileNameOCV = "path/to/save_location/save_mat_name.tif";
    cv::imwrite(SaveFileNameOCV, OutputMat);

    return 0;
}

k-means 聚类工作正常,当我直接保存 cv::Mat 时

cv::imwrite()

万事皆安。但是当我将 cv::Mat 重新转换为 vigra::FImage 对象并保存时,图像已损坏。看起来好像对象(在图像中)被镜像或复制了四次,尽管图像宽度和高度保持不变。我附上了图像(InputImg、OutputImg 和 OutputMat)。此外,如果我将 InputMat 重新转换为 OutputImg(在 k-means 之后),并保存这个图像,一切都很好(这个图像也附上了)。

最后,我不明白为什么在从 vigra::FImage 转换为 cv::Mat 时必须切换索引,反之亦然:

InputMat.at<float>(j,i) = InputImg(i,j);

但如果我不这样做,则生成的图像会旋转。

好的,所以我不太确定是否有人使用 Vigra 和 OpenCV,我猜 OpenCV 肯定比 Vigra 更常见。但是无论如何,如果有人可以提供帮助,那就太好了。

顺便说一句:我在 OpenSuSE 15.1 上的 Code::Blocks 中运行所有内容。任何库都是通过官方 OpenSuSE 存储库安装的。

输入图像 输出图像 输出垫 InputMat_after_kmeans

4

1 回答 1

0

好的,首先,我没有发现,为什么会这样。但我现在知道的是,如果我使用类型定义的版本 Mat_(例如 Mat1f...),我可以正确处理所有内容,并且保存的结果符合预期。

对于转换,我写了 2 种方法:

cv::Mat1f convertImg2Mat(vigra::FImage &img){
    int b = img.width();
    int h = img.height();

    cv::Mat1f mat(h, b);

    for(unsigned int j=0; j<h; j++){
        for(unsigned int i=0; i<b; i++){
            mat(j,i) = img(i,j);
        }
    }
    return mat;
}

vigra::FImage convertMat2Img(cv::Mat mat){
    int b = mat.rows;
    int h = mat.cols;
    cv::Mat1f tmp = mat.clone();

    vigra::FImage img(h, b);

    for(unsigned int j=0; j<h; j++){
        for(unsigned int i=0; i<b; i++){
            img(i,j) = tmp(j,i);
        }
    }
    return img;
}

两者都可以正常工作。

一个愚蠢的初学者错误是索引,因为 vigra 遵循 fortran 顺序,即

img(cols, rows)

OpenCV 使用另一种约定,即

mat(rows, cols).

所以从我的角度来看,这个问题还没有得到适当的回答,但无论如何我找到了一个可行的解决方案。

于 2021-01-20T11:07:13.820 回答