find length of individual dashed lines as well as the gaps between them - algebra

i have the following png with me:
[![enter image description here][1]][1]
in the above image i want to find the length of every dash and also the length between every 2 dashes ie the gaps.
Parallely, i have this code which gives me length of a line:
Another idea that i have in my mind about finding length of every dashes could be that, if i go on finding the medial axes of the dashes, then would traversing in the same direction of the medial axis(a line which passes through the centre of the concerned object) could some how give me the length in any way??
medial axis output: the green line inside the black is the medial axis i get, i have made use of open cv to fine the medial axis, but again i am not sure if that would work on another complex images such as the circle above and also, how do i find the length of gaps...
Any help would be appreciated!

This is a total brute force code.
Is not optimized at all, is super slow. I hope somebody vectorize it totally or in part in another answer. I invite to patch it.
This code detects each dash as a "blob", and that's the slowest part of the code. But there are many libraries for blob detection, which should be much faster. I just don't know how to use them.
The code scans line by line, and when it finds a black pixel, it adds it to a blob, but it results in some blobs being detected multiple times.
This image shows the centers of the blobs:
The second stage, which is the slowest, compares all blobs to join and discard the adjacent ones.
After the duplicated blobs are deleted, the distance between blob center is calculated by finding the nearest neighbor of each blob, and taking the median distance, to avoid the influence of outsiders.
Here the distance between centers is drawn with yellow arrows
The blobs get stored as instances of the class blob, which stores the pixels of each blob, calculates the center, and the axis of symmetry, to be able to tell the orientation of each dash.
The class blob has a function named extremeAxisOfInertia(), which returns 2 vectors: the first vector points in the direction of the longest side of the dash, and the second vector point to the shorter side
principalAxis, secondaryAxis = blobInstance.extremeAxisOfInertia()
Also, the maximum dimension of each blob (measured between centers of pixels) is given by the function dimensions()
blobLength, blobWidth = blobInstance.dimensions()
It is calculated as the length between the most distant pixels on the direction of the principal axis of inertia.
Here the dots show the center of each pixel of one blob:
The distance of separation between dashes is calculated as the difference between the median separation of the blobs center, minus the median length of the blobs.
As the title on the plot below says, the image posted by the OP results in a median separation of 9.17 pixels, and a median dash length of 7.60 pixels. But hat calculation only uses the closest neighbor. There is a lot of space for improvement.
print("Imports...")
import cmath
import numpy as np
import cv2
import matplotlib.pyplot as plt
URL = "https://i.stack.imgur.com/eNV5m.png"
def downloadImage(URL):
'''Downloads the image on the URL, and convers to cv2 BGR format'''
from io import BytesIO
from PIL import Image as PIL_Image
import requests
response = requests.get(URL)
image = PIL_Image.open(BytesIO(response.content))
return cv2.cvtColor(np.array(image), cv2.COLOR_BGR2RGB)
# index of row and column on each pixel=[pixel[_rw],pixel[_cl]]=[row,column]
_rw, _cl = 0, 1
class blob(object):
# array of xy coordinates
pixels: list
blobNumber: int
def __init__(self, blobNumber, row=None, col=None):
if row is not None and col is not None:
self.pixels = [[row, col]]
self.blobNumber = blobNumber
def addPixel(self, row, col):
if [row, col] not in self.pixels:
self.pixels.append([row, col])
else:
raise ValueError("Pixel already in blob")
# baricenter of the blob
def centerRC(self):
'''returns row and column of the baricenter of the blob
returns a pair of floats which may not match a specific pixel
returns rowY,columnX
'''
center = np.mean(self.pixels, axis=0)
return center[_rw], center[_cl]
def Ixx(self):
''' central moment of the blob respect to x axis'''
Cy, Cx = self.centerRC()
return sum((p[_rw]-Cy)**2 for p in self.pixels)
def Iyy(self):
''' central moment of the blob respect to y axis'''
Cy, Cx = self.centerRC()
return sum((p[_cl]-Cx)**2 for p in self.pixels)
def Ixy(self):
''' central moment of the blob respect to x and y axis'''
Cy, Cx = self.centerRC()
return sum((p[_rw]-Cy)*(p[_cl]-Cx) for p in self.pixels)
def extremeAxisOfInertia(self):
'''Calculates the principal axis of inertia of the blob
returns unitary vectors pointing on the direction normal to the
max [principal] axis of inertia
and the minimum [principal] axis of inertia
Also returns the maximum and minimum momentum along the principal axis of inertia
returns maxAxis, minAxis, maxI, minI
^minAxis
|max axis of inertia
┌──────|──────┐
---│------|------│---minor axis of inertia ──>maxAxis
└──────|──────┘
|
'''
Ixx = self.Ixx()
Iyy = self.Iyy()
Ixy = self.Ixy()
I = np.array([[Ixx, Ixy], [Ixy, Iyy]])
# print(f"I = {I}")
eigenvalues, eigenvectors = np.linalg.eig(I)
eigMatrix = np.array(eigenvectors)
Imax = np.matmul(eigMatrix, np.matmul(I, eigMatrix.T))
# print(f"eigenvalues = {eigenvalues}")
# print(f"eigenvectors = {eigenvectors}")
# print(f"Imax = {Imax}")
if Imax[0, 0] >= Imax[1, 1]:
maxAxis = eigenvectors[0]
minAxis = eigenvectors[1]
else:
maxAxis = eigenvectors[1]
minAxis = eigenvectors[0]
return maxAxis, minAxis, max(Imax[0, 0], Imax[1, 1]), min(Imax[0, 0], Imax[1, 1])
def dimensions(self):
'''
returns the dimensions of the blob, measured between pixel centers
assuming that the blob is roughly a rectangle with small side b and large side h
returns h,b
┌─────────────h─────────────┐(measured between pixel centers)
┌─────────────────────────────┐
| |┐
│pixel center non represented │|b (measured between pixel centers)
| |┘
└─────────────────────────────┘
'''
maxAxis, minAxis, maxI, minI = self.extremeAxisOfInertia()
# rotate all pixel coordinates to convert max axis into horizontal
# and extract the maximum and minimum x coordinates
rotor = complex(maxAxis[_cl], maxAxis[_rw])
pixelsHorizontalized = np.array(
[complex(p[_cl], p[_rw])/rotor for p in self.pixels])
x, y = pixelsHorizontalized.real, pixelsHorizontalized.imag
h = max(x)-min(x)
b = max(y)-min(y)
return h, b
def plotPixels(self):
import matplotlib.pyplot as plt
plt.scatter([p[_cl] for p in self.pixels], [p[_rw]
for p in self.pixels], label=f" blob {self.blobNumber}")
centerR, centerC = self.centerRC()
maxAxis, minAxis, _, __ = self.extremeAxisOfInertia()
length, width = self.dimensions()
plt.plot([centerC, centerC+maxAxis[_cl]*length/2], [centerR,
centerR+maxAxis[_rw]*length/2], 'r', label="Max axis")
plt.plot([centerC, centerC+minAxis[_cl]*width/2], [centerR,
centerR+minAxis[_rw]*width/2], 'b', label="Min axis")
ax = plt.gca()
ax.invert_yaxis() # on images y axis goes down
ax.legend()
plt.title(
f"Blob {self.blobNumber}; max dimension = {length:.2f}, min dimension = {width:.2f}")
plt.show()
print("Fetching image from URL...")
image = downloadImage(URL)
cv2.imshow('Original image', image)
cv2.waitKey(10)
img_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
threshold = 128
ret, thresh = cv2.threshold(
img_gray, type=cv2.THRESH_BINARY_INV, thresh=threshold, maxval=255)
print("Classification of points in blobs...")
# all pixels classified as 0 blob
# add extra border rows and columns to avoid problems with the blob classifier
padThresh = np.pad(thresh, (1, 1), constant_values=0)
classif = padThresh*0
blobs = {} # key=blobCount, value=blob object
blobCount = 0
neighborPixelsToCheck = [(-1, -1), (-1, 0), (-1, 1), (0, -1)]
for row in range(1, padThresh.shape[0]-1): # avoided first and last row added
# avoided first and last column added
for col in range(1, padThresh.shape[1]-1):
# if pixel is black...
if padThresh[row, col] > threshold:
# if up and left pixels are also black...
if any(padThresh[row+y, col+x] > threshold for y, x in neighborPixelsToCheck):
numBlob = max(classif[row+y, col+x]
for y, x in neighborPixelsToCheck)
classif[row, col] = numBlob
blobs[numBlob].addPixel(row, col)
else:
blobCount += 1
classif[row, col] = blobCount
blobs[blobCount] = blob(blobCount, row=row, col=col)
plt.imshow(classif/max(classif.flatten()))
# Collect centers of all blobs
centers = [value.centerRC() for key, value in blobs.items()]
plt.scatter([c[_cl] for c in centers], [c[_rw]
for c in centers], label="blobs \ncenters", color="red", marker="+")
#legend on upper right corner
plt.legend(loc="center")
plt.title("Blobs and Centers of blobs detected")
plt.show()
print("Unifying blobs...")
# unify adjacent blobs
keys = list(blobs.keys())
for idx, this in enumerate(keys[:-1]):
if this in blobs.keys(): # It may had been deleted by a previous loop
print(f" Comparing blob {this} of {len(keys)}...")
thisPixels = blobs[this].pixels[::-1] # reverse to speed up comparison
for other in keys[1+idx:]:
if other in blobs.keys(): # It may had been deleted by a previous loop
otherPixels = blobs[other].pixels
# if squared euclidean distance between centers of blobs < 2
if any((p[0]-q[0])**2+(p[1]-q[1])**2 < 2.1 for p in thisPixels for q in otherPixels):
# merge blobs
blobs[this].pixels.extend(otherPixels)
# reverse to speed up comparison
thisPixels.extend(otherPixels[::-1])
# remove other blob
del blobs[other]
plt.imshow(classif/max(classif.flatten()))
# Calculating median distance between blobs
# Collect centers of all blobs
centers = np.asarray([value.centerRC() for key, value in blobs.items()])
plt.scatter([c[_cl] for c in centers], [c[_rw]
for c in centers], label="centers", color="red", marker="+")
def closest_node(node, nodes):
# nodes = np.asarray(nodes)
deltas = nodes - node
dist_2 = np.einsum('ij,ij->i', deltas, deltas)
return np.argmin(dist_2)
nearest = []
for idx, c in enumerate(centers):
Cent_withoutC = np.delete(centers, idx, axis=0)
nearest.append(Cent_withoutC[closest_node(c, Cent_withoutC)])
# plt.scatter([c[_cl] for c in nearest],[c[_rw] for c in nearest],label="nearest",color="red",marker="+")
distances = [((n-c)[0]**2+(n-c)[1]**2)**0.5 for c, n in zip(centers, nearest)]
for c, n in zip(centers, nearest):
x, y, dx, dy = c[_cl], c[_rw], (n-c)[_cl], (n-c)[_rw]
plt.arrow(x, y, dx, dy, length_includes_head=True, head_width=1 /
4*(abs(dx*dy))**.5, color="yellow", edgecolor="black")
plt.title("Nearest neighbor of each blob")
plt.show()
plt.scatter(x=range(len(distances)), y=np.sort(distances),
label="Distances between blobs", color="red")
# the median value is better than an average,
# because is less sensible to outsiders
medianDistance = np.median(distances)
plt.plot([1, len(distances)], [medianDistance, medianDistance],
label="Median distance", color="red")
title=f"Median distance between blob centers = {medianDistance:.2f} pixels"
# Median value of the largest dimension of the blobs
blobsLengths=[]
for key,_blob in blobs.items():
length,width=_blob.dimensions()
blobsLengths.append(length)
medianBlobLength = np.median(blobsLengths)
plt.scatter(x=range(len(blobsLengths)), y=np.sort(
blobsLengths), label="blobs Lengths", color="blue")
plt.plot([1, len(blobsLengths)], [medianBlobLength, medianBlobLength],
label="Median blob length", color="blue")
# add to title the median value of the largest dimension of the blobs
title=f"{title}\nMedian blob length = {medianBlobLength:.2f} pixels"
medianBlobSeparation=medianDistance-medianBlobLength
title=f"{title}\nMedian blob separation = {medianBlobSeparation:.2f} pixels"
plt.title(title)
plt.legend()
plt.show()

