画像にノイズを混ぜる、ぼかす

Liu et al. (2014). Seeing Jesus in toast: Neural and behavioral correlates of face pareidolia で使われた刺激を作成するプログラムです。

論文を元に、そして不明な点は論文の著者にメールでたずねながらプログラムを作成しましたが、以下に公開するコードの動作を論文の著者が保証しているわけではありません。利用される場合はその点にご注意ください。

1枚の画像を作成するのに1分近くかかると思います。ノイズと合成したい顔画像、あるいは文字画像をフォルダに保存して、inputFolderを書き換えてください。

LinuxのOctaveで動作することを確認しています。Matlabでは、mvnpdf関数を使うために Statistics and Machine Learning Toolbox が、fspecial, imfilter関数を使うために Image Processing Toolbox が必要になりますが、Octaveでは pkg load statistics image とすることで無料でこれらの関数を使用することができます。

画像をぼかすだけであれば(ノイズを追加しないのであれば)次のコードだけで大丈夫です。

    gaussSigma = 7;
    filtersize = 2*ceil(2*gaussSigma)+1 % https://jp.mathworks.com/help/images/ref/imgaussfilt.html
    GF = fspecial('gaussian', filtersize, gaussSigma); % Gaussian Filter
    TL = imfilter(TF, GF);

以下が、すべてのプログラムです。

% Original paper
% Seeing Jesus in toast: Neural and behavioral correlates of face pareidolia
% https://www.sciencedirect.com/science/article/pii/S0010945214000288
%
% Please be careful that this code was not written by the authors.
% Daiichiro Kuroki who worked as a technical staff in Kyushu University
% made it by reading the paper and asking the authors.
% The authors and I do not grantee that the program works properly.
%
% Before running the program, face and letter images without noise must be
% saved in two separate folders.
% Be careful that the face-noise and letter-noise images could not be made
% at the same time. You have to set the inputFolder respectively.
%
% You have to change the extension of image files according to your
% stimuli. i.e., imgFileList.
% Moreover, please also check 'stimType' and 'difficulty'.
%
% This program could run in Octave under Ubuntu.
% If you use Matlab, both 'the Statistics and Machine Learning Toolbox' and
% 'the Image Processing Toolbox' are needed.
%
% It takes about 1 minute to make a noise image!
 
function makeImgWithNoise4
 
pkg load statistics image
%pkg load statistics
clear all;
 
% Be careful. Please check also Sigma, lambda, and gamma
inputFolder = ['images' filesep]; % folder name
imgFileList = dir([inputFolder '*.jpg']); % bmp, jpg, jpeg, or png?
stimType = 1; % 1=face, 2=charactor, 3=checker board
difficulty = 1; % 1=easy to detect, 2=hard to detect 
outputFolder = 'output';
if ~exist(outputFolder, 'dir')
    mkdir(outputFolder);
end
checkerBoardWidth = 40; % mod(noiseWidth, checkerBoardWidth) must be zero.
checkerColor1 = [100 100 100];
checkerColor2 = [200 200 200];
 
% These width/height should be greater than the width/heigth of images.
noiseWidth = 480;
noiseHeight = 480;
%noiseWidth = 800;
%noiseHeight = 700;
 
% PTB-3 correctly installed and functional? Abort otherwise.
AssertOpenGL;
%Screen('Preference', 'SkipSyncTests', 1); 
 
% Select screen with maximum id for output window:
screenid = max(Screen('Screens'));
 
% Open a fullscreen, onscreen window with gray background. Enable 32bpc
% floating point framebuffer via imaging pipeline on it, if this is possible
% on your hardware while alpha-blending is enabled. Otherwise use a 16bpc
% precision framebuffer together with alpha-blending. We need alpha-blending
% here to implement the nice superposition of overlapping gabors. The demo will
% abort if your graphics hardware is not capable of any of this.
PsychImaging('PrepareConfiguration');
PsychImaging('AddTask', 'General', 'FloatingPoint32BitIfPossible');
%[win winRect] = PsychImaging('OpenWindow', screenid, 128);
 
% Color value seems to be from 0 to 255 not from 0.0 to 1.0 at least in Linux.
% To present a sin-wave gabor, the background color should be grey.
%[win winRect] = PsychImaging('OpenWindow', screenid, 0);
[win, winRect] = PsychImaging('OpenWindow', screenid, 0, [0 0 1000 800] );
 
% Retrieve size of window in pixels, need it later to make sure that our
% moving gabors don't move out of the visible screen area:
[w, h] = RectSize(winRect);
centerX = w/2;
centerY = h/2;
% Query frame duration: We use it later on to time 'Flips' properly for an
% animation with constant framerate:
ifi = Screen('GetFlipInterval', win);
 
nImages = size(imgFileList, 1)
 
