Como reduzir o número de colors em uma imagem com o OpenCV?

Eu tenho um conjunto de arquivos de imagem e quero reduzir o número de colors deles para 64. Como posso fazer isso com o OpenCV?

Eu preciso disso para poder trabalhar com um histograma de imagens de 64 tamanhos. Estou implementando técnicas CBIR

O que eu quero é quantização de colors para uma paleta de 4 bits.

Existem várias maneiras de fazer isso. Os methods sugeridos por jeff7 são OK, mas algumas desvantagens são:

  • método 1 tem parâmetros N e M, que você deve escolher, e você também deve convertê-lo para outro espaço de colors.
  • O método 2 respondido pode ser muito lento, já que você deve calcular um histograma de 16.7 Milhões de checkboxs e classificá-lo por frequência (para obter os 64 valores de frequência mais alta)

Eu gosto de usar um algoritmo baseado nos Bits Mais Significativos para usar em uma cor RGB e convertê-lo em uma imagem de 64 colors. Se você estiver usando C / OpenCV, você pode usar algo como a function abaixo.

Se você está trabalhando com imagens em nível de cinza, eu recomendei usar a function LUT () do OpenCV 2.3, já que é mais rápido. Existe um tutorial sobre como usar o LUT para reduzir o número de colors. Veja: Tutorial: Como digitalizar imagens, tabelas de consulta … No entanto, eu acho mais complicado se você está trabalhando com imagens 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 } } } 

Você pode considerar K-meios, mas neste caso, provavelmente será extremamente lento. Uma abordagem melhor pode estar fazendo isso “manualmente” por conta própria. Digamos que você tenha uma imagem do tipo CV_8UC3 , ou seja, uma imagem em que cada pixel é representado por 3 valores RGB de 0 a 255 ( Vec3b ). Você pode “mapear” esses 256 valores para apenas 4 valores específicos, o que produziria 4 x 4 x 4 = 64 colors possíveis.

Eu tive um dataset, onde eu precisava ter certeza de que escuro = preto, luz = branco e reduzir a quantidade de colors entre tudo. Isso é o que eu fiz (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 } } } 

fazendo com que [0,64) se torne 0 , [64,128) -> 64 e [128,255) -> 255 , produzindo 27 colors:

insira a descrição da imagem aquiinsira a descrição da imagem aqui

Para mim isso parece ser limpo, perfeitamente claro e mais rápido do que qualquer outra coisa mencionada em outras respostas.

Você também pode considerar reduzir esses valores para um dos múltiplos de algum número, digamos:

 inline uchar reduceVal(const uchar val) { if (val < 192) return uchar(val / 64.0 + 0.5) * 64; return 255; } 

o que daria um conjunto de 5 valores possíveis: {0, 64, 128, 192, 255} , isto é, 125 colors.

Este assunto foi bem abordado no Cookbook de Programação de Aplicações de Visão por Computador OpenCV 2 :

O Capítulo 2 mostra algumas operações de redução, uma delas demonstrada aqui em C ++:

 #include  #include  #include  #include  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(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; } 

Abaixo você pode encontrar a imagem de input (esquerda) e a saída desta operação (direita):

Existe o algoritmo de clusterização K-means que já está disponível na biblioteca OpenCV. Em suma, determina quais são os melhores centróides em torno dos quais agrupar seus dados para um valor definido pelo usuário de k (= sem clusters). Então, no seu caso, você poderia encontrar os centróides em torno dos quais agrupar seus valores de pixel para um determinado valor de k = 64. Os detalhes estão lá se você procurar no Google. Aqui está uma breve introdução a k-means.

Algo semelhante ao que você provavelmente está tentando foi perguntado aqui em SO usando k-means, espero que ajude.

Outra abordagem seria usar a function Pyramid Mean Shift Filter no OpenCV. Ele produz imagens um pouco “achatadas”, ou seja, o número de colors é menor e pode ajudá-lo.

As respostas sugeridas aqui são muito boas. Eu pensei em adicionar minha ideia também. Eu sigo a formulação de muitos comentários aqui, nos quais se diz que 64 colors podem ser representadas por 2 bits de cada canal em uma imagem RGB.

A function no código abaixo toma como input uma imagem e o número de bits requeridos para quantização. Ele usa a manipulação de bits para “eliminar” os bits LSB e manter apenas o número necessário de bits. O resultado é um método flexível que pode quantizar a imagem para qualquer número de bits.

 #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(j, i); valVec[0] = valVec[0] & maskBit; valVec[1] = valVec[1] & maskBit; valVec[2] = valVec[2] & maskBit; retImage.at(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; } 

Aqui está uma imagem que é usada na chamada de function acima:

insira a descrição da imagem aqui

Imagem quantificada em 2 bits para cada canal RGB (Total 64 Colors):

insira a descrição da imagem aqui

3 bits para cada canal:

insira a descrição da imagem aqui

4 bits ...

insira a descrição da imagem aqui

Supondo que você queira usar as mesmas 64 colors para todas as imagens (ou seja, paleta não otimizada por imagem), há pelo menos algumas escolhas que eu posso imaginar:

1) Converta para Lab ou espaço de colors YCrCb e quantize usando N bits para luminância e M bits para cada canal de cor, N deve ser maior que M.

2) Calcule um histograma 3D de valores de cor em todas as suas imagens de treinamento e escolha as 64 colors com os maiores valores de checkbox. Quantize suas imagens atribuindo a cada pixel a cor da checkbox mais próxima do conjunto de treinamento.

O método 1 é o mais genérico e mais fácil de implementar, enquanto o método 2 pode ser mais adequado ao seu dataset específico.

Atualização: Por exemplo, 32 colors são 5 bits, então atribua 3 bits ao canal de luminância e 1 bits a cada canal de cor. Para fazer essa quantização, faça uma divisão inteira do canal de luminância em 2 ^ 8/2 ^ 3 = 32 e cada canal de cor em 2 ^ 8/2 ^ 1 = 128. Agora existem apenas 8 valores de luminância diferentes e 2 canais de colors diferentes cada. Recombine esses valores em um único inteiro fazendo bit shift ou matemática (quantized color value = luminance * 4 + color1 * 2 + color2);

Por que você não faz apenas multiplicação / divisão de matrizes? Os valores serão automaticamente arredondados.

Pseudo-código:

converter seus canais para caracteres não assinados (CV_8UC3),
Divida por total de colors / colors desejadas. Mat = Mat / (256/64) Pontos decimais serão truncados.
Multiplique pelo mesmo número. Mat = tapete * 4

Feito. Cada canal agora contém apenas 64 colors.

    Intereting Posts