Related

Applying scipy.stats.gaussian_kde to 3D point cloud

I have a set of about 33K (x,y,z) points in a csv file and would like to convert this to a grid of density values using scipy.stats.gaussian_kde. I have not been able to find a way to convert this point cloud array into an appropriate input format for the gaussian_kde function (and then take the output of this and convert it into a density value grid). Can anyone provide sample code?
Here's an example with some comments which may be of use. gaussian_kde wants the data and points to be row stacked, ie. (# ndim, # num values), as per the docs. In your case you would row_stack([x, y, z]) such that the shape is (3, 33000).
from scipy.stats import gaussian_kde
import numpy as np
import matplotlib.pyplot as plt
# simulate some data
n = 33000
x = np.random.randn(n)
y = np.random.randn(n) * 2
# data must be stacked as (# ndim, # n values) as per docs.
data = np.row_stack((x, y))
# perform KDE
kernel = gaussian_kde(data)
# create grid over which to evaluate KDE
s = np.linspace(-8, 8, 128)
grid = np.meshgrid(s, s)
# again KDE needs points to be row_stacked
grid_points = np.row_stack([g.ravel() for g in grid])
# evaluate KDE and reshape result correctly
Z = kernel(grid_points)
Z = Z.reshape(grid[0].shape)
# plot KDE as image and overlay some data points
fig, ax = plt.subplots()
ax.matshow(Z, extent=(s.min(), s.max(), s.min(), s.max()))
ax.plot(x[::10], y[::10], 'w.', ms=1, alpha=0.3)
ax.set_xlim(s.min(), s.max())
ax.set_ylim(s.min(), s.max())

