Artifical emphasis of Oppel-Kundt illusion (Oppel-Kundt 錯視の人工的強調)

Toru Nakata (AIST, Japan)

Original Image (原画像) Emphasized Image (錯視強調後)
Oppel-Kundt
toru nakata

 

Avatar face generator デカ目メイクで顔美人に修正

 


Humans, in general, tend to underestimate things that change less or slow. In visual perception, humans underestimate sizes of objects and areas with small fluctuations, so that humans recognize sights with shrinking blank areas and enlarging areas that wear detailed textures. This paper produces a simulation of such subjective visual perception.

人間の空間認知の歪み

人間の感覚と認知は、物理刺激を線形的に知覚するのではなく、物理量を歪めて評価する、いわゆる錯覚が含まれている。空間の認知では、間隔の距離を過小評価する錯覚傾向が知られている。間隔長さの知覚は、知覚にせよ触覚にせよ、長い間隔ほど実際より短めに感じる傾向がある。

つまり、画像の変化が乏しい領域の幅は短く見える傾向がある。一方、変化の激しい領域は実際の大きさ以上に大きいと感じやすい。これを Oppel-Kundt 錯視という。

この特性を模擬するには、画像の変化量が激しい部分から近隣の画像領域に対して斥力を、逆に変化の乏しい部分からは引力を発して、画像を弾性的に変形すればよい。

実験では、画素ごとのラプラシアン(輝度値の空間2次微分)のノルムを画像の変化量として採用した。引力と斥力の大きさは、画素間の距離の自乗に反比例し、画像の変化量に比例する値として計算した。比例定数は画像ごとに適宜調節した。

この変形の結果、画像全体でのラプラシアンの分布は平均化される。

人間の空間知覚と認知は、観測器としてはかなり不正確であるから、見間違えや見落としなどのヒューマンエラーの原因になる。こうしたミスを防ぐためにも、画面や操作盤のヒューマンインタフェースは工夫してデザインする必要がある。

そこで、あるデザインが人間にはどう見えているかというシミュレーションができれば、見やすいデザインの製作の役に立つであろう。単にシミュレーションをするだけでなく、錯覚を大きく強調することで、デザインの弱点もより明らかに発見できるだろう。

Original Image (原画像) Emphasized Image (錯視強調後)
Helmholtz illusion
zoellner illusion
shetland sheep dog
関東戒厳司令部
吾輩は猫である
二本榎出張所 ドイツ表現主義建築
修悦体
石垣
宇宙堂
東京地裁

Source code for MATLAB (henkei.m)

function [] = henkei(ImageFile,ForceMag,CalculationWidth)
% example:  henkei('aaa.jpg', 0.3, 20)
% (C) Toru Nakata 2010


%%%% Load image
OriginalPicture = imread(ImageFile);

CCMotoRed = OriginalPicture(:,:,1);
CCMotoGreen = OriginalPicture(:,:,2);
CCMotoBlue = OriginalPicture(:,:,3);
[SizeX  SizeY] = size(CCMotoRed);
MotoRed = zeros(SizeX, SizeY);
MotoGreen = zeros(SizeX, SizeY);
MotoBlue = zeros(SizeX, SizeY);

for iii = 1:SizeX
    for jjj = 1:SizeY
        MotoRed(iii,jjj) = CCMotoRed(iii,jjj);
    end;
end;
for iii = 1:SizeX
    for jjj = 1:SizeY
        MotoGreen(iii,jjj) = CCMotoGreen(iii,jjj);
    end;
end;
for iii = 1:SizeX
    for jjj = 1:SizeY
        MotoBlue(iii,jjj) = CCMotoBlue(iii,jjj);
    end;
end;


%%%%%%%%%%%%%%% calcurate Laplacian %%%%%%%%%%%%%%%%%%%%%

LapRed = del2(MotoRed);
LapGreen = del2(MotoGreen);
LapBlue = del2(MotoBlue);
LapRed = abs(LapRed);
LapGreen = abs(LapGreen);
LapBlue = abs(LapBlue);
TotalLapMat =  LapRed + LapGreen + LapBlue;