if stimType == 3 % checker board
  if mod(noiseWidth, checkerBoardWidth) ~= 0
    error('Please check the checkerBoardWidth');
  end
  
  if mod(noiseHeight, checkerBoardWidth) ~= 0
    error('Please check the checkerBoardWidth');
  end
    
  for j = 1:noiseHeight/checkerBoardWidth
    if mod(j,2) == 0
      colorType = 1;
    else
      colorType = 2;
    end
    
    for i = 1:noiseWidth/checkerBoardWidth   
      tmpRect = [(i-1)*checkerBoardWidth, (j-1)*checkerBoardWidth, i*checkerBoardWidth, j*checkerBoardWidth];
      if colorType == 1
        tmpColor = checkerColor1;
        colorType = 2;
      else
        tmpColor = checkerColor2;
        colorType = 1;
      end
      
      Screen('FillRect', win, tmpColor, tmpRect);
    end
  end
  Screen('Flip', win);
  imageArray = Screen('GetImage', win, [0, 0, noiseWidth, noiseHeight]);
  imwrite(imageArray, [outputFolder filesep 'checkr-board.jpg']);
  sca;
  return
end
 
for k = 1:nImages
  imgFileName = [inputFolder imgFileList(k).name]
 
  x1 = 1:noiseWidth;
  x2 = 1:noiseHeight;
  [X1, X2] = meshgrid(x1, x2);
 
  vNoise = zeros(length(X1(:)), 1); % Initialize (vNoise is equal to zeros(length(X2(:)), 1)
  for i = 1:3
      % The 'sd' written in the paper seems to be actually covariance.
      if i == 1
        sd = 64;
        contrast = 1;
      elseif i == 2
        sd = 256;
        contrast = 5;
      else
        sd = 1024;
        contrast = 25;
      end
      Sigma = eye(2).*sd;
      ngabors = round((noiseWidth/(sd*0.15))^2);
      %ngabors = 10;
 
      for j = 1:ngabors
        mu = [rand(1)*noiseWidth, rand(1)*noiseHeight];
        F = contrast.*mvnpdf([X1(:), X2(:)], mu, Sigma);
        vNoise = vNoise + F;
      end
      
      % for checking
%      noiseMatrix = reshape(vNoise, length(x2), length(x1))./max(vNoise);
%      gabortex=Screen('MakeTexture', win, noiseMatrix, [], [], 2);
%      Screen('DrawTextures', win, gabortex);  
%      Screen('Flip', win);
%      KbStrokeWait;
  end
 
  noiseMatrix = reshape(vNoise, length(x2), length(x1))./max(vNoise); % normalize from 0 to 1
  NI = (1 - noiseMatrix).*255; % reverse
      
  imdata = imread(imgFileName);    
 
  TF = zeros(noiseHeight, noiseWidth); % True Face
  LX = round((noiseWidth - size(imdata,2))/2)+1;
  LY = round((noiseHeight - size(imdata,1))/2)+1;
  %TF(LY:LY+size(imdata,1)-1, LX:LX+size(imdata,2)-1) = imdata(:,:,1);
  TF(LY:LY+size(imdata,1)-1, LX:LX+size(imdata,2)-1) = imdata;
 
  %imagetex = Screen('MakeTexture', win, TF);
  %Screen('DrawTexture', win, imagetex);
  %Screen('Flip', win);
  %KbStrokeWait;  
 
  if stimType == 1 % face 
    if difficulty == 1 % easy to detect
      Sigma = eye(2).*2400;
      lambda = 0.3; 
    else % hard to detect
      Sigma = eye(2).*(10^9);
      lambda = 0.9999997; 
    end
    
    mu = [noiseWidth/2, noiseHeight/2];
    
    F = mvnpdf([X1(:), X2(:)], mu, Sigma);
    F = F./max(F);
    F = reshape(F, length(x2), length(x1));
    gabor_brob = F;
    whos m gabor_brob NI TF; 
 
    B = lambda.*(1-gabor_brob)+(1-lambda).*gabor_brob;
    FN = lambda.*NI.*(1-gabor_brob) + (1-lambda).*TF.*gabor_brob;
    FN = FN./B;
    outputData = FN;
    
  elseif stimType == 2 % charactor
    if difficulty == 1 % easy to detect
      gamma = 0.6;
    else
      gamma = 0.35; % hard to detect (still easy)
    end
    
    % Blurring a letter. Although this is not written in the paper, the authors told me directly.
    gaussSigma = 7;
    filtersize = 2*ceil(2*gaussSigma)+1 % https://jp.mathworks.com/help/images/ref/imgaussfilt.html
    GF = fspecial('gaussian', filtersize, gaussSigma); % Gaussian Filter
    TL = imfilter(TF, GF);
    
    %TL = TF; % without blurring
    
    LN = NI - (158-TL).*gamma; % Negative values are possible.
    outputData = uint8(LN);
  else
  end
  
  newTex = Screen('MakeTexture', win, outputData);
  Screen('DrawTexture', win, newTex);
  Screen('Flip', win); % image with gabor
  %KbStrokeWait;
  
  %imageArray = Screen('GetImage', win, [centerX-noiseWidth/2, centerY-noiseHeight/2, centerX+noiseWidth/2, centerY+noiseHeight/2]);
  [pathstr, imgFilename2, ext] = fileparts(imgFileName);
  imwrite(uint8(outputData), [outputFolder filesep imgFilename2 '-withNoise.jpg']);
  
  Screen('Close');
  %error('stop');
end
 
% Close onscreen window, release all ressources:
sca;
 
% Done.
return;
 
% Little helper function: Converts degrees to radians, included for
% backward compatibility with old Matlab versions:
% function radians = deg2rad(degrees)
%     radians = degrees * pi / 180;
% return