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 |
ReStructuredtext for the above example:
.. list-table::
:widths: 50 50
:header-rows: 1
* - **Name**
- **e-mail**
* - Pedro
- .. raw:: html
<p><a href="mailto:pedro@address.es">pedro@mailaddress.es</a></p>
* - Dimitri
- .. raw:: html
<p><a href="mailto:dimitr@mail.es">dimitr@mailaddress.es</a></p>
%module example %{ #include "example.h" %} %include "example.h"
%module pysally %{ #include <libconfig.h> %} %include <cstring.i> %cstring_output_allocate(char **out1, free(*$1)); %{ void config_lookup_string_2( const config_t *config, const char *path, char **out1) { *out1 = (char *) malloc(1024); (*out1)[0] = 0; config_lookup_string(config, path, (const char *)out1); } %} %include <libconfig.h>
%module pysally %{ #include "pysally.h" %} %include "std_string.i" %include "pysally.h"
class Sally { public: Sally(int verbose, std::string in, std::string out) : entries_(0), input_(in), output_(out) {} ~Sally(); /// Load the configuration of Sally void load_config(const std::string& config_file); /// Init the Sally tool void init(); /// Main processing routine of Sally. /// This function processes chunks of strings. void process(); /// Get/Set configuration std::string getConfigAttribute(std::string name); void setConfigAttribute(std::string name, std::string value); // etc // ... // ... private: config_t cfg_; int verbose_; long entries_; std::string input_; std::string output_; };
verbose = 0 in = "/tmp/input" out = "/tmp/output" config = "/tmp/sally.cfg" s = Sally(verbose, in, out) s.load_config(config) s.init() s.process()
%module(directors="1") pysally %{ #include "pysally.h" %} %feature("director") Reader; %feature("director") Writer; %include "std_string.i" %include "pysally.h"
class Writer { public: Writer(std::string out); virtual ~Writer(); virtual void init(); virtual const std::string getName(); virtual int write(const output_list& output, int len); private: config_t& cfg_; std::string output_; bool hasout_; }; class Reader { public: Reader(std::string in); virtual ~Reader(); virtual void init(); virtual const std::string getName(); virtual long getNrEntries(); virtual int read(string_list& strs, int len); private: config_t& cfg_; std::string input_; long entries_; };
class MyReader(Reader): def __init__(self, input): super(MyReader, self).__init__(input) def read(self, strings, len): return super(MyReader, self).read(strings, len) def init(self): super(MyReader, self).init() def getNrEntries(self): return super(MyReader, self).getNrEntries() class MyWriter(Writer): def __init__(self, output): super(MyWriter, self).__init__(output) def init(self): pass def write(self, fvec, len): for j in range(len): print "l:", fvec.getFeaturesLabel(j), for i in range(fvec.getListLength(j)): print fvec.getDimension(j, i), fvec.getValue(j, i), print fvec.getValue(j, i) print fvec.getFeaturesSource(j) print return 1 input = "/home/edimchr/reuters.zip" output = "/home/edimchr/tmp/pyreuters.libsvm" verbose = 0 r = MyReader(input) w = MyWriter(output) #r = Reader(input) #w = Writer(output) s = Sally(verbose, r, w) s.load_config("./example.cfg") s.init() s.process()
class string_list { private: string_t* str_; public: string_list(string_t* str) : str_(str) {} /// Length for element i void setStringLength(int i, int len) { str_[i].len = len ; } /// String data for element i void setStringData(int i, char* data) { str_[i].str = strdup(data); } /// Optional label of string void setLabel(int i, float label) { str_[i].label = label; } /// Optional description of source void setSource(int i, char* src) { str_[i].src = strdup(src); } string_t* getString() const { return str_; } }; class output_list { private: fvec_t** vec_; public: output_list(fvec_t** vec) : vec_(vec) {} /// Length for element i unsigned long getListLength(int i) const { return vec_[i]->len; } /// Nr of features for element i unsigned long getTotalFeatures(int i) const { return vec_[i]->total; } /// Label for element i float getFeaturesLabel(int i) const { return vec_[i]->label; } /// List of dimensions j unsigned long getDimension(int i, int j) { return vec_[i]->dim[j]; } /// List of values for element i float getValue(int i, int j) { return vec_[i]->val[j]; } char* getFeaturesSource(int i) const { return vec_[i]->src; } fvec_t** getFvec() const { return vec_; } };
sally-0.6.1/
m4/
a
c_pkg_swig.m4
ax_pkg_swig.m4
ax_python_devel.m4
pysally/
Makefile.am
swig.i
src/
Makefile.am
Makefile.am
configure.in
2) Add
pysally to sally-0.6.1/Makefile.am
……
SUBDIRS = src doc tests contrib pysally
……
……
3) Add the following to sally-0.6.1/configure.in
AC_PROG_CXX
AC_DISABLE_STATIC
AC_PROG_LIBTOOL
AX_PYTHON_DEVEL(>= '2.3')
AM_PATH_PYTHON
AC_PROG_SWIG(1.3.21)
SWIG_ENABLE_CXX
SWIG_PYTHON
4) Add pysally/Makefile to AC_CONFIG_FILES in sally-0.6.1/configure.in
AC_CONFIG_FILES([
Makefile \
src/Makefile \
src/input/Makefile \
src/output/Makefile \
src/fvec/Makefile \
doc/Makefile \
tests/Makefile \
contrib/Makefile \
pysally/Makefile \
])
sally-0.6.1
Copyright © 2013 Software Ideas.