将大图像切割成10x10图像的有效方法(opencv)

时间:2017-07-20 21:00:14

标签: c++ performance opencv

我有一个大的2400x1300 png文件,我想要切成10x10像素有序图像并将它们保存到一个单独的文件中。我找到了多种方法,但我不确定哪种方法对于这个大文件大小最有效。绩效是一个关键因素。

以下是对我来说最有希望的解决方案之一:

var deferred = _$q_.defer();
spyOn($q, 'all').and.returnValue(deferred.promise);

var results = "FAIL"
deferred.resolve(results);

$q.all().then(function(a){
    expect(a).toBe("PASS") // -- this doesn't work
    expect(a).toBe("FAIL") // -- this does work but is not what we want to test;
});

}

1 个答案:

答案 0 :(得分:3)

由于您正在使用C ++,让我们切换到C ++ OpenCV API以简化操作(如果您真的需要,可以随意将其移植到旧的C API中。)

复制或不复制,这里的瓶颈将是编码并将图像写入磁盘。

最简单的方法是计算给定图块的边界矩形,然后使用cv::Mat::operator(...)以O(1)成本获得子区域(ROI)(不复制)。然后使用适当的文件名调用cv::imwrite进行保存(我将使用boost::format生成文件名,但您也可以使用std::stringstream

示例代码:

#include <opencv2/opencv.hpp>

#include <boost/format.hpp>

#include <cstdint>
#include <chrono>
#include <iostream>

void process_tile(cv::Mat const& src
    , cv::Size const& roi_size
    , int32_t tile_col
    , int32_t tile_row)
{
    cv::Rect roi_bounds(tile_col * roi_size.width
        , tile_row * roi_size.height
        , roi_size.width
        , roi_size.height);
    cv::Mat roi(src(roi_bounds));

    std::string file_name(str(boost::format("tiles/tile_%03d_%03d.png") 
        % tile_col % tile_row));

    cv::imwrite(file_name, roi);
}

void save_tiles(cv::Mat const& src, cv::Size const& roi_size)
{
    CV_Assert(src.cols % roi_size.width == 0);
    CV_Assert(src.rows % roi_size.height == 0);

    int32_t const TILE_COLS(src.cols / roi_size.width);
    int32_t const TILE_ROWS(src.rows / roi_size.height);

    for (int32_t r(0); r < TILE_ROWS; ++r) {
        for (int32_t c(0); c < TILE_COLS; ++c) {
            process_tile(src, roi_size, c, r);
        }
    }
}

int main()
{
    using std::chrono::high_resolution_clock;
    using std::chrono::duration_cast;
    using std::chrono::microseconds;

    // Generate some random test image...
    cv::Mat src(1300, 2400, CV_8UC3);
    cv::randu(src, 0, 256);

    cv::Size const ROI_SIZE(10, 10); // width, height

    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    save_tiles(src, ROI_SIZE);
    high_resolution_clock::time_point t2 = high_resolution_clock::now();

    auto duration = duration_cast<microseconds>(t2 - t1).count();
    double t_ms(static_cast<double>(duration) / 1000.0);
    std::cout << "Processed in " << t_ms << " ms\n";

    return 0;
}

这里运行大约21.4秒,但是我发现它只使用了大约5%的可用CPU。看起来我们可以通过并行化来做得更好。

但首先,让我们考虑一下imwrite。使用100像素图像,PNG使用的zlib压缩实际上没有太大区别,并且由于文件是几百字节(即比今天的文件系统块大小小得多),因此它对磁盘使用没有多大帮助。这意味着我们可以禁用不必要的压缩来加快速度。

std::vector<int32_t> compression_params{CV_IMWRITE_PNG_COMPRESSION , 0};
cv::imwrite(file_name, roi, compression_params);

这需要20.7秒 - 不是很大的收获,但仍然值得,因为我们没有失去任何东西。

让我们来看看并行化这个问题。 OpenCV为此提供了一个有用的工具 - cv::parallel_for_,它为我们完成了大部分工作。

示例代码:

#include <opencv2/opencv.hpp>

#include <boost/format.hpp>

#include <cstdint>
#include <chrono>
#include <iostream>
#include <vector>

void process_tile(cv::Mat const& src
    , cv::Size const& roi_size
    , int32_t tile_col
    , int32_t tile_row)
{
    cv::Rect roi_bounds(tile_col * roi_size.width
        , tile_row * roi_size.height
        , roi_size.width
        , roi_size.height);
    cv::Mat roi(src(roi_bounds));

    std::string file_name(str(boost::format("tiles/tile_%03d_%03d.png") 
        % tile_col % tile_row));

    std::vector<int32_t> compression_params{ CV_IMWRITE_PNG_COMPRESSION , 0};

    cv::imwrite(file_name, roi, compression_params);
}

class ParallelSaveTiles
    : public cv::ParallelLoopBody
{
public:
    ParallelSaveTiles(cv::Mat const& src, cv::Size const& roi_size)
        : src(src)
        , roi_size(roi_size)
    {
    }

    virtual void operator()(cv::Range const& range) const
    {
        int32_t const TILE_COLS(src.cols / roi_size.width);

        for (int32_t r(range.start); r < range.end; ++r) {
            for (int32_t c(0); c < TILE_COLS; ++c) {
                process_tile(src, roi_size, c, r);
            }
        }
    }

private:
    cv::Mat const& src;
    cv::Size const& roi_size;
};



void save_tiles(cv::Mat const& src, cv::Size const& roi_size)
{
    CV_Assert(src.cols % roi_size.width == 0);
    CV_Assert(src.rows % roi_size.height == 0);

    int32_t const TILE_ROWS(src.rows / roi_size.height);

    ParallelSaveTiles parallel_impl(src, roi_size);
    cv::parallel_for_(cv::Range(0, TILE_ROWS), parallel_impl);
}

int main()
{
    using std::chrono::high_resolution_clock;
    using std::chrono::duration_cast;
    using std::chrono::microseconds;

    cv::Mat src(1300, 2400, CV_8UC3);
    cv::randu(src, 0, 256);

    cv::Size const ROI_SIZE(10, 10); // width, height

    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    save_tiles(src, ROI_SIZE);
    high_resolution_clock::time_point t2 = high_resolution_clock::now();

    auto duration = duration_cast<microseconds>(t2 - t1).count();
    double t_ms(static_cast<double>(duration) / 1000.0);
    std::cout << "Processed in " << t_ms << " ms\n";

    return 0;
}

现在它运行大约5.3秒(在带有32 GB RAM的i7-4930K上,在Win10上写入SSD)。