%%%%%%%%% Low Pass Filter for Laplacian
LPFMat = TotalLapMat;
for iii = 2:SizeX-1
    for jjj = 2:SizeY-1
        LPFMat(iii,jjj) = 0.125 * (TotalLapMat(iii,jjj-1) ...
            + TotalLapMat(iii-1,jjj) ...
            + 4 * TotalLapMat(iii,jjj) ...
            + TotalLapMat(iii+1,jjj) ...
            + TotalLapMat(iii,jjj+1));
    end;
end;
TotalLapMat = LPFMat;

%%%%%%%%% Low Pass Filter for Laplacian
LPFMat = TotalLapMat;
for iii = 2:SizeX-1
    for jjj = 2:SizeY-1
        LPFMat(iii,jjj) = 0.125 * (TotalLapMat(iii,jjj-1) ...
            + TotalLapMat(iii-1,jjj) ...
            + 4 * TotalLapMat(iii,jjj) ...
            + TotalLapMat(iii+1,jjj) ...
            + TotalLapMat(iii,jjj+1));
    end;
end;
TotalLapMat = LPFMat;

%%%%%%%%% Low Pass Filter for Laplacian
LPFMat = TotalLapMat;
for iii = 2:SizeX-1
    for jjj = 2:SizeY-1
        LPFMat(iii,jjj) = 0.125 * (TotalLapMat(iii,jjj-1) ...
            + TotalLapMat(iii-1,jjj) ...
            + 4 * TotalLapMat(iii,jjj) ...
            + TotalLapMat(iii+1,jjj) ...
            + TotalLapMat(iii,jjj+1));
    end;
end;
TotalLapMat = LPFMat;

%%%%% modfy Laplacian to Log %%%%%%%%%%%%%%%%%%%%%%%
LogLaplacianMat = TotalLapMat + ones(SizeX,SizeY);
LogLaplacianMat = log(LogLaplacianMat);

OneMat = ones(SizeX,SizeY);
AverageLogLap = mean(mean(LogLaplacianMat));
LogLaplacianMat = LogLaplacianMat - AverageLogLap * OneMat;


%%%%% calc Forces %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

ForceX = zeros(SizeX,SizeY);
ForceY = zeros(SizeX,SizeY);
Kirisute = mean(std(LogLaplacianMat));
%ForceOffset = 0;
ForceOffset = ForceOffset * Kirisute;
Kirisute = Kirisute * 0.3;

for iii = 1:SizeX
    for jjj = 1:SizeY
            
        MotoForce = LogLaplacianMat(iii,jjj) + ForceOffset;
        
        if abs(MotoForce) > Kirisute
        
            startX = iii - CalculationWidth;
            if startX <= 0
                startX = 1;
            end;
            endX = iii + CalculationWidth;
            if endX > SizeX
                endX = SizeX;
            end;

            for kkk = startX:endX
                startY = jjj - CalculationWidth;
                if startY <= 0
                    startY = 1;
                end;
                endY = jjj + CalculationWidth;
                if endY > SizeY
                    endY = SizeY;
                end;

                for lll = startY:endY
                    difX = kkk - iii;
                    difY = lll - jjj;

                    if (difX == 0) & (difY == 0)
                        facfac = 0;
                    else
%                         difnorm = sqrt(difX * difX + difY * difY);
%                         facfac = MotoForce / (difnorm * difnorm * difnorm);
                        facfac = MotoForce / (difX * difX + difY * difY);% 1-order
                    end;
                        ForceX(kkk,lll) = ForceX(kkk,lll) + facfac * difX;
                        ForceY(kkk,lll) = ForceY(kkk,lll) + facfac * difY;
                end;
            end;
        end;
    end;
    if mod(iii, 20) == 0
        ProccessingSoFar = iii/SizeX
    end
end;

%%%%% Move Pixels %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

ForceX = round(ForceMag * ForceX);
ForceY = round(ForceMag * ForceY);

GridX = zeros(SizeX,SizeY);
GridY = zeros(SizeX,SizeY);
for iii = 1:SizeX
    for jjj = 1:SizeY
        GridX(iii,jjj) = iii;
        GridY(iii,jjj) = jjj;
    end;
end;
GridX = GridX + ForceX;%Sekiryoku
GridY = GridY + ForceY;%Sekiryoku

SekiryokuMap = zeros(SizeX,SizeY,3);
SekiryokuPointConde = zeros(SizeX,SizeY);

