画像ファイルのセットがあり、それらの色の数を64に減らしたいのですが、OpenCVでこれを行うにはどうすればよいですか?
64サイズの画像ヒストグラムで作業できるようにこれが必要です。CBIRテクニックを実装しています
私が欲しいのは、4ビットパレットへの色の量子化です。
画像ファイルのセットがあり、それらの色の数を64に減らしたいのですが、OpenCVでこれを行うにはどうすればよいですか?
64サイズの画像ヒストグラムで作業できるようにこれが必要です。CBIRテクニックを実装しています
私が欲しいのは、4ビットパレットへの色の量子化です。
K-meansを検討するかもしれませんが、この場合は非常に遅い可能性があります。より良いアプローチは、これを「手動で」自分で行うことかもしれません。タイプCV_8UC3
の画像があるとします。つまり、各ピクセルが0から255(Vec3b
)までの3つのRGB値で表される画像です。これらの256個の値を4つの特定の値のみに「マップ」すると、4 x 4 x 4
=64
可能な色が得られます。
私はデータセットを持っていました。そこでは、暗い=黒、明るい=白であることを確認し、その間のすべての色の量を減らす必要がありました。これは私がしたことです(C ++):
inline uchar reduceVal(const uchar val)
{
if (val < 64) return 0;
if (val < 128) return 64;
return 255;
}
void processColors(Mat& img)
{
uchar* pixelPtr = img.data;
for (int i = 0; i < img.rows; i++)
{
for (int j = 0; j < img.cols; j++)
{
const int pi = i*img.cols*3 + j*3;
pixelPtr[pi + 0] = reduceVal(pixelPtr[pi + 0]); // B
pixelPtr[pi + 1] = reduceVal(pixelPtr[pi + 1]); // G
pixelPtr[pi + 2] = reduceVal(pixelPtr[pi + 2]); // R
}
}
}
、->および->[0,64)
になり、色が生成されます。0
[64,128)
64
[128,255)
255
27
私には、これは他の回答で言及されている他の何よりもきちんとしていて、完全に明確で、速いように思えます。
また、これらの値をある数の倍数の1つに減らすことを検討することもできます。たとえば、次のようにします。
inline uchar reduceVal(const uchar val)
{
if (val < 192) return uchar(val / 64.0 + 0.5) * 64;
return 255;
}
これにより、5つの可能な値のセットが生成され{0, 64, 128, 192, 255}
ます。つまり、125色です。
このテーマは、OpenCV2コンピュータービジョンアプリケーションプログラミングクックブックで十分に取り上げられています。
第2章では、いくつかの削減操作を示します。そのうちの1つは、ここでC ++で、後でPythonで示されています。
#include <iostream>
#include <vector>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
void colorReduce(cv::Mat& image, int div=64)
{
int nl = image.rows; // number of lines
int nc = image.cols * image.channels(); // number of elements per line
for (int j = 0; j < nl; j++)
{
// get the address of row j
uchar* data = image.ptr<uchar>(j);
for (int i = 0; i < nc; i++)
{
// process each pixel
data[i] = data[i] / div * div + div / 2;
}
}
}
int main(int argc, char* argv[])
{
// Load input image (colored, 3-channel, BGR)
cv::Mat input = cv::imread(argv[1]);
if (input.empty())
{
std::cout << "!!! Failed imread()" << std::endl;
return -1;
}
colorReduce(input);
cv::imshow("Color Reduction", input);
cv::imwrite("output.jpg", input);
cv::waitKey(0);
return 0;
}
以下に、入力画像(左)とこの操作の出力(右)を示します。
Pythonの同等のコードは次のようになります:( @eliezer-bernartへのクレジット)
import cv2
import numpy as np
input = cv2.imread('castle.jpg')
# colorReduce()
div = 64
quantized = input // div * div + div // 2
cv2.imwrite('output.jpg', quantized)
それを行うには多くの方法があります。jeff7によって提案された方法は問題ありませんが、いくつかの欠点があります。
最上位ビットに基づくアルゴリズムを使用してRGBカラーで使用し、64カラー画像に変換するのが好きです。C / OpenCVを使用している場合は、以下の関数のようなものを使用できます。
グレーレベルの画像を使用している場合は、OpenCV 2.3のLUT()関数を使用することをお勧めします。これは、より高速であるためです。LUTを使用して色の数を減らす方法に関するチュートリアルがあります。参照:チュートリアル:画像のスキャン方法、ルックアップテーブル...ただし、RGB画像を使用している場合は、より複雑になります。
void reduceTo64Colors(IplImage *img, IplImage *img_quant) {
int i,j;
int height = img->height;
int width = img->width;
int step = img->widthStep;
uchar *data = (uchar *)img->imageData;
int step2 = img_quant->widthStep;
uchar *data2 = (uchar *)img_quant->imageData;
for (i = 0; i < height ; i++) {
for (j = 0; j < width; j++) {
// operator XXXXXXXX & 11000000 equivalent to XXXXXXXX AND 11000000 (=192)
// operator 01000000 >> 2 is a 2-bit shift to the right = 00010000
uchar C1 = (data[i*step+j*3+0] & 192)>>2;
uchar C2 = (data[i*step+j*3+1] & 192)>>4;
uchar C3 = (data[i*step+j*3+2] & 192)>>6;
data2[i*step2+j] = C1 | C2 | C3; // merges the 2 MSB of each channel
}
}
}
これは、K-Meansクラスタリングを使用した色の量子化のPython実装ですcv2.kmeans
。アイデアは、画像の色の外観を可能な限り維持しながら、画像内の異なる色の数を減らすことです。結果は次のとおりです。
入出力->
_
コード
import cv2
import numpy as np
def kmeans_color_quantization(image, clusters=8, rounds=1):
h, w = image.shape[:2]
samples = np.zeros([h*w,3], dtype=np.float32)
count = 0
for x in range(h):
for y in range(w):
samples[count] = image[x][y]
count += 1
compactness, labels, centers = cv2.kmeans(samples,
clusters,
None,
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10000, 0.0001),
rounds,
cv2.KMEANS_RANDOM_CENTERS)
centers = np.uint8(centers)
res = centers[labels.flatten()]
return res.reshape((image.shape))
image = cv2.imread('1.jpg')
result = kmeans_color_quantization(image, clusters=8)
cv2.imshow('result', result)
cv2.waitKey()
ここで提案された答えは本当に良いです。私も自分の考えを追加しようと思いました。ここでは、RGB画像の各チャンネルの2ビットで64色を表現できると言われている多くのコメントの定式化に従います。
以下のコードの関数は、画像と量子化に必要なビット数を入力として受け取ります。ビット操作を使用してLSBビットを「ドロップ」し、必要なビット数のみを保持します。その結果、画像を任意のビット数に量子化できる柔軟な方法が得られます。
#include "include\opencv\cv.h"
#include "include\opencv\highgui.h"
// quantize the image to numBits
cv::Mat quantizeImage(const cv::Mat& inImage, int numBits)
{
cv::Mat retImage = inImage.clone();
uchar maskBit = 0xFF;
// keep numBits as 1 and (8 - numBits) would be all 0 towards the right
maskBit = maskBit << (8 - numBits);
for(int j = 0; j < retImage.rows; j++)
for(int i = 0; i < retImage.cols; i++)
{
cv::Vec3b valVec = retImage.at<cv::Vec3b>(j, i);
valVec[0] = valVec[0] & maskBit;
valVec[1] = valVec[1] & maskBit;
valVec[2] = valVec[2] & maskBit;
retImage.at<cv::Vec3b>(j, i) = valVec;
}
return retImage;
}
int main ()
{
cv::Mat inImage;
inImage = cv::imread("testImage.jpg");
char buffer[30];
for(int i = 1; i <= 8; i++)
{
cv::Mat quantizedImage = quantizeImage(inImage, i);
sprintf(buffer, "%d Bit Image", i);
cv::imshow(buffer, quantizedImage);
sprintf(buffer, "%d Bit Image.png", i);
cv::imwrite(buffer, quantizedImage);
}
cv::waitKey(0);
return 0;
}
上記の関数呼び出しで使用される画像は次のとおりです。
RGBチャネルごとに2ビットに量子化された画像(合計64色):
各チャネルに3ビット:
4ビット..。
OpenCVライブラリですでに利用可能なK-meansクラスタリングアルゴリズムがあります。つまり、ユーザー定義の値k(=クラスターの数)に対してデータをクラスター化するのに最適な重心を決定します。したがって、あなたの場合、k=64の特定の値に対してピクセル値をクラスター化する重心を見つけることができます。あなたがグーグルで回れば詳細はそこにあります。これがk-meansの簡単な紹介です。
おそらくあなたが試していることに似た何かが、ここでk-meansを使用してSOで尋ねられました、それが役立つことを願っています。
別のアプローチは、OpenCVでピラミッド平均シフトフィルター機能を使用することです。それは幾分「平らな」画像を生み出します、すなわち色の数が少ないのでそれはあなたを助けることができるかもしれません。
すべての画像に同じ64色を使用したい場合(つまり、画像ごとに最適化されていないパレット)、私が考えることができる少なくとも2つの選択肢があります。
1)LabまたはYCrCb色空間に変換し、輝度にNビット、各カラーチャネルにMビットを使用して量子化します。NはMより大きくする必要があります。
2)すべてのトレーニング画像の色値の3Dヒストグラムを計算し、ビン値が最大の64色を選択します。各ピクセルにトレーニングセットから最も近いビンの色を割り当てて、画像を定量化します。
方法1は最も一般的で実装が最も簡単ですが、方法2は特定のデータセットに合わせて調整することができます。
更新:たとえば、32色は5ビットなので、輝度チャネルに3ビット、各カラーチャネルに1ビットを割り当てます。この量子化を行うには、輝度チャネルを2 ^ 8/2 ^ 3 = 32で整数除算し、各カラーチャネルを2 ^ 8/2 ^ 1 = 128で除算します。これで、8つの異なる輝度値と2つの異なるカラーチャネルのみが存在します。各。これらの値を単一の整数に再結合して、ビットシフトまたは計算を実行します(量子化された色の値=輝度* 4 + color1 * 2 + color2);
単純なビット単位で適切なビットマスクを使用すると、うまくいきます。
python、64色用、
img = img & int("11000000", 2)
RGB画像の色の数は、完全な立方体である必要があります(3つのチャネルで同じ)。
この方法では、チャネルの可能な値の数は2の累乗である必要があります(このチェックはコードによって無視され、次に低い2の累乗が使用されます)
import numpy as np
import cv2 as cv
def is_cube(n):
cbrt = np.cbrt(n)
return cbrt ** 3 == n, int(cbrt)
def reduce_color_space(img, n_colors=64):
n_valid, cbrt = is_cube(n_colors)
if not n_valid:
print("n_colors should be a perfect cube")
return
n_bits = int(np.log2(cbrt))
if n_bits > 8:
print("Can't generate more colors")
return
bitmask = int(f"{'1' * n_bits}{'0' * (8 - n_bits)}", 2)
return img & bitmask
img = cv.imread("image.png")
cv.imshow("orig", img)
cv.imshow("reduced", reduce_color_space(img))
cv.waitKey(0)
img = numpy.multiply(img // 32、32)
行列の乗算/除算をしてみませんか?値は自動的に丸められます。
擬似コード:
チャンネルを符号なし文字(CV_8UC3)に変換し、
合計色/目的の色で割ります。マット=マット/(256/64)。小数点は切り捨てられます。
同じ数を掛けます。マット=マット*4
終わり。現在、各チャネルには64色しか含まれていません。