To do what you need, simply scroll through the pixels of the image using the Mat::at
. This function allows you to access the value of a pixel in a given coordinate x
and y
.
Conversion to Ash
Conversion to shades of gray can be done in a few different ways. The simplest way is with the average method: you basically average the values in each color band (red, green and blue) to get a single luminous intensity value. A slightly better method is the extraction of brightness. In this method, you average only of the highest and lowest values of those bands (thus ignoring that intermediate color). Finally, another method is directly related to how humans perceive the world. The proportions used in this formula are derived from the proportions of the cones (photosensitive cells in the human retina):
Theaveragemethodispracticallythesameasthebrightnessmethod,andtheytendtoreducethecontrast.Thebrightnessmethodworksbestoverall,somuchsothatitisthemostcommonlyusedmethod(includingeditingtoolssuchasGimporPhotoshop).Anotherreasonforittobeusedisthathumansaremoresensitivetogreen,andsothisformofconversionusuallygeneratesimagesthataremorepleasingtohumanperception.
BinaryImage
Onthresholding,theprocessusedtocreateabinaryimage,thisisanequallysimpleprocess.Simplyprocessallthepixelsandchangethemto0
(theequivalentofblack)or255
(theequivalentofwhite)dependingonwhethertheyareaboveorbelowachosenthreshold.Thechoiceofhowtodothisisaconvention,butitusuallyturnsinto0
ifthevalueislessthanorequalandtransformto255
ifitisgreaterthanthethreshold.Choosingthethresholdisbasedonwhatyouwanttodo(forexample,separatingobjectsfromthebackground)andalsothecolordistributionintheimage(usethe color histogram to do this analysis).
Examples
Here is a sample code that does what is explained:
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
// Método da Média: Média dos valores das cores
uchar metodoMedia(Vec3b RGB)
{
uchar azul = RGB.val[0];
uchar verde = RGB.val[1];
uchar vermelho = RGB.val[2];
return (uchar) ((azul + verde + vermelho) / 3);
}
// Método do Brilho: Média dos valores máximos e mínimos das cores
uchar metodoBrilho(Vec3b RGB)
{
uchar azul = RGB.val[0];
uchar verde = RGB.val[1];
uchar vermelho = RGB.val[2];
return (uchar) ((std::max(std::max(azul, verde), vermelho) + std::min(std::min(azul, verde), vermelho)) / 2);
}
// Método da Luminosidade: Ponderação decorrente das proporções médias de cones no olho humano
uchar metodoLuminosidade(Vec3b RGB)
{
uchar azul = RGB.val[0];
uchar verde = RGB.val[1];
uchar vermelho = RGB.val[2];
return (uchar) ((0.21 * vermelho) + (0.72 * verde) + (0.07 * azul));
}
int main()
{
// --------------------------------------------
// Carrega a imagem original
// --------------------------------------------
Mat img = imread("lena.jpg"); // A imagem RGB carregada é CV_8UC3 porque tem três canais, as cores (R + G + B).
int largura = img.size().width;
int altura = img.size().height;
// --------------------------------------------
// Cria uma nova imagem em tons de cinza
// (com o método de luminosidade)
// --------------------------------------------
Mat gray(largura, altura, CV_8UC1); // A nova imagem criada só tem 1 canal (CV_8UC1), a intensidade luminosa.
int x, y;
for(x = 0; x < largura; x++)
{
for(y = 0; y < altura; y++)
{
Vec3b pixel = img.at<Vec3b>(x, y);
uchar intensidade = metodoLuminosidade(pixel);
gray.at<uchar>(x, y) = intensidade;
}
}
// --------------------------------------------
// Cria uma imagem binarizada
// --------------------------------------------
Mat bin(largura, altura, CV_8UC1); // Essa imagem também só tem 1 canal, de preto e branco.
uchar limiar = 128; // Limiar utilizado.
// O método é o seguinte:
// Pixels com luminosidade abaixo do limiar se tornam "preto" (0), e acima se tornam "branco" (255).
for(x = 0; x < largura; x++)
{
for(y = 0; y < altura; y++)
{
Vec3b pixel = img.at<Vec3b>(x, y);
uchar intensidade = metodoLuminosidade(pixel);
if(intensidade <= limiar)
bin.at<uchar>(x, y) = 0;
else
bin.at<uchar>(x, y) = 255;
}
}
namedWindow("Imagem Original");
imshow("Imagem Original",img);
namedWindow("Imagem em Cinza");
imshow("Imagem em Cinza", gray);
namedWindow("Imagem Binária");
imshow("Imagem Binária", bin);
cvWaitKey();
return 0;
}
The result of this code is the following windows:
Althoughthemethodofluminosity(thatderivedfromhumanperception)isthemostused,itisnoticedthattheothermethodsgenerateverysimilarresults.Seetheexamplesbelow(Ididnotputthecodethatgeneratestheseimages,butjustusethethreemethodsthatexistinthepreviouscode):
It should be possible to note that the last image is darker (it has a higher mean luminous intensity) and also more beautiful (although this is a matter of opinion). This is due to the fact that greenness is given more weight in the method of luminosity, as it happens in human perception. Still, there may be some use where the other methods are interesting (when you just want to reduce the contrast between light and dark in the image).
IMPORTANT: Note in the code (especially in the conversion functions
for the various methods) that although I (and you) call the pattern
three RGB bands (because of Red, Green and Blue), OpenCV uses BGR
in the manipulation of your images!