最大値255の PGM バイナリ形式の画像を読み込む(OpenCV)

はじめに

この記事では、最大値255のPGM バイナリ(PNM P5)形式の画像を cv::Matに読み込むコードを載せております。 しかし OpenCVcv::imreadで上記の形式に限らず PNM 形式を読み込めますので、 本来このような処理を自分で書く必要はありません。 所謂、車輪の再発明です。
上記の形式ならば、あらかじめメモリを確保したcv::Matのdataメンバに直接readして 同じものになるのではないかという実験的なものです。 事前にメモリ確保すれば処理時間が短くなることが期待できるかもしれませんが、 参考にする際は上記のことをご留意ください。

コード

#include <opencv2/opencv.hpp>

// 負の値は異常値
int32_t readPnmNumber(std::istream& is) {
    char c = -1;
    // 1文字目を見つける
    bool comment = false;
    while (is.get(c)) {
        if (comment) {
            if (c == '\n' || c == '\r') {
                comment = false;
            }
        }
        else if (c == '#') {
            comment = true;
        }
        else if (std::isspace(c)) {
            continue;
        }
        else { break; }
    }
    
    if (!std::isdigit(c)) { return -1; }
    std::string str(1, c);
    
    // 2文字目以降を追加する。
    while (is.get(c)) {        
        if (std::isspace(c)) { break; }
        if (!std::isdigit(c)) { return -1; }
        str.push_back(c);
    }
    
    // 文字列を数値に変換
    char* ptr;
    auto n = std::strtoul(str.c_str(), &ptr, 10);
    assert((ptr - &*str.begin()) == str.size());
    return n;
}

struct PnmHeader {
    char magic_number[2];
    int width;
    int height;
    int maximum_value;
};
bool readPnmHeader(std::istream& is, PnmHeader& pnm_header) {
    char c;
    // magic number
    if (!is.get(pnm_header.magic_number[0])) { return false; }
    if (!is.get(pnm_header.magic_number[1])) { return false; }
    if (!is.get(c)) { return false; }
    bool is_pnm = (pnm_header.magic_number[0] == 'P')
        && ('0' < pnm_header.magic_number[1] && pnm_header.magic_number[1] <= '6')
        && std::isspace(c);
    if (!is_pnm) { return false; }
    // width height
    pnm_header.width = readPnmNumber(is);
    pnm_header.height = readPnmNumber(is);
    is_pnm = pnm_header.width > 0 && pnm_header.height > 0;
    if (!is_pnm) { return false; }
    // maximum value
    if (!(pnm_header.magic_number[1] == '1' || pnm_header.magic_number[1] == '4')) {
        pnm_header.maximum_value = readPnmNumber(is);
        if (pnm_header.maximum_value < 0) { return false; }
    }
    return true;
}

bool readPnmP5Max255(const std::string& filename, cv::Mat& img) {
    std::ifstream ifs(filename, std::ios::binary);
    if (!ifs.is_open()) {
        return false;
    }
    PnmHeader header;
    bool reads = readPnmHeader(ifs, header);
    if (!reads) {
        return false;
    }
    if (header.magic_number[1] == '5' && header.maximum_value == 255) {
        img.create(header.height, header.width, CV_8UC1);
        char* p = reinterpret_cast<char*>(img.data);
        ifs.read(p, header.height * header.width);
    } else {
        return false;
    }
    return true;
}

int main() {
    std::string filename = "test.pgm";

    cv::Mat1b img1;
    readPnmP5Max255(filename, img1);

    // 一致確認
    auto img2 = cv::imread(filename, cv::IMREAD_GRAYSCALE);
    std::cout << cv::countNonZero(img - img2);

    return 0;
}

結果(標準出力)

0

参考

PNM形式のヘッダの読み込み処理に関して、以下のページを参考にさせていただきました。 www.mm2d.net