N-dimensional GP Regression

I'm trying to use GPflow for a multidimensional regression. But I'm confused by the shapes of the mean and variance.
For example: A 2-dimensional input space X of shape (20,20) is supposed to be predicted. My training samples are of shape (8,2) which means 8 training samples overall for the two dimensions. The y-values are of shape (8,1) which of course means one value of the ground truth per combination of the 2 input dimensions.
If I now use model.predict_y(X) I would expect to receive a mean of shape (20,20) but obtain a shape of (20,1). Same goes for the variance. I think that this problem comes from the shape of the y-values but I have have no idea how to fix it.
bound = 3
num = 20
X = np.random.uniform(-bound, bound, (num,num))
print(X_sample.shape) # (8,2)
print(Y_sample.shape) # (8,1)
k = gpflow.kernels.RBF(input_dim=2)
m = gpflow.models.GPR(X_sample, Y_sample, kern=k)
m.likelihood.variance = sigma_n
m.compile()
gpflow.train.ScipyOptimizer().minimize(m)
mean, var = m.predict_y(X)
print(mean.shape) # (20, 1)
print(var.shape) # (20, 1)
It sounds like you may be confused between the shape of a grid of input positions and the shape of the numpy arrays: if you want to predict on a 20 x 20 grid in two dimensions, you have 400 points in total, each with 2 values. So X (the one that you pass to m.predict_y()) should have shape (400, 2). (Note that the second dimension needs to have the same shape as X_sample!)
To construct this array of shape (400,2) you can use np.meshgrid (e.g., see What is the purpose of meshgrid in Python / NumPy?).
m.predict_y(X) only predicts the marginal variance at each test point, so the returned mean and var both have shape (400,1) (same length as X). You can of course reshape them to the 20 x 20 values on your grid.
(It is also possible to compute the full covariance, for the latent f this is implemented as m.predict_f_full_cov, which for X of shape (400,2) would return a 400x400 matrix. This is relevant if you want consistent samples from the GP, but I suspect that goes well beyond this question.)
I was indeed making the mistake to not flatten the arrays which in return produced the mistake. Thank you for the fast response STJ!
Here is an example of the working code:
# Generate data
bound = 3.
x1 = np.linspace(-bound, bound, num)
x2 = np.linspace(-bound, bound, num)
x1_mesh,x2_mesh = np.meshgrid(x1, x2)
X = np.dstack([x1_mesh, x2_mesh]).reshape(-1, 2)
z = f(x1_mesh, x2_mesh) # evaluation of the function on the grid
# Draw samples from feature vectors and function by a given index
size = 2
np.random.seed(1991)
index = np.random.choice(range(len(x1)), size=(size,X.ndim), replace=False)
samples = utils.sampleFeature([x1,x2], index)
X1_sample = samples[0]
X2_sample = samples[1]
X_sample = np.column_stack((X1_sample, X2_sample))
Y_sample = utils.samplefromFunc(f=z, ind=index)
# Change noise parameter
sigma_n = 0.0
# Construct models with initial guess
k = gpflow.kernels.RBF(2,active_dims=[0,1], lengthscales=1.0,ARD=True)
m = gpflow.models.GPR(X_sample, Y_sample, kern=k)
m.likelihood.variance = sigma_n
m.compile()
#print(X.shape)
mean, var = m.predict_y(X)
mean_square = mean.reshape(x1_mesh.shape) # Shape: (num,num)
var_square = var.reshape(x1_mesh.shape) # Shape: (num,num)
# Plot mean
fig = plt.figure(figsize=(16, 12))
ax = plt.axes(projection='3d')
ax.plot_surface(x1_mesh, x2_mesh, mean_square, cmap=cm.viridis, linewidth=0.5, antialiased=True, alpha=0.8)
cbar = ax.contourf(x1_mesh, x2_mesh, mean_square, zdir='z', offset=offset, cmap=cm.viridis, antialiased=True)
ax.scatter3D(X1_sample, X2_sample, offset, marker='o',edgecolors='k', color='r', s=150)
fig.colorbar(cbar)
for t in ax.zaxis.get_major_ticks(): t.label.set_fontsize(fontsize_ticks)
ax.set_title("$\mu(x_1,x_2)$", fontsize=fontsize_title)
ax.set_xlabel("\n$x_1$", fontsize=fontsize_label)
ax.set_ylabel("\n$x_2$", fontsize=fontsize_label)
ax.set_zlabel('\n\n$\mu(x_1,x_2)$', fontsize=fontsize_label)
plt.xticks(fontsize=fontsize_ticks)
plt.yticks(fontsize=fontsize_ticks)
plt.xlim(left=-bound, right=bound)
plt.ylim(bottom=-bound, top=bound)
ax.set_zlim3d(offset,np.max(z))
which leads to (red dots are the sample points drawn from the function). Note: Code not refactored what so ever :)

