web-dev-qa-db-ja.com

OpenCVで画像の色数を減らす方法は?

画像ファイルのセットがあり、それらの色の数を64に減らしたいのですが、OpenCVでこれを行うにはどうすればよいですか?

64サイズの画像ヒストグラムで作業できるように、これが必要です。 CBIRテクニックを実装しています

4ビットパレットへのカラー量子化が必要です。

23
Felipe Hummel

それを行うには多くの方法があります。 jeff7が提案する方法は問題ありませんが、いくつかの欠点があります。

  • 方法1にはパラメーターNとMがあり、これらを選択する必要があります。また、パラメーターを別の色空間に変換する必要もあります。
  • 16.7ミリオンのビンヒストグラムを計算し、頻度でソートする必要があるため(方法64のより高い頻度値を取得するため)、方法2は非常に遅くなる可能性があります。

Most Significant Bitsに基づくアルゴリズムを使用して、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
        }     
    }
}
10
Moacir Ponti

K-meansを検討することもできますが、この場合は非常に遅くなる可能性があります。より良いアプローチは、これを「手動で」自分で行うことです。タイプCV_8UC3の画像、つまり、各ピクセルが0〜255の3つのRGB値(Vec3b)で表される画像があるとします。これらの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色になります。

enter image description hereenter image description here

私にとってこれは、他の回答で言及されている他のどの記事よりも、きちんとしていて、完全に明確で、速いようです。

また、これらの値をある数の倍数の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色。

13
LihO

この主題はOpenCV 2 Computer Vision Application Programming Cookbookで十分にカバーされました:

第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;
}

以下にinput画像(左)とoutputがあります。この操作(右):

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)
12
karlphillip

ここで提案された答えは本当に良いです。私も自分の考えを追加すると思いました。ここでは、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;
}

上記の関数呼び出しで使用される画像は次のとおりです。

enter image description here

RGBチャネルごとに2ビットに量子化された画像(合計64色):

enter image description here

各チャネルに3ビット:

enter image description here

4ビット...

enter image description here

5
masad

OpenCVライブラリですでに利用可能なK平均クラスタリングアルゴリズムがあります。つまり、ユーザー定義値k(=クラスターなし)のデータをクラスター化するのに最適なセントロイドを決定します。したがって、あなたのケースでは、k = 64の特定の値のピクセル値をクラスター化するための重心を見つけることができます。あなたがググってみれば詳細はそこにあります。 ここ はk-meansの短い紹介です。

[〜#〜] so [〜#〜] でk-meansを使用して、あなたがおそらく試みているものに似た何かが尋ねられて、それが役に立てば幸いです。

別のアプローチは、OpenCVで ピラミッド平均シフトフィルター 関数を使用することです。多少「平坦化」された画像が生成されます。つまり、色の数が少なくなるため、役立つ場合があります。

3
AruniRC

Python K-Means Clustering using 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()     
1
nathancy

すべての画像に同じ64色(つまり、画像ごとに最適化されていないパレット)を使用する場合、少なくともいくつかの選択肢が考えられます。

1)ラボまたは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 +カラー1 * 2 +カラー2);

1
jeff7