how to take an average of a 2x2 pixel block? - matlab

I have an image and I have to take the average of the color of 2x2 pixel.
take the average color of a 2x2 pixel block, then compute the distance between the colors in the image and the colors available for our use.
I have no idea what taking the average of 2x2 pixel block means. How to solve this?
Thank you.

You can process non-overlapping blocks of an image using blockproc
Im = imread('coins.png'); %// example image
fun = #(block_struct) mean( block_struct.data(:) ); %// anonymous function to get average of a block
B = blockproc(Im,[2 2],fun); %// process 2 by 2 blocks
imshow(B,[]); %// show resulting image

One other method I can suggest is to use combination of colfilt with the 'sliding' flag and mean as the function to operate on. The 'distinct' flag is what you actually need to use, but if you see the conversation between myself and #eigenchris, we couldn't get it to work. Still, eigenchris has shown that this is 300x faster than blockproc.
Therefore, assuming your image is stored in im, you can simply do:
out = uint8(colfilt(im, [2 2], 'sliding', #mean));
out2 = out(1:2:end,1:2:end);
The reason why you would need to subsample the results is because when we apply a sliding option, you have overlapping blocks processing the image at a time. Because you want distinct blocks, you only need 1/4 of the image because you have decomposed the image into 2 x 2 blocks. Within a 2 x 2 block, if you did the sliding option, you would have three other results that are not required, and so doing the subsampling above by a factor of two eliminates those three other blocks that give results.
Note that you'll need to cast the result as the output will be of type double.
Going with the discussion between myself and eigenchris, you can replace colfilt with the canonical imfilter to replicate the first line of the above code. I managed to get an 8x speedup on my machine when comparing the two together. Therefore:
out = imfilter(im, [0.25 0.25; 0.25 0.25], 'replicate');
out2 = out(1:2:end,1:2:end);
In terms of speedup, I wrapped each call in an anonymous function, then used timeit to time the functions:
>> f = #() uint8(colfilt(im, [2 2], 'sliding', #mean));
>> g = #() imfilter(im, [0.25 0.25; 0.25 0.25], 'replicate');
>> T = timeit(f);
>> T2 = timeit(g);
>> T/T2
ans =
7.5421
As you can see, there is roughly a 8x speedup over colfilt... most likely because it's calling im2col and col2im under the hood.

Using loops: (another method, just for knowing)
A = imread('cameraman.tif');
i = 1; j = 1;
[rows, cols] = size(A);
C(rows/2,cols/2) = 0;
for x = 1:2:rows
for y = 1:2:cols
block = [A(x,y),A(x+1,y),A(x,y+1),A(x+1,y+1)];
C(i,j) = mean(block(:));
j = j+1;
end
i = i+1;
j = 1;
end
C = uint8(C);
figure;
imshow(A); %// show original image
figure;
imshow(C); %// show resulting image

Related

Laplacian filter returning very black image

I'm trying to apply a local Laplacian filter to an image using blockproc but the result I get is far darker than the result in the textbook. I've seen some other posts about this same problem but I'm not sure what exactly I'm failing to grasp.
My code:
img1 = im2double(imread('Fig0352(a)(blurry_moon).tif'));
kernel = [0 1 0; 1 -4 1; 0 1 0];
%generate laplacian image
fun = #(block_struct)laplacian_kernel(block_struct.data, kernel);
img2 = blockproc(img1, [3 3], fun, 'PadPartialBlocks', true, 'TrimBorder', true);
img3 = img2(:, 1:end-2);%crop extra two columns added by last passthrough of blockproc
img4 = imsubtract(img1,img3);%get enhanced image
%display
figure;
subplot(2,2,1);
imshow(img1)
subplot(2,2,2);
imshow(img3);
subplot(2,2,3);
imshow(img4);
function f = laplacian_kernel(img, kernal)
%want to pad this 3x3 chunk of the image so we don't exceed the bounds of
%the array when doing laplacian
img1 = padarray(img, [1 1]);
out = zeros(size(img1));
for i = 2 : (length(img1)-1)
for j = 2 : (length(img1)-1)
neighborhood = img1(i-1: i+1, j-1: j+1);%get neighborhood
out(i,j)= sum(neighborhood .* kernal, 'all');
end
end
f = out(2:length(img1)-1, 2:length(img1)-1);%remove padding
end
And the image I'm getting is
The laplacian is the image on the top right, which is far darker than what is shown in the book.
The image from the book looks much more like this:
I know it's still very dark (and a small image, sorry) but there is much more detail to it than what I'm producing with my custom code. What am I doing wrong with my laplacian? I've tried rewriting the code multiple times but always get more or less the same result.
If I use nlfilter, my result matches that in the book. However, it takes so much more time I don't like to use it. I'm clearly stumbling when piecing the blocks back together with blockproc but I can't conceptualize where I'm going wrong. Here are my results when using nlfilter:
img1 = im2double(imread('Fig0352(a)(blurry_moon).tif'));
kernel = [0 1 0; 1 -4 1; 0 1 0];
k2 = [1 1 1; 1 -8 1; 1 1 1];
fun = #(x)laplacian_kernel(x, kernel);
img2 = nlfilter(img1, 'indexed', [3 3], fun);
img3 = imsubtract(img1, img2);
figure;
subplot(1,3,1);
imshow(img1);
title('original image');
subplot(1,3,2);
imshow(img2);
title('laplacian using nlfilter');
subplot(1,3,3);
imshow(img3);
title('enhanced image');
function f = laplacian_kernel(img, kernal)
f = sum(img.*kernal, 'all');
end
With output:
Clearly, the results from nlfilter are much better. I'm just not sure where I'm going wrong with blockproc.
The typical output of a Laplace operator is very dark, and approximately half of its pixels have a negative value. To display it properly, instead of imshow(img3), use imshow(img3,[]). This will scale the image so its minimum value is black and its maximum value is white. Most pixels will now be middle-gray with positive pixels closer to white and negative pixels closer to black.
But you have a bigger problem in the code: by processing each 3x3 block with zero padding, only one pixel in that block has the right value, the other 8 pixels sample some of that padding. You want to use the BorderSize option in blockproc instead of your zero padding:
img2 = blockproc(img1, [3 3], #(s)conv2(s.data, kernel, 'same'), ...
'PadPartialBlocks', false, ...
'BorderSize', [1,1], 'TrimBorder', true);
img3 = img1 - img2;
We’re using conv2 now, because that’s all we need, there’s no need to re-invent the wheel. But if you do want to use your own code for filtering, consider swapping your two loops, because it’s more efficient that way, and use size(img,1) and size(img,2) instead of length(img).
Note that not padding partial blocks should make your subsequent image trimming unnecessary.
Using bigger blocks than 3x3 will make this more efficient.
To emulate conv2 with blockproc, use 1x1 blocks with a 1-pixel border (giving a 3x3 window into the image), and use the dot product with the kernel as your block processing function:
img2 = blockproc(img1, [1 1], #(s)sum(s.data(:).*kernel(:)), ...
'BorderSize', [1,1], 'TrimBorder', false);
img3 = img1 - img2;
This is obviously not at all fast, writing a convolution by hand as a double loop results in code about 5x as fast as using blockproc.

How to reduce for loops in a moving window based operation?

How to reduce for loops in a moving window based operation? I'm using a 15x15 window across two images and performing multiplication to get average value per pixel.
[ma,na]=size(g);
z= (win1 -1)/2;%centre of window
ini=z+1;
for i= ini :(ma-z)
for j= ini:(na-z)
for a= (i-z):(i+z)
for b=(j-z):(j+z)
W(pp,qq)= g(a, b);%window on image
Es(pp,qq)=edg(a,b);%window on edge
qq=qq+1;
end
qq=1;
pp=pp+1;
end
pp=1;
E(i,j)=sum(sum(W.*Es))/sum(sum(Es));
end
end
I might have gotten lost in your loops and i can't exactly read the formula (it's a bit fuzzy) but i think this is what you want:
g = rand(5); %sample img1
edg = rand(5); %sample img2
windowsize = 3; %set this to 15 for real images
A = g.*edg; % multiply each element beforehand, corresponds to mu*sigma in formula
B = movsum(movsum(A,windowsize,2),windowsize,1); % get moving window sum of A, corresponds to numerator in formula
C = movsum(movsum(edg,windowsize,2),windowsize,1); % get moving window sum of edg, corresponds to denominator in formula
E = B./C; %hopefully what you wanted
Ps: You need 2016a or newer to run this

Image histogram implementation with Matlab

I'm tyring to implement (I know there's a custom function for achieving it) the grayscale image histogram in Matlab, so far I've tried:
function h = histogram_matlab(imageSource)
openImage = rgb2gray(imread(imageSource));
[rows,cols] = size(openImage);
histogram_values = [0:255];
for i = 1:rows
for j = 1:cols
p = openImage(i,j);
histogram_values(p) = histogram_values(p) + 1;
end
end
histogram(histogram_values)
However when I call the function, for example: histogram_matlab('Harris.png')
I obtain some graph like:
which is obviously not what I expect, x axis should go from 0 to 255 and y axis from 0 to whatever max value is stored in histogram_values.
I need to obtain something like what imhist offers:
How should I set it up? Am I doing a bad implementation?
Edit
I've changed my code to improvements and corrections suggested by #rayryeng:
function h = histogram_matlab(imageSource)
openImage = rgb2gray(imread(imageSource));
[rows,cols] = size(openImage);
histogram_values = zeros(256,1)
for i = 1:rows
for j = 1:cols
p = double(openImage(i,j)) +1;
histogram_values(p) = histogram_values(p) + 1;
end
end
histogram(histogram_values, 0:255)
However the histogram plot is not that expected:
Here it's noticeable that there's some issue or error on y axis as it definitely would reach MORE than 2.
In terms of calculating the histogram, the computation of the frequency per intensity is correct though there is a slight error... more on that later. Also, I would personally avoid using loops here. See my small note at the end of this post.
Nevertheless, there are three problems with your code:
Problem #1 - Histogram is not initialized properly
histogram_values should contain your histogram, yet you are initializing the histogram by a vector of 0:255. Each intensity value should start with a count of 0, and so you actually need to do this:
histogram_values = zeros(256,1);
Problem #2 - Slight error in for loop
Your intensities range from 0 to 255, yet MATLAB starts indexing at 1. If you ever get intensities that are 0, you will get an out-of-bounds error. As such, the proper thing to do is to take p and add it with 1 so that you start indexing at 1. However, one intricacy I need to point out is that if you have a uint8 precision image, adding 1 to an intensity of 255 will simply saturate the value to 255. It won't go to 256.... so it's also prudent that you cast to something like double to ensure that 256 will be reached.
Therefore:
histogram_values = zeros(256,1);
for i = 1:rows
for j = 1:cols
p = double(openImage(i,j)) + 1;
histogram_values(p) = histogram_values(p) + 1;
end
end
Problem #3 - Not calling histogram right
You should override the behaviour of histogram and include the edges. Basically, do this:
histogram(histogram_values, 0:255);
The second vector specifies where we should place bars on the x-axis.
Small note
You can totally implement the histogram computation yourself without any for loops. You can try this with a combination of bsxfun, permute, reshape and two sum calls:
mat = bsxfun(#eq, permute(0:255, [1 3 2]), im);
h = reshape(sum(sum(mat, 2), 1), 256, 1);
If you'd like a more detailed explanation of how this code works under the hood, see this conversation between kkuilla and myself: https://chat.stackoverflow.com/rooms/81987/conversation/explanation-of-computing-an-images-histogram-vectorized
However, the gist of it as below.
The first line of code creates a 3D vector of 1 column that ranges from 0 to 255 by permute, and then using bsxfun with the eq (equals) function, we use broadcasting so that we get a 3D matrix where each slice is the same size as the grayscale image and gives us locations that are equal to an intensity of interest. Specifically, the first slice tells you where elements are equal to 0, the second slice tells you where elements are equal to 1 up until the last slice where it tells you where elements are equal to 255.
For the second line of code, once we compute this 3D matrix, we compute two sums - first summing each row independently, then summing each column of this intermediate result. We then get the total sum per slice which tells us how many values there were for each intensity. This is consequently a 3D vector, and so we reshape this back into a single 1D vector to finish the computation.
In order to display a histogram, I would use bar with the histc flag. Here's a reproducible example if we use the cameraman.tif image:
%// Read in grayscale image
openImage = imread('cameraman.tif');
[rows,cols] = size(openImage);
%// Your code corrected
histogram_values = zeros(256,1);
for i = 1:rows
for j = 1:cols
p = double(openImage(i,j)) + 1;
histogram_values(p) = histogram_values(p) + 1;
end
end
%// Show histogram
bar(0:255, histogram_values, 'histc');
We get this:
Your code looks correct. The problem is with the call to histogram. You need to supply the number of bins in the call to histogram, otherwise they will be computed automatically.
Try this simple modification which calls stem to get the right plot, instead of relying on histogram
function h = histogram_matlab(imageSource)
openImage = rgb2gray(imread(imageSource));
[rows,cols] = size(openImage);
histogram_values = [0:255];
for i = 1:rows
for j = 1:cols
p = openImage(i,j);
histogram_values(p) = histogram_values(p) + 1;
end
end
stem(histogram_values); axis tight;
EDIT: After some inspection of the code you have a 0/1 error. If you have a pixel of value zero then histogram_value(p) will give you an index error
Try this instead. No need for vectorization for this simple case:
function hv = histogram_matlab_vec(I)
assert(isa(I,'uint8')); % for now we assume uint8 with range [0, 255]
hv = zeros(1,256);
for i = 1 : numel(I)
p = I(i);
hv(p + 1) = hv(p + 1) + 1;
end
stem(hv); axis tight;
end

How can thin horizontal lines be added between each row in a grayscale image?

I need to create an nth-order Hadamard matrix, row double it, within each row randomly permute the elements of the matrix, and then display it. So far, I have accomplished all of these things. What I end up with when I imshow(matrix) is a nice picture of black and white boxes. But I haven't figured out how to insert a fine line to divide each row. I can create something like the first image on the left, but not the image on the right (these are Figures 1 and 2 from this paper)
Any help or comments would be thoroughly appreciated.
I've found using vector approaches (e.g., patch and rectangle) for this sort of problem unnecessarily challenging. I think that it's more straightforward to build a new image. This avoids floating-point rounding issues and other things that crop up with vector graphics. My solution below relies on some functions in the Image Processing Toolbox, but is simple and fast:
% Create data similarly to #TryHard
H = hadamard(48);
C = (1+[H;-H])/2;
rng(0); % Set seed
C(:) = C(randperm(numel(C))); % For demo, just permute all values, not rows
% Scale image and lines
scl = 10; % Amount to vertically scale each row
pad = 2; % Number of pixels to add between each row
C = imresize(C,scl,'nearest');
C = blockproc(C,[scl size(C,2)],#(x)[x.data;zeros(pad,size(C,2))]);
C = C(1:end-pad,:); % Remove last line added
% Dispay image
imshow(C)
This results in an image like this
The scl and pad parameters can be easily adjusted to obtain different sizes and relative sizes. You can call imresize(...,'nearest') again after adding the lines to further scale the image if desired. The blocproc line could potentially be made more efficient with various options (see the help). It could also be replaced by calls to im2col and col2im, which possibly could be faster, if messier.
I did not try the code, but I think that something like that should work:
sizeOfACube = 6;
numberOfRows = 47;
RGB = imread('image.png');
RGB = imresize(A, [(numRows+numberOfRows) numCols]);
for i=1:1:NumberOfRows
RGB(i*6,:,:) = 0;
end
imagesc(RGB);
imwrite(RGB,'newImage.png');
with:
sizeOfAcube the size of one cube on the QRcode.
numRows and numCols the number of Rows and Column of the original image.
One solution is to use patches, for instance as follows:
% set up example array
xl = 24; yl = xl;
[X Y] = find(hadamard(xl)==1);
% generate figure
figure, hold on
for ii=1:length(X)
patch(X(ii) + [0 0 1 1],Y(ii) + [0.1 0.9 0.9 0.1],[1 1 1],'Edgecolor',[1 1 1])
end
axis([0 xl+1 0 yl+1])
axis('square')
The patch command patch(x,y, color) accepts the vertices of the polygon element as x and y. In this example you can modify the term [0.1 0.9 0.9 0.1] to set the thickness of the bounding black line.
This generates
Edited
For the particular instance provided by the OP:
H=Hadamard(48); %# now to row-double the matrix
A=(1+H)/2;
B=(1-H)/2;
C=[A; B]; %# the code below randomly permutes elements within the rows of the matrix
[nRows,nCols] = size(C);
[junk,idx] = sort(rand(nRows,nCols),2); %# convert column indices into linear indices
idx = (idx-1)*nRows + ndgrid(1:nRows,1:nCols); %# rearrange whatever matrix
E = C;
E(:) = E(idx);
[X Y] = find(logical(E));
xl = length(X);
yl = length(Y);
figure, hold on
for ii=1:xl
rectangle('Position',[X(ii) Y(ii)+.2 1 0.8],'facecolor',[1 1 1],'edgecolor',[1 1 1])
end
axis([0 max(X)+1 0 max(Y)+1])
axis('square')
set(gca,'color',[0 0 0])
set(gca,'XTickLabel',[],'YTickLabel',[],'XTick',[],'YTick',[])
This example uses rectangle instead of patch to generate sharp corners.
The image:

Matlab - moving window, avoiding nested loops

I'm trying to write a "weighted moving window" without nested loops for speed improvement.
I already tried using arrayfun without getting exciting results, but maybe I did it in a wrong way.
The window has a different weight in each position (stored in B) and should be superimposed on a matrix A returning the values of the matrix A that lie inside the window, times the weight of the window in that position (read from B).
Also, the windows can overlap one on the other and in this case the maximum value should be kept.
Finally window's dimension and shift should be parameters of the function.
It looks more difficult that it actually is, so I show you the code that I would like to improve:
A = reshape([1:35],7,5)'; % values matrix
B = [1:3;4:6]; % window s weight matrix
% matrices size
[m n] = size(A);
[a b] = size(B);
% window s parameters
shift = 2; % window s movement at each iteration
zone = 3; % window s size (zone x zone)
% preallocation
C = ones(m,n); % to store the right weight to be applied in each position
% loop through positions and find the best weight when they overlap
for i=1:m
for j=1:n
C(i,j) = max(max(B( max(round((i-zone)/shift)+1,1) : min(ceil(i/shift),a) , max(round((j-zone)/shift)+1,1) : min(ceil(j/shift),b))));
end
end
% find the output of the windows
result = C.*A;
I hope that I made myself clear, but if you need more details please ask.
Thank you in advance for your help!
If you have access to the Image Processing Toolbox, you'll want to check out how to perform sliding neighborhood operations. In particular, I think the function NLFILTER can be used to achieve the result you want:
A = reshape([1:35],7,5)'; %'# Matrix to be filtered
B = [1:3;4:6]; %# Window weights
result = nlfilter(A,[2 3],#(M) max(M(:).*B(:)));
I would use im2col. Assuming your image is j x k, and your window is m x n, you'll get a matrix that is mn x (j-m+1)*(k-n+1). Then, you can just take every other column.
Example Code:
%A = your_image
B = im2col(A, [m n],'sliding');
C = B(:,1:2:end);
And there's your sliding window with "shift 2".
Try filter.
For example, to do a windowed average, over 5 elements:
outdata = filter([ 0.2 0.2 0.2 0.2 0.2 ], 1, indata);