Windows日本語ファイルパスの罠
WindowsのC/C++でOpenCVのimreadやimwriteで日本語ファイルパスにあるファイルを開こうとすると失敗します。C/C++に限らずPythonなどから呼び出しても起きるのですが、要するにUTF-8で書かれたファイルパスをWindowsのfopenで開けないのが原因です。
まことに残念ながら、OpenCV側ではこの問題は対処しないことに決めたようです。
上のエントリを見ると日本人のみならず、中国人は漢字が、韓国人はハングルが、ドイツ人はウムラウトがあと阿鼻叫喚ですが、最新のOSはWindowsを含めてだいたいUTF-8に対応してるはずだ、ダメなら一旦メモリに読み込んでからデコードしてちょ、ということらしい。えー、WindowsでUTF-8のパス通らないけどなー。
Windows C/C++ での対処
ダメならいったんメモリに読み込んでバッファをOpenCVてデコードせよとのことです。PythonではPython側で普通に読み込んでバッファを渡すという対処でいけるのですが、C/C++では普通に読み込んでがダメでした。C/C++ではUTF-8をwcharに変換して_wfopenを使う必要があるようです。
というわけでcv::imread、cv::imwriteの代替関数を書きました。UTF-8からwcharへの変換にはMultiByteToWideCharというWindows APIの関数を使いました。この関数はバッファサイズ0で呼ぶと変換後のバッファサイズを返すので、それでバッファを確保してあらためて変換を要請するという、面倒くさい仕様ですね。
- wimread()
・入力ファイルパスをUTF-8からwcharに変換する。
・wchar対応の_wfopen_sでオープンし、メモリ内にファイルを読み込む
・cv::imdecode()でMatに変換する
Mat wimread(char* inpath) {
setlocale(LC_ALL, "japanese");
int size = ::MultiByteToWideChar(CP_UTF8, 0, inpath, -1, (wchar_t*)NULL, 0);
wchar_t* winpath = (wchar_t*)new wchar_t[size];
::MultiByteToWideChar(CP_UTF8, 0, inpath, -1, winpath, size);
FILE* fp;
_wfopen_s(&fp, winpath, L"rb");
delete[] winpath;
if (fp == NULL) {
std::cout << "cant open " << inpath << std::endl;
return Mat();
}
long long int fsize = _filelengthi64(_fileno(fp));
unsigned char* buff = new unsigned char[fsize];
fread(buff, fsize, 1, fp);
fclose(fp);
// Matへ変換
std::vector<uchar> jpeg(buff, buff + fsize);
cv::Mat img = cv::imdecode(jpeg, 1);
delete[] buff;
return img;
}
- wimwrite
・入力ファイルパスをUTF-8からwcharに変換する。
・wchar対応の_wfopen_sでオープンする
・Mat内のデータをファイルに書き込む
・エンコードの品質95で決め打ちなので、必要ならなんとかしてください
void wimwrite(char* outpath, Mat img) {
setlocale(LC_ALL, "japanese");
std::vector<uchar> buff2; //buffer for coding
std::vector<int> param = std::vector<int>(2);
param[0] = 1;
param[1] = 95; //default(95) 0-100
imencode(".jpg", img, buff2, param);
int size = ::MultiByteToWideChar(CP_UTF8, 0, outpath, -1, (wchar_t*)NULL, 0);
wchar_t* woutpath = (wchar_t*)new wchar_t[size];
::MultiByteToWideChar(CP_UTF8, 0, outpath, -1, woutpath, size);
FILE* fp2;
_wfopen_s(&fp2, woutpath, L"wb");
delete[] woutpath;
if (fp2 == NULL) {
std::cout << "output cant open" << std::endl;
return;
}
fwrite(buff2.data(), buff2.size(), 1, fp2);
fclose(fp2);
}
同じことが、OpenCVの機械学習関連関数のモデルの読み込みでも起きます。そっちはファイルが大きいので、いったんメモリに取り込むとか無理だろうなあ。モデルを日本語ディレクトリに置かないようにすればなんとかはなりますが。OpenCVの中の人が言うWindowsでも最新リリースはUTF-8でfopenできるというのは何かやり方あるんだろか。