for iii = 1:SizeX
    for jjj = 1:SizeY
        nowX = round(GridX(iii,jjj));
        nowY = round(GridY(iii,jjj));
        if (nowX > 0) & (nowX <= SizeX) & (nowY > 0) & (nowY <= SizeY)
            SekiryokuMap(nowX,nowY,1) = SekiryokuMap(nowX,nowY,1) + MotoRed(iii,jjj); 
            SekiryokuMap(nowX,nowY,2) = SekiryokuMap(nowX,nowY,2) + MotoGreen(iii,jjj); 
            SekiryokuMap(nowX,nowY,3) = SekiryokuMap(nowX,nowY,3) + MotoBlue(iii,jjj); 
            SekiryokuPointConde(nowX,nowY) = SekiryokuPointConde(nowX,nowY) + 1;
        end;
    end;
end;

tempval = 0;
for iii = 1:SizeX
    for jjj = 1:SizeY
        if SekiryokuPointConde(iii,jjj) > 1
            tempval = SekiryokuPointConde(iii,jjj);
            SekiryokuMap(iii,jjj,1) = SekiryokuMap(iii,jjj,1) / tempval;
            SekiryokuMap(iii,jjj,2) = SekiryokuMap(iii,jjj,2) / tempval;
            SekiryokuMap(iii,jjj,3) = SekiryokuMap(iii,jjj,3) / tempval;
            SekiryokuPointConde(iii,jjj) = 1;
        end;
    end;
end;


%%%%%%%%%%%%%%%%%%%  Fill blank pixels %%%%%%%%%%%%%%%%%%%%%%
tempval = 0;
for kkk = 1:8
    for iii = 2:SizeX-1
        for jjj = 2:SizeY-1
            if SekiryokuPointConde(iii,jjj) == 0
                memmem = ...
                    SekiryokuPointConde(iii-1,jjj-1) + ...
                    SekiryokuPointConde(iii-1,jjj  ) + ...
                    SekiryokuPointConde(iii-1,jjj+1) + ...
                    SekiryokuPointConde(iii  ,jjj-1) + ...
                    SekiryokuPointConde(iii  ,jjj+1) + ...
                    SekiryokuPointConde(iii+1,jjj-1) + ...
                    SekiryokuPointConde(iii+1,jjj  ) + ...
                    SekiryokuPointConde(iii+1,jjj+1);
                if memmem > 9-kkk 
                    tempval = ...
                        SekiryokuMap(iii-1,jjj-1,1) * SekiryokuPointConde(iii-1,jjj-1) + ...
                        SekiryokuMap(iii-1,jjj  ,1) * SekiryokuPointConde(iii-1,jjj  ) + ...
                        SekiryokuMap(iii-1,jjj+1,1) * SekiryokuPointConde(iii-1,jjj+1) + ...
                        SekiryokuMap(iii  ,jjj-1,1) * SekiryokuPointConde(iii  ,jjj-1) + ...
                        SekiryokuMap(iii  ,jjj+1,1) * SekiryokuPointConde(iii  ,jjj+1) + ...
                        SekiryokuMap(iii+1,jjj-1,1) * SekiryokuPointConde(iii+1,jjj-1) + ...
                        SekiryokuMap(iii+1,jjj  ,1) * SekiryokuPointConde(iii+1,jjj  ) + ...
                        SekiryokuMap(iii+1,jjj+1,1) * SekiryokuPointConde(iii+1,jjj+1);
                    SekiryokuMap(iii,jjj,1) = tempval / memmem;
                    tempval = ...
                        SekiryokuMap(iii-1,jjj-1,2) * SekiryokuPointConde(iii-1,jjj-1) + ...
                        SekiryokuMap(iii-1,jjj  ,2) * SekiryokuPointConde(iii-1,jjj  ) + ...
                        SekiryokuMap(iii-1,jjj+1,2) * SekiryokuPointConde(iii-1,jjj+1) + ...
                        SekiryokuMap(iii  ,jjj-1,2) * SekiryokuPointConde(iii  ,jjj-1) + ...
                        SekiryokuMap(iii  ,jjj+1,2) * SekiryokuPointConde(iii  ,jjj+1) + ...
                        SekiryokuMap(iii+1,jjj-1,2) * SekiryokuPointConde(iii+1,jjj-1) + ...
                        SekiryokuMap(iii+1,jjj  ,2) * SekiryokuPointConde(iii+1,jjj  ) + ...
                        SekiryokuMap(iii+1,jjj+1,2) * SekiryokuPointConde(iii+1,jjj+1);
                    SekiryokuMap(iii,jjj,2) = tempval / memmem;
                    tempval = ...
                        SekiryokuMap(iii-1,jjj-1,3) * SekiryokuPointConde(iii-1,jjj-1) + ...
                        SekiryokuMap(iii-1,jjj  ,3) * SekiryokuPointConde(iii-1,jjj  ) + ...
                        SekiryokuMap(iii-1,jjj+1,3) * SekiryokuPointConde(iii-1,jjj+1) + ...
                        SekiryokuMap(iii  ,jjj-1,3) * SekiryokuPointConde(iii  ,jjj-1) + ...
                        SekiryokuMap(iii  ,jjj+1,3) * SekiryokuPointConde(iii  ,jjj+1) + ...
                        SekiryokuMap(iii+1,jjj-1,3) * SekiryokuPointConde(iii+1,jjj-1) + ...
                        SekiryokuMap(iii+1,jjj  ,3) * SekiryokuPointConde(iii+1,jjj  ) + ...
                        SekiryokuMap(iii+1,jjj+1,3) * SekiryokuPointConde(iii+1,jjj+1);
                    SekiryokuMap(iii,jjj,3) = tempval / memmem;
                    SekiryokuPointConde(iii,jjj) = 1;
                end;
            end;
        end;
    end;
