% Non-parametric Texture Synthesis using Efros & Leung's algorithm % Author: Alex Rubinsteyn (alex.rubinsteyn at gmail) % This program is free software; you can redistribute it and/or modify % it under the terms of the GNU General Public License as published by % the Free Software Foundation; either version 2 of the License, or % (at your option) any later version. % % This program is distributed in the hope that it will be useful, % but WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the % GNU General Public License for more details. % % You should have received a copy of the GNU General Public License % along with this program; if not, write to the Free Software % Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA % 02110-1301 USA function [Image, time] = synth(filename, winsize, newRows, newCols) tic MaxErrThreshold = 0.1; rawSample = im2double(imread(filename)); sample = rawSample; [rows, cols, channels] = size(sample); windowlessSize = [(rows - winsize + 1) (cols - winsize + 1)]; halfWindow = (winsize - 1) / 2; npixels = newRows * newCols; Image = zeros(newRows, newCols, 3); red_patches = im2col(sample(:, :, 1), [winsize winsize], 'sliding'); green_patches = im2col(sample(:, :, 2), [winsize winsize], 'sliding'); blue_patches = im2col(sample(:, :, 3), [winsize winsize], 'sliding'); %initialize new texture with a random 3x3 patch from the sample randRow = ceil(rand() * (rows - 2)); randCol = ceil(rand() * (cols - 2)); seedSize = 3; seedRows = ceil(newRows/2):ceil(newRows/2)+seedSize-1; seedCols = ceil(newCols/2):ceil(newCols/2)+seedSize-1; Image(seedRows, seedCols, :) = sample(randRow:randRow+seedSize-1, randCol:randCol+seedSize-1, :); nfilled = seedSize * seedSize; filled = repmat(false, [newRows newCols]); filled(seedRows, seedCols) = repmat(true, [3 3]); gaussMask = fspecial('gaussian',winsize, winsize/6.4); nskipped = 0; while nfilled < npixels progress = false; [pixelRows, pixelCols] = GetUnfilledNeighbors(filled, winsize); for i = 1:length(pixelRows) pixelRow = pixelRows(i); pixelCol = pixelCols(i); rowRange = pixelRow-halfWindow:pixelRow+halfWindow; colRange = pixelCol - halfWindow:pixelCol + halfWindow; deadRows = rowRange < 1 | rowRange > newRows; deadCols = colRange < 1 | colRange > newCols; if sum(deadRows) + sum(deadCols) > 0 safeRows = rowRange(~deadRows); safeCols = colRange(~deadCols); template = zeros(winsize, winsize, 3); template(~deadRows, ~deadCols, :) = Image(safeRows, safeCols, :); validMask = repmat(false, [winsize winsize]); validMask(~deadRows, ~deadCols) = filled(safeRows, safeCols); else template = Image(rowRange, colRange, :); validMask = filled(rowRange, colRange); end [bestMatches, SSD] = FindMatches(template, validMask, gaussMask, red_patches, green_patches, blue_patches); matchIdx = RandomPick(bestMatches); matchError = SSD(matchIdx); if matchError < MaxErrThreshold [matchRow, matchCol] = ind2sub(windowlessSize, matchIdx); %match coords are at corner of window and need to be offset matchRow = matchRow + halfWindow; matchCol = matchCol + halfWindow; Image(pixelRow, pixelCol, :) = sample(matchRow, matchCol, :); filled(pixelRow, pixelCol) = true; nfilled = nfilled + 1; progress = true; else nskipped = nskipped + 1; end end disp(sprintf('Pixels filled: %d / %d', nfilled, npixels)); figure; subplot(2,1,1); imshow(filled); subplot(2,1,2); imshow(Image); if ~progress MaxErrThreshold = MaxErrThreshold * 1.1; disp(sprintf('Incrementing error tolerance to %d', MaxErrThreshold)); end end toc time = toc; %% Get pixels at edge of synthesized texture function [pixelRows, pixelCols] = GetUnfilledNeighbors(filled, winsize) border = bwmorph(filled,'dilate')-filled; [pixelRows, pixelCols] = find(border); len = length(pixelRows); %randomly permute candidate pixels randIdx = randperm(len); pixelRows = pixelRows(randIdx); pixelCols = pixelCols(randIdx); %sort by number of neighbors filledSums = colfilt(filled, [winsize winsize], 'sliding', @sum); numFilledNeighbors = filledSums( sub2ind(size(filled), pixelRows, pixelCols) ); [sorted, sortIndex] = sort(numFilledNeighbors, 1, 'descend'); pixelRows = pixelRows(sortIndex); pixelCols = pixelCols(sortIndex); %% Pick a random pixel from valid patches function idx = RandomPick(matches) indices = find(matches); idx = indices(ceil(rand() * length(indices))); %% Find candidate patches that match template function [pixelList, SSD] = FindMatches (template, validMask, gaussMask, red_patches, green_patches, blue_patches) ErrThreshold = 0.3; [pixels_per_patch, npatches] = size(red_patches); totalWeight = sum(sum(gaussMask(validMask))); mask = (gaussMask .* validMask) / totalWeight; mask_vec = mask(:)'; red = reshape(template(:, :, 1), [pixels_per_patch 1]); green = reshape(template(:, :, 2), [pixels_per_patch 1]); blue = reshape(template(:, :, 3), [pixels_per_patch 1]); red_templates = repmat(red, [1 npatches]); green_templates = repmat(green, [1 npatches]); blue_templates = repmat(blue, [1 npatches]); red_dist = mask_vec * (red_templates - red_patches).^2; green_dist = mask_vec * (green_templates - green_patches).^2 ; blue_dist = mask_vec * (blue_templates - blue_patches).^2; SSD = (red_dist + green_dist + blue_dist); pixelList = SSD <= min(SSD) * (1+ErrThreshold);