【问题标题】:Cimg throwing CImgIOException at randomCimg 随机抛出 CImgIOException
【发布时间】:2018-07-04 19:31:51
【问题描述】:

我有一些代码可以并行运行一堆线程。每个线程执行一个函数,以便在从目录加载的图像上复制“水印”,并将修改后的图像保存在用户指定的另一个目录中。为了达到这个结果,我必须使用 CImg 库。我不明白为什么有时 CImg 会抛出 CImgIOExceptionCImg<unsigned char>::save_other(): Failed to save fileCImg<unsigned char>::save_jpeg(): Failed to save file。我已经安装了 imagemagick,所以使用 jpeg/jpg 格式保存和打开图像应该没有问题。

这是我的代码:

#include <atomic>
#include <chrono>
#include "CImg.h"
#include <cstdlib>
#include <filesystem>
#include <functional>
#include <iostream>
#include <mutex>
#include <optional>
#include <queue>
#include <string>
#include <thread>
#include <typeinfo>

using namespace cimg_library;

std::mutex IMAGES_MUTEX, IO_MUTEX;
std::atomic_int PROCESSED_IMAGES = 0;

void apply_watermark(std::queue<std::string>& images, CImg<unsigned char>& watermark, int workload, \
                     std::string output_dir, int id) {
    std::queue<std::string> to_process;
    int counter = 0;
    bool stop = false;
    CImg<unsigned char> img;

    while (!stop) {
        IMAGES_MUTEX.lock();
        while(counter < workload) {
            if (images.empty()) {
                std::cout << "Thread " << id << ": founded empty queue" << std::endl;
                counter = workload;
                stop = true;
            } else {
                std::cout << "Thread " << id << ": aquiring image" << images.front() << std::endl;
                to_process.push(images.front());
                images.pop();
                counter += 1;
            }
        }
        IMAGES_MUTEX.unlock();

        counter = 0;

        while(!(to_process.empty())) {
            img.assign(to_process.front().c_str());

            if (!(img.width() != 1024 || img.height() != 768)) {
                cimg_forXY(watermark, x, y) {
                    int R = (int)watermark(x, y, 0, 0);

                    if (R != 255) {
                        img(x, y, 0, 0) = 0;
                        img(x, y, 0, 1) = 0;
                        img(x, y, 0, 2) = 0;
                    }
                }

                std::string fname = to_process.front().substr(to_process.front().find_last_of('/') + 1);

                IO_MUTEX.lock();
                std::cout << "Thread " << id << ": saving image " << fname << std::endl;
                IO_MUTEX.unlock();

                img.save_jpeg(((std::string)output_dir + (std::string)"/" + fname).c_str());
                to_process.pop();
                PROCESSED_IMAGES += 1;
            } else {
                to_process.pop();
            }
            img.clear();
        }
    }
    return;
}

int main(int argc, char const *argv[]) {
    auto completion_time_start = std::chrono::high_resolution_clock::now();

    if (argc != 5) {
        std::cout << "MISSING PARAMETERS!" << std::endl;

        return -1;
    }

    int par_degree = std::atoi(argv[3]);

    if (!(std::filesystem::exists(argv[2]))) {
        std::cout << "WATERMARK NOT FOUND!" << std::endl;

        return -1;
    }

    CImg<unsigned char> wtrk(argv[2]);
    std::queue<std::string> images;

    if (!(std::filesystem::exists(argv[1]))) {
        std::cout << "IMAGES' DIRECTORY NOT FOUND!" << std::endl;

        return -1;
    }

    for (auto& path : std::filesystem::directory_iterator(argv[1])) {
        std::string fname = path.path().string().substr(path.path().string().find_last_of('/') + 1);

        if (fname != ".DS_Store") {
            images.push(path.path().string());
        }
    }

    if (!(std::filesystem::exists((std::string)argv[4]))) {
        std::filesystem::create_directory((std::string)argv[4]);
    }

    if (par_degree == 1) {
        while(!(images.empty())) {
            CImg<unsigned char> img(images.front().c_str());

            if (!(img.width() != 1024 || img.height() != 768)) {
                cimg_forXY(wtrk, x, y) {
                    int R = (int)wtrk(x, y, 0, 0);

                    if (R != 255) {
                        img(x, y, 0, 0) = 0;
                        img(x, y, 0, 1) = 0;
                        img(x, y, 0, 2) = 0;
                    }
                }

                std::string fname = images.front().substr(images.front().find_last_of('/') + 1);
                img.save_jpeg(((std::string)argv[4] + (std::string)"/" + fname).c_str());
                images.pop();
                PROCESSED_IMAGES += 1;
            } else {
                images.pop();
            }
        }
    } else {
        int workload = (int)images.size() / par_degree;
        std::thread workers[par_degree];

        for (int i = 0; i < par_degree; i++) {
            workers[i] = std::thread(apply_watermark, std::ref(images), std::ref(wtrk), workload, \
                                     (std::string)argv[4], i);
        }

        for (int i = 0; i < par_degree; i++) {
            workers[i].join();
        }
    }

    auto completion_time_end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::ratio<1>> completion_time = completion_time_end - \
                                                                   completion_time_start;

    std::cout << "\nPARALLELISM DEGREE: " << par_degree << std::endl;
    std::cout << "COMPLETION TIME: " << completion_time.count() << " SECONDS" << std::endl;
    std::cout << "PROCESSED IMAGES: " << PROCESSED_IMAGES << std::endl;

    return 0;
}

