Como posso converter uma imagem RGB em escala de cinza, mas manter uma cor?

Eu estou tentando criar um efeito semelhante ao de Sin City ou outros filmes, onde eles removem todas as colors, exceto uma de uma imagem.

Eu tenho uma imagem RGB que eu quero converter em escala de cinza, mas eu quero manter uma cor.

Esta é minha foto:

texto alternativo

Eu quero manter a cor vermelha. O resto deve ser em escala de cinza.

Isto é o que o meu código produz até agora (você pode ver que as áreas estão corretas, eu não sei porque elas são brancas em vez de vermelhas):

texto alternativo

Aqui esta o meu codigo ate agora:

filename = 'roses.jpg'; [cdata,map] = imread( filename ); % convert to RGB if it is indexed image if ~isempty( map ) cdata = idx2rgb( cdata, map ); end %imtool('roses.jpg'); imWidth = 685; imHeight = 428; % RGB ranges of a color we want to keep redRange = [140 255]; greenRange = [0 40]; blueRange = [0 40]; % RGB values we don't want to convert to grayscale redToKeep = zeros(imHeight, imWidth); greenToKeep = zeros(imHeight, imWidth); blueToKeep = zeros(imHeight, imWidth); for x=1:imWidth for y=1:imHeight red = cdata( y, x, 1 ); green = cdata( y, x, 2 ); blue = cdata( y, x, 3 ); if (red >= redRange(1) && red = greenRange(1) && green = blueRange(1) && blue <= blueRange(2)) redToKeep( y, x ) = red; greenToKeep( y, x ) = green; blueToKeep( y, x ) = blue; else redToKeep( y, x ) = 999; greenToKeep( y, x ) = 999; blueToKeep( y, x ) = 999; end end end im = rgb2gray(cdata); [X, map] = gray2ind(im); im = ind2rgb(X, map); for x=1:imWidth for y=1:imHeight if (redToKeep( y, x ) < 999) im( y, x, 1 ) = 240; end if (greenToKeep( y, x ) < 999) im( y, x, 2 ) = greenToKeep( y, x ); end if (blueToKeep( y, x ) < 999) im( y, x, 3 ) = blueToKeep( y, x ); end end end imshow(im); 

 figure pic = imread('EcyOd.jpg'); for mm = 1:size(pic,1) for nn = 1:size(pic,2) if pic(mm,nn,1) < 80 || pic(mm,nn,2) > 80 || pic(mm,nn,3) > 100 gsc = 0.3*pic(mm,nn,1) + 0.59*pic(mm,nn,2) + 0.11*pic(mm,nn,3); pic(mm,nn,:) = [gsc gsc gsc]; end end end imshow(pic) 

texto alternativo

Uma opção que melhora muito a qualidade da imagem resultante é convertê-la em um espaço de cor diferente para selecionar mais facilmente suas colors. Em particular, o espaço de colors do HSV define colors de pixel em termos de matiz (a cor), saturação (a quantidade de cor) e valor (o brilho da cor).

Por exemplo, você pode converter sua imagem RGB em HSV usando a function rgb2hsv , encontrar pixels com matizes que abrangem o que você deseja definir como colors “não vermelhas” (como, por exemplo, 20 a 340 graus), definir a saturação para aqueles pixels a 0 (então eles são em tons de cinza), então converta a imagem de volta para o espaço RGB usando a function hsv2rgb :

 cdata = imread('EcyOd.jpg'); % Load image hsvImage = rgb2hsv(cdata); % Convert the image to HSV space hPlane = 360.*hsvImage(:, :, 1); % Get the hue plane scaled from 0 to 360 sPlane = hsvImage(:, :, 2); % Get the saturation plane nonRedIndex = (hPlane > 20) & ... % Select "non-red" pixels (hPlane < 340); sPlane(nonRedIndex) = 0; % Set the selected pixel saturations to 0 hsvImage(:, :, 2) = sPlane; % Update the saturation plane rgbImage = hsv2rgb(hsvImage); % Convert the image back to RGB space 

E aqui está a imagem resultante:

texto alternativo

Observe como, comparado à solução de zellus , você pode facilmente manter os tons rosa claro nas flores. Observe também que tons acastanhados no caule e no chão também desapareceram.

Para um exemplo interessante de selecionar objects de uma imagem com base em suas propriedades de colors, você pode conferir a postagem no blog de Steve Eddins, The Two Friends, que descreve uma solução de Brett Shoelson no MathWorks para extrair um "amigo" de uma imagem.

Uma nota sobre a seleção de intervalos de colors ...

Uma coisa adicional que você pode fazer, que pode ajudá-lo a selecionar intervalos de colors, é olhar para um histograma dos matizes (ou seja, hPlane de cima) presentes nos pixels de sua imagem HSV. Aqui está um exemplo que usa as funções histc (ou as histcounts recomendadas, se disponíveis) e bar :

 binEdges = 0:360; % Edges of histogram bins hFigure = figure(); % New figure % Bin pixel hues and plot histogram: if verLessThan('matlab', '8.4') N = histc(hPlane(:), binEdges); % Use histc in older versions hBar = bar(binEdges(1:end-1), N(1:end-1), 'histc'); else N = histcounts(hPlane(:), binEdges); hBar = bar(binEdges(1:end-1), N, 'histc'); end set(hBar, 'CData', 1:360, ... % Change the color of the bars using 'CDataMapping', 'direct', ... % indexed color mapping (360 colors) 'EdgeColor', 'none'); % and remove edge coloring colormap(hsv(360)); % Change to an HSV color map with 360 points axis([0 360 0 max(N)]); % Change the axes limits set(gca, 'Color', 'k'); % Change the axes background color set(hFigure, 'Pos', [50 400 560 200]); % Change the figure size xlabel('HSV hue (in degrees)'); % Add an x label ylabel('Bin counts'); % Add ay label 

E aqui está o histograma de colors de pixel resultante:

texto alternativo

Observe como a imagem original contém principalmente pixels coloridos em vermelho, verde e amarelo (com alguns pixels cor de laranja). Quase não há pixels coloridos em ciano, azul, índigo ou magenta. Observe também que os intervalos que eu selecionei acima (20 a 340 graus) fazem um bom trabalho de excluir quase tudo que não faz parte dos dois grandes clusters vermelhos em cada extremidade.

Eu realmente não sei como funciona o matlab, então não posso realmente comentar sobre o código, mas talvez isso ajude a explicar um pouco como as colors RGB funcionam.

Ao usar colors RGB, uma escala de cinza pode ser feita certificando-se de que os valores de R, G e B sejam os mesmos. Então, basicamente, o que você quer fazer é detectar se um pixel é vermelho, quando não apenas tornar R, G e B iguais (você pode usar uma média dos 3 para um resultado rudimentar).

Parte mais difícil é como detectar se um pixel é realmente vermelho, você não pode apenas verificar se um pixel é alto no valor R, pois ele ainda pode ter outra cor, e um valor baixo de R pode significar apenas um vermelho mais escuro.

então você poderia fazer algo assim: (Eu não tenho matlab, assumindo syntax):

 vermelho = cdata (y, x, 1);
 verde = cdata (y, x, 2);
 azul = cdata (y, x, 3);

 if (vermelho < (azul * 1.4) || vermelho <(verde * 1.4))
 {
     avg = (vermelho + verde + azul) / 3;
     cdata (y, x, 1) = avg;
     cdata (y, x, 2) = avg;
     cdata (y, x, 3) = avg;
 }

Existem provavelmente melhores maneiras de detectar o vermelho e obter um cinza médio, mas é um começo;)