end;

%%%%% finishing filling %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
for kkk = 1:8
    for iii = 2:SizeX-1
        for jjj = 2:SizeY-1
            if SekiryokuPointConde(iii,jjj) == 0
                memmem = ...
                    SekiryokuPointConde(iii-1,jjj-1) + ...
                    SekiryokuPointConde(iii-1,jjj  ) + ...
                    SekiryokuPointConde(iii-1,jjj+1) + ...
                    SekiryokuPointConde(iii  ,jjj-1) + ...
                    SekiryokuPointConde(iii  ,jjj+1) + ...
                    SekiryokuPointConde(iii+1,jjj-1) + ...
                    SekiryokuPointConde(iii+1,jjj  ) + ...
                    SekiryokuPointConde(iii+1,jjj+1);
                if memmem > 0 
                    tempval = ...
                        SekiryokuMap(iii-1,jjj-1,1) * SekiryokuPointConde(iii-1,jjj-1) + ...
                        SekiryokuMap(iii-1,jjj  ,1) * SekiryokuPointConde(iii-1,jjj  ) + ...
                        SekiryokuMap(iii-1,jjj+1,1) * SekiryokuPointConde(iii-1,jjj+1) + ...
                        SekiryokuMap(iii  ,jjj-1,1) * SekiryokuPointConde(iii  ,jjj-1) + ...
                        SekiryokuMap(iii  ,jjj+1,1) * SekiryokuPointConde(iii  ,jjj+1) + ...
                        SekiryokuMap(iii+1,jjj-1,1) * SekiryokuPointConde(iii+1,jjj-1) + ...
                        SekiryokuMap(iii+1,jjj  ,1) * SekiryokuPointConde(iii+1,jjj  ) + ...
                        SekiryokuMap(iii+1,jjj+1,1) * SekiryokuPointConde(iii+1,jjj+1);
                    SekiryokuMap(iii,jjj,1) = tempval / memmem;
                    tempval = ...
                        SekiryokuMap(iii-1,jjj-1,2) * SekiryokuPointConde(iii-1,jjj-1) + ...
                        SekiryokuMap(iii-1,jjj  ,2) * SekiryokuPointConde(iii-1,jjj  ) + ...
                        SekiryokuMap(iii-1,jjj+1,2) * SekiryokuPointConde(iii-1,jjj+1) + ...
                        SekiryokuMap(iii  ,jjj-1,2) * SekiryokuPointConde(iii  ,jjj-1) + ...
                        SekiryokuMap(iii  ,jjj+1,2) * SekiryokuPointConde(iii  ,jjj+1) + ...
                        SekiryokuMap(iii+1,jjj-1,2) * SekiryokuPointConde(iii+1,jjj-1) + ...
                        SekiryokuMap(iii+1,jjj  ,2) * SekiryokuPointConde(iii+1,jjj  ) + ...
                        SekiryokuMap(iii+1,jjj+1,2) * SekiryokuPointConde(iii+1,jjj+1);
                    SekiryokuMap(iii,jjj,2) = tempval / memmem;
                    tempval = ...
                        SekiryokuMap(iii-1,jjj-1,3) * SekiryokuPointConde(iii-1,jjj-1) + ...
                        SekiryokuMap(iii-1,jjj  ,3) * SekiryokuPointConde(iii-1,jjj  ) + ...
                        SekiryokuMap(iii-1,jjj+1,3) * SekiryokuPointConde(iii-1,jjj+1) + ...
                        SekiryokuMap(iii  ,jjj-1,3) * SekiryokuPointConde(iii  ,jjj-1) + ...
                        SekiryokuMap(iii  ,jjj+1,3) * SekiryokuPointConde(iii  ,jjj+1) + ...
                        SekiryokuMap(iii+1,jjj-1,3) * SekiryokuPointConde(iii+1,jjj-1) + ...
                        SekiryokuMap(iii+1,jjj  ,3) * SekiryokuPointConde(iii+1,jjj  ) + ...
                        SekiryokuMap(iii+1,jjj+1,3) * SekiryokuPointConde(iii+1,jjj+1);
                    SekiryokuMap(iii,jjj,3) = tempval / memmem;
                    SekiryokuPointConde(iii,jjj) = 1;
                end;
            end;
        end;
    end;