所有的异常都是在保存图像的过程中抛出的,我对 C++ 语言很陌生,我真的不知道。仅使用一个线程(主线程)运行程序不会产生任何问题。

这是一个启动命令的例子:

./test ../imgs ../watermark.jpg 4 ../output_dir

我们将不胜感激。

【问题讨论】:

  • 简化和learn how to debug your programs。您应该从一个重要的简化开始:不要使用线程。事实上,在开始使用线程之前,您应该始终确保您的算法是单线程的。另一个简化是在测试之前不要一次编写太多代码。从小处着手,逐步添加新事物,并在每个小步骤之间进行测试。
  • 另外,不要试图通过使用缩写来节省空间,特别是如果它们并没有真正节省太多(并且仅在源代码本身中)。例如,为什么使用wtrk 而不是完美找到watermark?后者比神秘的wtrk 更容易理解它是什么。现在的编译器可以处理超过六个字符的符号。
  • @Someprogrammerdude 正如我所写,我已经编写了程序的顺序版本。它在 main 函数中,并在并行度为 1 时启动。感谢您的建议,但我仍然不明白为什么会启动此异常。我已经测试了每一行代码,这个问题仍然存在。调试部分我用过lldb,还是一无所获。
  • 当您尝试保存图像时似乎发生了异常。您当时尝试使用的文件名是什么?它是一个有效的名称吗?它是有效的路径吗?您可以写入该路径和文件吗?路径的每一部分都存在吗?该文件是否已经存在?
  • 哦,你真的应该在真正进行任何 IO(即,将图像保存到文件)之前解锁IO_MUTEX吗?

标签: c++ multithreading image exception cimg


【解决方案1】:

我怀疑写命令不是线程安全的。例如,大多数 jpeg 编码器/解码器使用需要为每个线程制作的结构,在这里它们可能是共享的。尽管 cimg 网站上有声明,但是否/如何考虑这一点并不是很明显。

我会通过序列化.save_jpeg 命令的执行来测试假设,尽管解决方案将来自不使用 cimg,而是直接调用 libjpeg-turbo:

//somewhere in global
std::mutex protect_save_image;

//At your section
{
    std::unique_lock lk(protect_save_image);
    img.save_jpeg(((std::string)argv[4] + (std::string)"/" + fname).c_str());`
}

此外,如果线程数非常高(又名par_degree > 1000),您可能会超过导致 IO 失败的可用文件描述的最大数量。

【讨论】:

  • 我得出了和你一样的结论。在尝试解决该问题时,我将保存功能放在了 try/catch 块之间,其中 catch 部分也应用了保存功能,目前这已经解决了问题。为了优化程序的性能,我没有考虑将另一个互斥锁用于保存功能。我会将您的答案标记为正确,谢谢:)
  • @g_rmz 作为伪经建议的友好词,您的代码可能会受益于多线程。 1) 保存 jpeg 命令在压缩数据时执行大量计算 2) 大多数 IO 系统由许多层组成,需要饱和才能达到峰值性能(即使是单个 HDD 也需要饱和以保持磁头始终写入)。您必须直接调用 libjpeg-turbo。
猜你喜欢
  • 1970-01-01
  • 2013-12-24
  • 1970-01-01
  • 1970-01-01
  • 2011-08-22
  • 2011-01-16
  • 2017-12-15
  • 2015-11-23
  • 2014-03-16
相关资源
最近更新 更多