Matlab boxplot adjacent values

I found that calculating an index to specify outliers of a dataset according to how the boxplot works does not give the same results. Please find below an example where I create some data, extract the values from the boxplot (as seen in datatips in the figure window) and compare them to the values I calculated.
While the median and quartiles match up the upper and lower adjacent values do not. According to the Matlab help under 'Whisker', the adjacent values are calculated as q3 + w*(q3-q1) where q3 and q1 are the quantiles and w is the specified whisker length.
Am I calculating this wrong or is there any other mistake? I would like to be able to explain the error.
Screenshot of results table (please note the results vary due to random data)
%Boxplot test
% create random, normally distributed dataset
data = round(randn(1000,1)*10,2);
figure(10)
clf
boxplot(data,'Whisker',1.5)
clear stats tmp
% read data from boxplot, same values as can be seen in datatips in the figure window
h = findobj(gcf,'tag','Median');
tmp = get(h,'YData');
stats(1,1) = tmp(1);
h = findobj(gcf,'tag','Box');
tmp = get(h,'YData');
stats(1,2) = tmp(1);
stats(1,3) = tmp(2);
h = findobj(gcf,'tag','Upper Adjacent Value');
tmp = get(h,'YData');
stats(1,4) = tmp(1);
h = findobj(gcf,'tag','Lower Adjacent Value');
tmp = get(h,'YData');
stats(1,5) = tmp(1);
% calculated data
stats(2,1) = median(data);
stats(2,2) = quantile(data,0.25);
stats(2,3) = quantile(data,0.75);
range = stats(2,3) - stats(2,2);
stats(2,4) = stats(2,3) + 1.5*range;
stats(2,5) = stats(2,2) - 1.5*range;
% error calculation
for k=1:size(stats,2)
stats(3,k) = stats(2,k)-stats(1,k);
end %for k
% convert results to table with labels
T = array2table(stats,'VariableNames',{'Median','P25','P75','Upper','Lower'}, ...
'RowNames',{'Boxplot','Calculation','Error'});
While the calculation of the boundaries, e.g. q3 = q3 + w*(q3-q1), is correct, it is not displayed in the boxplot. What is actually displayed and marked as upper/lower adjacent value is the minimum and maximum of the values within the aforementioned boundaries.
Regarding the initial task leading to the question: For applying the same filtering of outliers as in the boxplot the calculated boundaries can be used.

