Comment réduire le nombre de couleurs dans une image avec OpenCV?

j'ai une série de fichiers d'image, et je veux réduire le nombre de couleurs à 64 ans. Comment puis-je faire ça avec OpenCV?

j'en ai besoin pour travailler avec un histogramme d'image de 64 tailles. J'implémente des techniques CBIR

ce que je veux, c'est la quantification des couleurs en une palette de 4 bits.

19
demandé sur Matt 2011-05-06 08:12:24

7 réponses

Il y a plusieurs façons de le faire. Les méthodes suggérées par jeff7 sont OK, mais quelques inconvénients sont:

  • méthode 1 ont les paramètres N et M, que vous devez choisir, et vous devez également le convertir en un autre colorspace.
  • méthode 2 la réponse peut être très lente, puisque vous devez calculer un histogramme de 16,7 milions et le Trier par fréquence (pour obtenir les 64 valeurs de fréquence plus élevées)

j'aime utiliser un algorithme basé sur le Le Plus Significatif Bits pour l'utiliser dans une couleur RVB et la convertir en une image 64 couleurs. Si vous utilisez C / OpenCV, vous pouvez utiliser quelque chose comme la fonction ci-dessous.

si vous travaillez avec des images de niveau gris, je recommande d'utiliser la fonction LUT() de L'OpenCV 2.3, car elle est plus rapide. Il y a un tutoriel sur la façon d'utiliser LUT pour réduire le nombre de couleurs. Voir:tutoriel: comment numériser des images, des tables de recherche... cependant je trouve que c'est plus compliqué si vous travaillez avec des images 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
        }     
    }
}
9
répondu Moacir Ponti 2014-03-20 13:34:34

vous pourriez considérer K-means, mais dans ce cas, il sera très probablement extrêmement lent. Une meilleure approche pourrait être le faire "manuellement" sur votre propre. Disons que vous avez l'image du type CV_8UC3, c'est-à-dire une image où chaque pixel est représenté par 3 valeurs de RVB de 0 à 255 (Vec3b). Vous pourriez "carte" ces 256 valeurs à seulement 4 valeurs spécifiques, ce qui donnerait 4 x 4 x 4= 64 couleurs possibles.

j'ai eu un ensemble de données, où je devais m'assurer que dark = black, light= blanc et de réduire la quantité de couleurs de tout entre. C'est ce que j'ai fait (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
        }
    }
}

causant [0,64) devenir 0,[64,128) -> 64 et [128,255) -> 255, produisant 27 couleurs:

enter image description hereenter image description here

pour moi, cela semble être soigné, parfaitement clair et plus rapide que tout ce qui est mentionné dans les autres réponses.

Vous pourriez aussi envisager de réduire ces valeurs à l'un des multiples de un certain nombre, disons:

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

ce qui donnerait un ensemble de 5 valeurs possibles: {0, 64, 128, 192, 255}, soit 125 couleurs.

10
répondu LihO 2013-11-23 01:31:11

Ce sujet a été bien couvert sur OpenCV 2 Vision Par Ordinateur De Programmation D'Application De Livre De Cuisine:

Chapitre 2 montre quelques opérations de réduction, dont l'une est illustrée ici en C++:

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

ci-dessous vous pouvez trouver le entrée image (à gauche) et le sortie de cette opération (à droite):

10
répondu karlphillip 2013-12-21 03:23:05

il y a l'algorithme de clustering k-means qui est déjà disponible dans la bibliothèque OpenCV. En bref, il détermine quels sont les meilleurs centroïdes autour desquels regrouper vos données pour une valeur définie par l'utilisateur de k ( = no de clusters). Ainsi, dans votre cas, vous pourriez trouver les centroïdes autour desquels regrouper vos valeurs de pixels pour une valeur donnée de k=64. Les détails sont là si vous cherchez sur google. Ici

quelque Chose de semblable à ce que vous êtes essayez probablement été demandé ici en utilisant k-means, j'espère que ça aidera.

une Autre approche serait d'utiliser le pyramide de décalage filtre fonction OpenCV. Il donne des images un peu" aplaties", c'est-à-dire que le nombre de couleurs est moins élevé, donc il pourrait être en mesure de vous aider.

3
répondu AruniRC 2017-05-23 12:26:15

les réponses suggérées ici sont vraiment bonnes. J'ai pensé que je pourrais ajouter mon idée aussi bien. Je suis la formulation de nombreux commentaires ici, dans lequel il est dit que 64 couleurs peuvent être représentées par 2 bits de chaque canal dans une image RGB.

la fonction en code ci-dessous prend comme entrée une image et le nombre de bits requis pour la quantification. Il utilise la manipulation de bits pour 'laisser tomber' les bits LSB et garder seulement le nombre requis de bits. Le résultat est une méthode souple qui peut quantifier l'image à n'importe quel nombre 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<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;
}

Voici une image qui est utilisée dans l'appel de fonction ci-dessus:

enter image description here

Image quantifiée sur 2 bits pour chaque canal RVB (Total 64 Couleurs):

enter image description here

3 bits pour chaque canal:

enter image description here

4 bits ...

enter image description here

3
répondu masad 2016-10-10 22:28:18

en supposant que vous voulez utiliser les mêmes 64 couleurs pour toutes les images (c'est-à-dire que la palette n'est pas optimisée par image), il y a au moins quelques choix auxquels je peux penser:

1) Convertir En Lab ou YCrCb colorspace et quantize en utilisant N bits pour la luminance et M bits pour chaque canal de couleur, N devrait être plus grand que M.

2) calculez un histogramme 3D des valeurs de couleur sur toutes vos images de formation, puis choisissez les 64 couleurs avec les plus grandes valeurs bin. Quantifiez vos images par en assignant à chaque pixel la couleur la plus proche de la corbeille de l'ensemble de la formation.

la méthode 1 est la plus générique et la plus facile à mettre en œuvre, tandis que la méthode 2 peut être mieux adaptée à votre ensemble de données spécifique.

mise à jour: Par exemple, 32 couleurs est de 5 bits donc assignez 3 bits au canal de luminance et 1 bits à chaque canal de couleur. Pour faire cette quantification, faire une division entière du canal de luminance par 2^8/2^3 = 32 et chaque canal de couleur par 2^8/2^1 = 128. Maintenant il y a seulement 8 différentes valeurs de luminance et 2 canaux de couleurs différentes chacun. Recombinez ces valeurs en un seul entier faisant un décalage de bits ou des maths (valeur de couleur quantifiée = luminance*4 + color1*2+color2);

1
répondu jeff7 2011-05-06 17:19:48

pourquoi ne pas simplement faire la multiplication/division matricielle? Les valeurs seront automatiquement arrondies.

Pseudo:

Convertissez vos canaux en caractères non signés (CV_8UC3),

Diviser par couleurs / couleurs désirées. Mat = Mat / (256/64). Virgule sera tronqué.

Multiplier par le même nombre. Mat = mat * 4

Terminé. Chaque canal ne contient 64 couleurs.

-1
répondu john ktejik 2012-12-29 17:03:17