Below, is a Python implementation of the paper Accurate Eye Center Location through Invariant Isocentric Patterns. Most of the comments in the code are copied directly from the paper.
Ideas, experiments and benchmarks in C++ and Python
jar.png |
#Import both skimage and cv
from skimage import transform as tf
from skimage import io
import cv2
import numpy as np
from scipy import optimize
import matplotlib.pyplot as plt
# Could use either skimage or cv to read the image
# img = io.imread('jar.png')
img = cv2.imread('jar.png')
gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray_image,127,255,cv2.THRESH_BINARY)
edges = cv2.Canny(thresh ,100, 200)
# Find largest contour (should be the label)
contours,hierarchy = cv2.findContours(edges, 0, 1)
areas = [cv2.contourArea(c) for c in contours]
max_index = np.argmax(areas)
cnt=contours[max_index]
# Create a mask of the label
mask = np.zeros(img.shape,np.uint8)
cv2.drawContours(mask, [cnt],0,255,-1)
Mask of the label |
# Find the 4 borders
scale = 1
delta = 0
ddepth = cv2.CV_8U
borderType=cv2.BORDER_DEFAULT
left=cv2.Sobel(mask,ddepth,1,0,ksize=1,scale=1,delta=0,borderType)
right=cv2.Sobel(mask,ddepth,1,0,ksize=1,scale=-1,delta=0, borderType)
top=cv2.Sobel(mask,ddepth,0,1,ksize=1,scale=1,delta=0,borderType)
bottom=cv2.Sobel(mask,ddepth,0,1,ksize=1,scale=-1,delta=0,borderType)
Left & Right borders after Sobel |
# Remove noise from borders
kernel = np.ones((2,2),np.uint8)
left_border = cv2.erode(left,kernel,iterations = 1)
right_border = cv2.erode(right,kernel,iterations = 1)
top_border = cv2.erode(top,kernel,iterations = 1)
bottom_border = cv2.erode(bottom,kernel,iterations = 1)
Left & Right borders after calling erode |
# Find coeficients c1,c2, ... ,c7,c8 by minimizing the error function.
# Points on the left border should be mapped to (0,anything).
# Points on the right border should be mapped to (108,anything)
# Points on the top border should be mapped to (anything,0)
# Points on the bottom border should be mapped to (anything,70)
# Equations 1 and 2:
# c1 + c2*x + c3*y + c4*x*y, c5 + c6*y + c7*x + c8*x^2
sumOfSquares_y = '+'.join(["(c[0]+c[1]*%s+c[2]*%s+c[3]*%s*%s)**2" %
(x,y,x,y) for y,x,z in np.transpose(np.nonzero(left_border)) ])
sumOfSquares_y += " + "
sumOfSquares_y += \
'+'.join(["(-108+c[0]+c[1]*%s+c[2]*%s+c[3]*%s*%s)**2" % \
(x,y,x,y) for y,x,z in np.transpose(np.nonzero(right_border)) ])
res_y = optimize.minimize(lambda c: eval(sumOfSquares_y),(0,0,0,0),method='SLSQP')
sumOfSquares_x = \
'+'.join(["(-70+c[0]+c[1]*%s+c[2]*%s+c[3]*%s*%s)**2" % \
(y,x,x,x) for y,x,z in np.transpose(np.nonzero(bottom_border))])
sumOfSquares_x += " + "
sumOfSquares_x += \
'+'.join( [ "(c[0]+c[1]*%s+c[2]*%s+c[3]*%s*%s)**2" % \
(y,x,x,x) for y,x,z in np.transpose(np.nonzero(top_border)) ] )
res_x = optimize.minimize(lambda c: eval(sumOfSquares_x),(0,0,0,0), method='SLSQP')
# Map the image using equatinos 1 and 2 (coeficients c1...c8 in res_x and res_y)
def map_x(res, cord):
m = res[0]+res[1]*cord[1]+res[2]*cord[0]+res[3]*cord[1]*cord[0]
return m
def map_y(res, cord):
m = res[0]+res[1]*cord[0]+res[2]*cord[1]+res[3]*cord[1]*cord[1]
return m
flattened = np.zeros(img.shape, img.dtype)
for y,x,z in np.transpose(np.nonzero(mask)):
new_y = map_y(res_x.x,[y,x])
new_x = map_x(res_y.x,[y,x])
flattened[float(new_y)][float(new_x)] = img[y][x]
# Crop the image
flattened = flattened[0:70, 0:105]
Flattened Image |
# Use skimage to transform the image
leftmost = tuple(cnt[cnt[:,:,0].argmin()][0])
rightmost = tuple(cnt[cnt[:,:,0].argmax()][0])
topmost = tuple(cnt[cnt[:,:,1].argmin()][0])
bottommost = tuple(cnt[cnt[:,:,1].argmax()][0])
dst = list()
src = list()
for y,x,z in np.transpose(np.nonzero(top_border)):
dst.append([x,y])
src.append([x,topmost[1]])
for y,x,z in np.transpose(np.nonzero(bottom_border)):
dst.append([x,y])
src.append([x,bottommost[1]])
for y,x,z in np.transpose(np.nonzero(left_border)):
dst.append([x,y])
src.append([leftmost[0],y])
for y,x,z in np.transpose(np.nonzero(right_border)):
dst.append([x,y])
src.append([rightmost[0],y])
src = np.array(src)
dst = np.array(dst)
tform3 = tf.PiecewiseAffineTransform()
tform3.estimate(src, dst)
warped = tf.warp(img, tform3, order=2)
warped = warped[85:170, 31:138]
Flattened label usgin skimage |
Windows VM images can be found here: http://www.modern.ie/en-us/virtualization-tools#downloads
OS X VM image can be found here (link to a torrent): http://www.souldevteam.net/blog/2012/06/15/os-x-mountain-lion-10-8-dp4-vmware-image-release-notes-links/
Android VM can be found here: http://www.genymotion.com/
See my previous post on how-to install.
:~$ sudo apt-get install openjdk-6-jdk
:~$ sudo apt-get install ant
:~$ export JAVA_HOME=/usr/lib/jvm/java-6-openjdk
:~$ export PATH=$PATH:/usr/lib/jvm/java-6-openjdk/bin
:~$ cd android-sdk-linux
:~$ tools/android update sdk –no-ui
:~$ export PATH=$PATH:~android-sdk-linux/tools :~android-sdk-linux/platform-tools
:~$ cd genymotion
:~$ ./genymotion
:~$ cd OpenCV-2.4.8-android-sdk/samples/15-puzzle
:~$ android update project --path .
:~$ ant debug
:~$ cd bin
:~$ export PATH=$PATH:~android-ndk-r9c/
:~$ cd OpenCV-2.4.8-android-sdk/samples/native-activity
:~$ vi jni/Android.mk
:~$ android update project --path .
:~$ ndk-build -B
:~$ ant debug
:~$ cd bin
#include <vector>
#include <stdio.h>
#include "ParallelForEach.h"
template<class T>
T square(T x)
{
return x * x;
}
void func() {
std::vector<int> In {1,2,3,4,5,6};
std::vector<int> OutGpu(6);
std::vector<int> OutCpu(6);
compute::parallel_for_each(In.begin(), In.end(), OutGpu.begin(), [](int x){
return square(x);
});
std::transform(In.begin(), In.end(), OutCpu.begin(), [](int x) {
return square(x);
});
//
// Do something with OutCpu and OutGpu …..........
//
}
int main() {
func();
return 0;
}
struct A {
int GetIt() const __attribute__((amp_restrict("cpu"))) {
return 2;
}
int GetIt() const __attribute__((amp_restrict("gpu"))) {
return 4;
}
};
compute::parallel_for_each(In.begin(), In.end(), OutGpu.begin(), [](int x){
A a;
return a.GetIt(); // returns 4
});
This code using std::find_if and a lambda function is executed on the GPU.
#include <algorithm>
extern "C" void _Kernel_find_if(int* arg, int len, int* out) {
int* it = std::find_if (arg, arg+len, [] (int i) { return ((i%2)==1); } );
out[0] = *it;
}
This code using C++11's std::enable_if is executed on the GPU.
#include <type_traits>
template<class T>
T foo(T t, typename std::enable_if<std::is_integral<T>::value >::type* = 0)
{
return 1;
}
template<class T>
T foo(T t, typename std::enable_if<std::is_floating_point<T>::value >::type* = 0)
{
return 0;
}
extern "C" void _Kernel_enable_if_int_argument(int* arg0, int* out)
{
out[0] = foo(arg0[0]);
}
template <class InputIterator, class ForwardIterator, class T, class Time=double>
std::pair<std::vector<Time>, ForwardIterator> __runge_kutta4(
InputIterator inBegin, InputIterator inEnd,
Time x0, Time x1, int nr_steps, T f, ForwardIterator yout)
{
typedef typename std::iterator_traits<InputIterator>::value_type
value_type;
auto delta_t = static_cast<Time>((x1-x0)/nr_steps);
auto nr_equations = std::distance(inBegin, inEnd);
ForwardIterator youtP = yout;
yout = std::copy(inBegin, inEnd, yout);
std::vector<Time> ytime(nr_steps);
for (auto ii=0; ii< nr_steps; ++ii) {
typedef typename std::vector<value_type> vec;
// K1 = f(x, u(x))
vec v1(nr_equations);
typename vec::iterator k1 = f(yout-nr_equations, x0, begin(v1));
// K2 = f(x+deltaX/2, u(x)+K1*deltaX/2)
vec v2(nr_equations);
vec y2(nr_equations);
std::transform(yout-nr_equations, yout, k1, begin(y2),
[delta_t](value_type& y,value_type& k) {
return y+0.5*delta_t*k;
});
typename vec::iterator k2 =
f(begin(y2), 0.5*delta_t+x0, begin(v2));
// K3 = f(x+deltaX/2, u(x)+K2*deltaX/2)
vec v3(nr_equations);
vec y3(nr_equations);
std::transform(yout-nr_equations, yout, k2, begin(y3),
[delta_t](value_type& y,value_type& k){
return y+0.5*delta_t*k;
});
typename vec::iterator k3 =
f(begin(y3), 0.5*delta_t+x0, begin(v3));
// K4 = f(x+deltaX, u(x)+K2*deltaX)
vec v4(nr_equations);
vec y4(nr_equations);
std::transform(yout-nr_equations, yout, k3, begin(y4),
[delta_t](value_type& y, value_type& k){ return y+delta_t*k; });
typename vec::const_iterator k4
= f(begin(y4), delta_t+x0, begin(v4));
// u(x+deltaX) = u(x) + (1/6) (K1 + 2 K2 + 2 K3 + K4) * deltaX
for (auto i=0; i<nr_equations; ++i,++yout) {
*yout = *(yout-nr_equations) +
(1.0/6.0) * (k1[i] + 2.0*k2[i] + 2.0*k3[i] + k4[i]) * delta_t;
}
ytime[ii] = x0;
x0 += delta_t;
}
return std::make_pair(ytime, yout);
}
Listing 1: Generic Implementation of Runga-kutta4
template <class T, class Vector=boost::numeric::ublas::vector<double> >
Vector rk4(const Vector& init, double x0, double x1, int nr_steps, T f)
{
Vector y = init;
auto h = static_cast<double>((x1-x0)/nr_steps);
double xn = x0;
for (auto ii=0; ii< nr_steps; ++ii) {
Vector k1 = f(y, xn);
Vector k2 = f(y+0.5*h*k1, 0.5*h+xn);
Vector k3 = f(y+0.5*h*k2, 0.5*h+xn);
Vector k4 = f(y+h*k3, h+xn);
xn += h;
y = u + (1.0/6.0) * (k1 + 2.0*k2 + 2.0*k3 + k4) * h;
}
return y;
}
Listing 2: Non-Generic Implementation of Runga-kutta4 (using ublas)
for n = 0,1,2,3,..., using
k1 = h f(xn, yn)
k2 = h f(xn + h/2, yn + k1/2)
k3 = h f(xn + h/2, yn + k2/2)
k4 = h f(xn + h, yn + k3)
xn+1 = xn + h
yn+1 = yn + h(1/6)(k1 + 2k2 + 2k3 + k4)
Listing 3: Runga-kutta4 equations.
Copyright © 2013 Software Ideas.