end;


%%%%% Processing for Picture Edges %%%%%%%%%%%%%%%%%%%%%%%%%%%
for iii = 1:SizeX
    if SekiryokuPointConde(iii,1) == 0;
        SekiryokuMap(iii,1,1) = SekiryokuMap(iii,2,1);
        SekiryokuMap(iii,1,2) = SekiryokuMap(iii,2,2);
        SekiryokuMap(iii,1,3) = SekiryokuMap(iii,2,3);
    end;
end;
for iii = 1:SizeX
    if SekiryokuPointConde(iii,SizeY) == 0;
        SekiryokuMap(iii,SizeY,1) = SekiryokuMap(iii,SizeY-1,1);
        SekiryokuMap(iii,SizeY,2) = SekiryokuMap(iii,SizeY-1,2);
        SekiryokuMap(iii,SizeY,3) = SekiryokuMap(iii,SizeY-1,3);
    end;
end;

for jjj = 1:SizeY
    if SekiryokuPointConde(1,jjj) == 0;
        SekiryokuMap(1,jjj,1) = SekiryokuMap(2,jjj,1);
        SekiryokuMap(1,jjj,2) = SekiryokuMap(2,jjj,2);
        SekiryokuMap(1,jjj,3) = SekiryokuMap(2,jjj,3);
    end;
end;
for jjj = 1:SizeY
    if SekiryokuPointConde(SizeX,jjj) == 0;
        SekiryokuMap(SizeX,jjj,1) = SekiryokuMap(SizeX-1,jjj,1);
        SekiryokuMap(SizeX,jjj,2) = SekiryokuMap(SizeX-1,jjj,2);
        SekiryokuMap(SizeX,jjj,3) = SekiryokuMap(SizeX-1,jjj,3);
    end;
end;

%%%%%%%%%%%%% format for picture %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
SekiResultPic = zeros(SizeX,SizeY,3,'uint8');
for iii = 1:SizeX
    for jjj = 1:SizeY
        SekiResultPic(iii,jjj,1) = round(SekiryokuMap(iii,jjj,1));
        SekiResultPic(iii,jjj,2) = round(SekiryokuMap(iii,jjj,2));
        SekiResultPic(iii,jjj,3) = round(SekiryokuMap(iii,jjj,3));
    end;
end;

%%% save %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
imwrite(SekiResultPic,'SekiResultPic.png','PNG');

%%%%%%% show %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
figure(1)
subplot(2,1,1)
imshow(OriginalPicture);
subplot(2,1,2)
imshow(SekiResultPic);

%%%% end