Summing Values based on Area in Matlab

Im trying to write a code in Matlab to calculate an area of influence type question. This is an exert from my data (Weighting, x-coord, y-coord):
M =
15072.00 486.00 -292
13269.00 486.00 -292
12843.00 414.00 -267
10969.00 496.00 -287
9907.00 411.00 -274
9718.00 440.00 -265
9233.00 446.00 -253
9138.00 462.00 -275
8830.00 496.00 -257
8632.00 432.00 -253
R =
-13891.00 452.00 -398
-13471.00 461.00 -356
-12035.00 492.00 -329
-11309.00 413.00 -353
-11079.00 467.00 -375
-10659.00 493.00 -333
-10643.00 495.00 -338
-10121.00 455.00 -346
-9795.00 456.00 -367
-8927.00 485.00 -361
-8765.00 467.00 -351
I want to make a function to calculate the sum of the weightings at any given position based on a circle of influence of 30 for each coordinate.
I have thought of using a for loop to calculate each point independently and summing the result but seems unnecessarily complicated and inefficient.
I also thought of assigning an intensity of color to each circle and overlaying them but I dont know how to change color intensity based on value here is my attempt so far (I would like to have a visual of the result):
function [] = Influence()
M = xlsread('MR.xlsx','A4:C310');
R = xlsread('MR.xlsx','E4:G368');
%these are my values around 300 coordinates
%M are negative values and R positive, I want to see which are dominant in their regions
hold on
scatter(M(:,2),M(:,3),3000,'b','filled')
scatter(R(:,2),R(:,3),3000,'y','filled')
axis([350 650 -450 -200])
hold off
end
%had to use a scalar of 3000 for some reason as it isnt correlated to the graph size
I'd appreciate any ideas/solutions thank you
This is the same but with ca. 2000 data points
How about this:
r_influence = 30; % radius of influence
r = #(p,A) sqrt((p(1)-A(:,2)).^2 + (p(2)-A(:,3)).^2); % distance
wsum = #(p,A) sum(A(find(r(p,A)<=r_influence),1)); % sum where distance less than roi
% compute sum on a grid
xrange = linspace(350,550,201);
yrange = linspace(-200,-450,201);
[XY,YX] = meshgrid(xrange,yrange);
map_M = arrayfun(#(p1,p2) wsum([p1,p2],M),XY,YX);
map_R = arrayfun(#(p1,p2) wsum([p1,p2],R),XY,YX);
figure(1);
clf;
imagesc(xrange,yrange,map_M + map_R);
colorbar;
Gives a picture like this:
Is that what you are looking for?

Nearest-neighbor interpolation algorithm in MATLAB

I am trying to write my own function for scaling up an input image by using the Nearest-neighbor interpolation algorithm. The bad part is I am able to see how it works but cannot find the algorithm itself. I will be grateful for any help.
Here's what I tried for scaling up the input image by a factor of 2:
function output = nearest(input)
[x,y]=size(input);
output = repmat(uint8(0),x*2,y*2);
[newwidth,newheight]=size(output);
for i=1:y
for j=1:x
xloc = round ((j * (newwidth+1)) / (x+1));
yloc = round ((i * (newheight+1)) / (y+1));
output(xloc,yloc) = input(j,i);
end
end
Here is the output after Mark's suggestion
This answer is more explanatory than trying to be concise and efficient. I think gnovice's solution is best in that regard. In case you are trying to understand how it works, keep reading...
Now the problem with your code is that you are mapping locations from the input image to the output image, which is why you are getting the spotty output. Consider an example where input image is all white and output initialized to black, we get the following:
What you should be doing is the opposite (from output to input). To illustrate, consider the following notation:
1 c 1 scaleC*c
+-----------+ 1 +----------------------+ 1
| | | | | |
|----o | <=== | | |
| (ii,jj) | |--------o |
+-----------+ r | (i,j) |
inputImage | |
| |
+----------------------+ scaleR*r
ouputImage
Note: I am using matrix notation (row/col), so:
i ranges on [1,scaleR*r] , and j on [1,scaleC*c]
and ii on [1,r], jj on [1,c]
The idea is that for each location (i,j) in the output image, we want to map it to the "nearest" location in the input image coordinates. Since this is a simple mapping we use the formula that maps a given x to y (given all the other params):
x-minX y-minY
--------- = ---------
maxX-minX maxY-minY
in our case, x is the i/j coordinate and y is the ii/jj coordinate. Therefore substituting for each gives us:
jj = (j-1)*(c-1)/(scaleC*c-1) + 1
ii = (i-1)*(r-1)/(scaleR*r-1) + 1
Putting pieces together, we get the following code:
% read a sample image
inputI = imread('coins.png');
[r,c] = size(inputI);
scale = [2 2]; % you could scale each dimension differently
outputI = zeros(scale(1)*r,scale(2)*c, class(inputI));
for i=1:scale(1)*r
for j=1:scale(2)*c
% map from output image location to input image location
ii = round( (i-1)*(r-1)/(scale(1)*r-1)+1 );
jj = round( (j-1)*(c-1)/(scale(2)*c-1)+1 );
% assign value
outputI(i,j) = inputI(ii,jj);
end
end
figure(1), imshow(inputI)
figure(2), imshow(outputI)
A while back I went through the code of the imresize function in the MATLAB Image Processing Toolbox to create a simplified version for just nearest neighbor interpolation of images. Here's how it would be applied to your problem:
%# Initializations:
scale = [2 2]; %# The resolution scale factors: [rows columns]
oldSize = size(inputImage); %# Get the size of your image
newSize = max(floor(scale.*oldSize(1:2)),1); %# Compute the new image size
%# Compute an upsampled set of indices:
rowIndex = min(round(((1:newSize(1))-0.5)./scale(1)+0.5),oldSize(1));
colIndex = min(round(((1:newSize(2))-0.5)./scale(2)+0.5),oldSize(2));
%# Index old image to get new image:
outputImage = inputImage(rowIndex,colIndex,:);
Another option would be to use the built-in interp2 function, although you mentioned not wanting to use built-in functions in one of your comments.
EDIT: EXPLANATION
In case anyone is interested, I thought I'd explain how the solution above works...
newSize = max(floor(scale.*oldSize(1:2)),1);
First, to get the new row and column sizes the old row and column sizes are multiplied by the scale factor. This result is rounded down to the nearest integer with floor. If the scale factor is less than 1 you could end up with a weird case of one of the size values being 0, which is why the call to max is there to replace anything less than 1 with 1.
rowIndex = min(round(((1:newSize(1))-0.5)./scale(1)+0.5),oldSize(1));
colIndex = min(round(((1:newSize(2))-0.5)./scale(2)+0.5),oldSize(2));
Next, a new set of indices is computed for both the rows and columns. First, a set of indices for the upsampled image is computed: 1:newSize(...). Each image pixel is considered as having a given width, such that pixel 1 spans from 0 to 1, pixel 2 spans from 1 to 2, etc.. The "coordinate" of the pixel is thus treated as the center, which is why 0.5 is subtracted from the indices. These coordinates are then divided by the scale factor to give a set of pixel-center coordinates for the original image, which then have 0.5 added to them and are rounded off to get a set of integer indices for the original image. The call to min ensures that none of these indices are larger than the original image size oldSize(...).
outputImage = inputImage(rowIndex,colIndex,:);
Finally, the new upsampled image is created by simply indexing into the original image.
MATLAB has already done it for you. Use imresize:
output = imresize(input,size(input)*2,'nearest');
or if you want to scale both x & y equally,
output = imresize(input,2,'nearest');
You just need a more generalized formula for calculating xloc and yloc.
xloc = (j * (newwidth+1)) / (x+1);
yloc = (i * (newheight+1)) / (y+1);
This assumes your variables have enough range for the multiplication results.