OpenCVでの16bit画像、しかも値に偏りのある場合の、8bitノーマライズの仕方メモ
C++, Python でそれぞれについて
ここでは、1CH(グレースケール)の話
各画素の値分布がこんな感じのとき、普通にやると真っ暗な画像になってしまう。
また定数でやらないとノイズで動画がチラつく。
その16bit画像が「0x0000が真っ黒/0xFFFFが真っ白」というわけではなく、こんなときにやるノーマライズ
- 実際は12bitまでしか使っていない
- 最低値が0じゃない
- N以下/M以上はノイズ
その画像のシェイプを確かめる
C++ シェイプ確認
// 型: cv::Mat
cout << frame.rows << ", " << frame.cols << ", " << frame.channels() << endl;
// → HEIGHT, WIDTH, 1
cout << frame.type() << endl;
// → 2 (つまりCV_16U)
Python シェイプ確認
# 型: numpy.ndarray
print(frame.shape)
# → (HEIGHT, WIDTH, 1)
print(frame.dtype)
# → uint16
普通のノーマライズ
今回はこの仕様で困ったわけだが、何も考えずにcv2.imshow()すると、単純に256で割られたノーマライズで表示される。
If the image is 16-bit unsigned, the pixels are divided by 256. That is, the value range [0,255*256] is mapped to [0,255].
これは、次のやり方と同じ。
従ってこれらはcv2.imshow()
するためにはやる必要のないことたち。
C++ 普通のノーマライズ
src.convertTo(dist, CV_8UC1, 1. / 256.);
Python 普通のノーマライズ
dist = (src/256).astype('uint8')
min/maxを指定したノーマライズ
ちなみに下記でmin/maxを入れ替えると白黒反転する。
C++ min/max指定ノーマライズ
const int MIN = 200;
const int MAX = 1200;
cv::Mat dist;
double alpha = 256. / ((double)MAX - MIN);
double beta = alpha * -1. * MIN;
frame.convertTo(dist, CV_8UC1, alpha, beta);
cv::imshow("normalised frame", dist);
cv::waitKey(1);
Python min/maxノーマライズ
MIN = 200;
MAX = 1200;
alpha = 256.0 / (MAX - MIN)
beta = alpha * MIN * -1
dist = cv2.convertScaleAbs(frame, alpha=alpha, beta=beta)
cv2.imshow("normalised frame", dist)
cv2.waitKey(1)
min/max 動的に決めるノーマライズ
その画像中のmin/maxを取得するのが自然っぽいが、ノイズの影響を受けまくる。
冒頭のような、ノイズが存在する画像の動画だと、チラつきが気になってしまう。
やるなら、動画中でmin/maxあまり振れすぎないようするか、平均/標準偏差(cv::meanStdDev
)を取得してmin/max決定するのがよさげ。
ちなみに「画像中のmin/max」で正規化する、そのやり方
double min, max;
cv::minMaxLoc(frames[1], &min, &max);
alpha = 256. / (max - min);
beta = alpha * -1. * min;
frames[1].convertTo(dist4, CV_8UC1, alpha, beta);
は
cv::normalize(frames[1], dist1, 0., 255., cv::NORM_MINMAX, CV_8UC1);
が同じことをしているのでこれでいい。