Friday, October 31, 2014

iBeacon heat map test

Generating heat map using iBeacons with android application

Intro

About a month ago I had an opportunity to play with an iBeacon on the Android platform. My first task was to implement a proximity trigger to Android client for one of our apps at work. As we played with it, we wanted a more precise data such as a heat map.

Searching

I googled some familiar projects around, bud didn't find a precise app in the Play Store that generates a heat map from actual distances from iBeacons. After visualization the raw accuracies in a graph (picture below) I found this interesting page that nevertheless confirm my results. These are my observations.

  • raw data is not as accurate as I first thought
  • accuracy depends on rotation of iBeacon to the smart phone
  • at some distance, the error is rising rapidly
  • to get more precise data, you have to set a transmit interval and transmit power to the maximum, but that also leads to more battery consumption

What I did

I created an Android app with the purpose to generate a real-time heat map. As I mentioned before, the accuracy from iBeacons is not very precise so I should call it iBeacon heat map beta demo. Anyway the app allows you to assign colors to iBeacons, set room sizes and choose one of the few approximate methods. The results are still not very satisfactory. For now, the most acceptable results are from the most simple method (make an average of the last 30 values).

Details

I used IBKS 105 iBeacons from Spanish vendor accent-systems with the tx-Power and transmit interval set to maximum available value. I use the bluetooth address as an unique identifier so you don't have to set UUID, major or minor values.


Github: https://github.com/svab-m/ibeacon_heatmap_demo
Google Play: https://play.google.com/store/apps/details?id=sk.svb.ibeacon.heatmap


         

Share:

Sunday, October 5, 2014

Bill cropper

Automatic tool for cropping bills from scan

What is it?

We have this project at work, that is intended for small and medium companies. It covers much more than just simple accounting. It has many cool features that I'm not going to write about, except one. Automatic processing bills/ invoices. You can do it via your mobile client (Android, iOS) or a web application.

What to improve?

We ran into a problem that people scanned more bills together, instead of one image - one document rule. So I decided to create a small script to recognize and crop these multi-bills images into separate images.

Algorithm

To crop the bills out of the images I used a few simple preprocessing image methods. First, I remove the noise. After that I convert the image to binary and separate it into white areas. Finally I find enough big white areas and find a bounding box for them. I think the code below is self explaining enough.
  • 1. RESIZE
  • 2. IMOPEN - MORPHOLOGICAL OPERATION
  • 3. IMG TO BINARY
  • 4. BLOD DETECTS
  • 5. FIND BIGGEST BLOB
  • 6. GET BOUNDING BOX
  • 7. PERCENTAGE LIMIT
  • 9. CROP WITH RESIZE


Requirements
  • GNU Octave, version 3.6.4
  • Octave with image package

       
#! /bin/octave -qf
# a sample Octave program

% @Author Miroslav Bodis
% created: 2014-10-04
% updated: 2015-10-04

% -- -- CUT BILL/BILLS FROM PICTURE -- --
% 0. VALIDATE INPUT
% 1. RESIZE
% 2. IMOPEN - MORPHOLOGICAL OPERATION
% 4. IMG TO BINARY
% 5. BLOD DETECTS 
% 6. FIND BIGGEST BLOB
% 7. GET BOUNDING BOX
% 8. PERCENTAGE LIMIT
% 9. CROP WITH RESIZE



IMG_TO_BINARY_TRASH = 0.3;
IMG_LIMIT_BILL_DETECT = 1000;
IMG_LIMIT_BILL_PERCENT = 15;
IMG_SIZE_LIMIT = 550;
DEBUG = false;
% DEBUG = true;




% 0 ---- VALIDATE INPUT
%------------------------------
% - input require input image
 arg_list = argv();

 if ( size(arg_list, 1) == 0)
   printf("\nrequired 1 arg, input file \n");
   return;
 endif;  

 %validate input
 if ( size(arg_list, 1) != 1)
     printf("\nERROR invalid input, allowing only one image as input_file! \n\n");
   return;
 endif;

 %input exists
 if (!exist(arg_list{1}))
   fprintf("\n image %s not exists\n", arg_list{1});
   return;
 endif;

 pkg load image;



% 1 ---- RESIZE 
%------------------------------
 img_rgb_full = imread(arg_list{1}); 

 [height, width, rgb] = size(img_rgb_full);
 resize = 1;
 if (width > IMG_SIZE_LIMIT && width > height)
  resize = IMG_SIZE_LIMIT / width;
 elseif (height > IMG_SIZE_LIMIT)
  resize = IMG_SIZE_LIMIT / height;
 endif;

 img_rgb = imresize(img_rgb_full, resize, 'nearest');
 if (DEBUG)
  imwrite(img_rgb, 'debug_0_resize.jpg', 'jpg', 'Quality', 100);
 endif;



% 2 ---- IMOPEN - MORPHOLOGICAL OPERATION
%------------------------------
 gray = rgb2gray(img_rgb);

 % disc = uint8 ([0 0 0 1 0 0 0
 %               0 1 1 1 1 1 0
 %               0 1 1 1 1 1 0
 %               1 1 1 1 1 1 1
 %               0 1 1 1 1 1 0
 %               0 1 1 1 1 1 0
 %               0 0 0 1 0 0 0]);

 disc = uint8 ([0 0 0 0 0 1 0 0 0 0 0
      0 0 0 1 1 1 1 1 0 0 0
      0 0 1 1 1 1 1 1 1 0 0
      0 1 1 1 1 1 1 1 1 1 0 
      0 1 1 1 1 1 1 1 1 1 0
      1 1 1 1 1 1 1 1 1 1 1
      0 1 1 1 1 1 1 1 1 1 0
      0 1 1 1 1 1 1 1 1 1 0
      0 0 1 1 1 1 1 1 1 0 0
      0 0 0 1 1 1 1 1 0 0 0
      0 0 0 0 0 1 0 0 0 0 0]);

 gray = imdilate (imerode(gray, disc), disc);
 if (DEBUG)
  imwrite(gray, 'debug_1_opening.jpg', 'jpg', 'Quality', 100);
 endif;


% 3 ---- IMG TO BINARY
%------------------------------
 bw = im2bw(gray, IMG_TO_BINARY_TRASH);
 if (DEBUG)
  imwrite(bw, 'debug_2_im2bw.jpg', 'jpg', 'Quality', 100);
 endif;



% 4 ---- BLOB DETECTS 
%------------------------------
 cc = bwconncomp(bw, 4);
 % cc.NumObjects % print number of blobs
 % cc.ImageSize % print image size

 % -- -- select blob number 225
 % grain = false(size(bw));
 % grain(cc.PixelIdxList{225}) = true;
 % imwrite(grain, 'output.jpg', 'jpg', 'Quality', 100);



% 5 ---- FIND BIGGEST BLOB 
%------------------------------
 % -- -- biggest blob
 % numPixels = cellfun(@numel,cc.PixelIdxList); 
 % [biggest,idx] = max(numPixels);   

 % -- find more bigger objects
 object = 0;
 for j = 1:cc.NumObjects  
  
  [s1,s2] = size(cc.PixelIdxList{j});

  if (s1 > IMG_LIMIT_BILL_DETECT)
   object+=1;   
   bw = false(size(bw));    % black img   
   bw(cc.PixelIdxList{j}) = true; % add selected object  
   if (DEBUG)
    imwrite(bw, 'debug_34_biggest_bloc.jpg', 'jpg', 'Quality', 100);
   endif;



% 6 ---- GET BOUNDING BOX
%------------------------------
   boundaries = bwboundaries(bw);
   numberOfBoundaries = size(boundaries);

   from = boundaries{1};
   fromx = min(from(:,1));
   fromy = min(from(:,2));

   to = boundaries{2};
   tox = max(to(:,1));
   toy = max(to(:,2));


   if (DEBUG)
    for k = 1 : numberOfBoundaries
        thisBoundary = boundaries{k};
        plot(thisBoundary(:,2), thisBoundary(:,1), 'g', 'LineWidth', 2); 
    end  
    print("debug_5_boundbox_plot.png", "-dpng");
   endif;



% 7 ---- PERCENTAGE LIMIT
%------------------------------
   l=tox-fromx;
   w=toy-fromy;
   [x,y,rgb] = size(img_rgb);
   blob_percent_area = ((l*w) / (x*y) *100)
   if ( blob_percent_area > IMG_LIMIT_BILL_PERCENT)  

% 8 ---- CROP WITH RESIZE 
%------------------------------
   extra = (1/resize);
   x1 = round( fromx * extra);
   x2 = round( (x1 + l * extra) );
   y1 = round( fromy * extra );
   y2 = round( (y1 + w * extra) );

   crop_img = img_rgb_full(x1:x2, y1:y2);

    name = char ([98, 105, 108, 108, 45, 48+object]); % bill-1, bill-2 ... 
    imwrite(crop_img, name, 'jpg', 'Quality', 100);

   endif; 



  endif; 
 endfor


        
         

Share:

Thursday, September 11, 2014

CodeIgniter log

Custom log helper for CodeIgniter framework 

For the last few months we were working with the PHP CodeIgniter framework. Every time I was debugging and wanted to write down some variable I had to write this log_messge('error', $tog_this_var ). 18 characters! Horrible. If the variable was boolean, object or array I'd have to write more or add some if statements. So I decided to write a small helper. Maybe you will find it useful.

       
<?
if ( !function_exists('l') && !function_exists('l2') && !function_exists('get_parameter_value') )
{ 

  function get_parameter_value($param = null)
  {  

    if (is_null($param)){
      return 'NULL';

    }else if (!isset($param)){
      return "parameter is not SET";

    }else if(is_array($param)){
      if (empty($param)){
        return "empty array()";
      }else{
        return print_r($param, true);
      }

    }else if(is_string($param) || is_float($param) || is_int($param)){
 return $param;

    }else if (is_bool($param)){
      return ($param) ? 'TRUE' : 'FALSE';

    }else if (is_object($param)){   
      return var_export($param, true);

    }else{
      return "UNKNOWN TYPE";
   
    }

}

  function l($param1 = null, $msg_prefix = null)
  {   
    return log_message('error', (is_string($msg_prefix) ? " " 
      . $msg_prefix : "") . get_parameter_value($param1) );
  }

  function l2($param1 = null, $param2 = null)
  {   
    return log_message('error', get_parameter_value($param1) . " " . get_parameter_value($param2));
  }
 
}else{
  log_message('error', __FILE__ . ' - function l, l2, get_parameter_value  not created '); 
}

?>


       
<? 
  // example
  l($unknow_variable);  
?>


Share:

Sunday, August 10, 2014

CI invalid session

Strange session behaviour

How does it look like?

Lately at work we've ran into a strange behaviour of an irregular logging out. We used CodeIgniter php framework for developing a web application. We noticed this problem earlier on, but we couldn't repeat it on purpose. This behaviour occurred randomly. We didn't know how to repeat it so it was almost impossible to debug it. But one day, we found that if the screen with the map of the POI positions was repeatidly updated in few seconds intervals, this problem occurred more often.


What was the problem?

The session was not expired as we thought first, the cookie was somehow cut off about 10, 20 symbols. The problem was in CodeIgniter Security class in xss_clean which prevented the cross site scripting. To be exact the offender was regular expression that was trying to filter every onEvent javascript function. So if your generated cookie contained substring "#### on SOMETHING=" it removed the part "SOMETHING=". And application looked like the same as when a session expires.


How to fix it?

There were a few options. One, was to change the regular expression on event (which might lead to a security risk), or to simply overwrite the cut cookie with a full cookie at the right place.
       

<? 
// the first parameter in $evil_attributes do the problematic logging out 
//...
protected function _remove_evil_attributes($str, $is_image)
 {
  // All javascript event handlers (e.g. onload, onclick, onmouseover), style, and xmlns
  $evil_attributes = array('on\w*', 'style', 'xmlns', 'formaction');
 }
// ...
?> 

Share:

Friday, July 18, 2014

Nxt drill photo printer

Drill photo using Android and leJOS


This project consists of two parts. First is an android application which communicates via bluetooth with the Lego NXT cube v1.0. I update a NXT cube with leJOS firmware so I can write code in Java.


Android

Android application contains more than a drilling a picture. First it was a controller for all the small programs like follow line, use accelerometer to move with robot, etc. You can also set the type of the robot with according programs. After pairing via bluetooth with NXT cube you can play with it. On pictures below you can see the screen shots of the application.

I also try to control the movement of Tribot with colors glued to my hand. I used a phone's back camera to recognize colors with the phone placed on a tripod. I had a playable demo but it wasn't as awesome as I imagined before. Anyway, my first idea was for the robot to recognize hand gestures, but it wasn't giving good results because of the transfer speed (I needed to to change directions in less than half second). Maybe I'll finish it later. So, I switched to the printer idea.


NXT / leJOS

Some people say, build a robot, that can't take more than a few minutes. It's not true! It can take many hours/days to build something that works, is stable and you need lots of LEGO pieces (I had to order from bricklink.com three times). In the process of making it, I only encountered one bigger problem. Regardless of a very paintful debugging on small NXT screen which can carry only few lines of text, the problem was memory. NXT cube has a very limited memory so I couldn't send full picture to the cube. I had to send it by parts. As the printing is a very slow process I needed to set the phone on a charger, set screen to wake up and let it send the picture full time part by part (biggest picture took 24 hours!).

My last problem was that I needed a very thin and long drill. After visiting a few modeling shops, I realized that the length of the drill I wanted was not produced. So I had to make a workaround and superglue some sand to the rest of drill to prolong the drill part. And it works! I used a floral foam for printing which is one on the most fine material I found.

But wait there's more! The drill head is replaceable and you can draw a B&W picture (also a very slow process). You can see all the results on the bottom of this page.

What can I say, I finally full filled my child dream to build a robot that can do cool stuff. I'm planning to build more robots in the future.


Update

As I mentioned before the printing process is very slow, so I added e-mail notifications after the printing was finished.

           
  
  



Share:

Monday, April 14, 2014

Auto image rotation

Using Tesseract OCR to autorotate scanned text to right rotation.

Problem

From time to time, everyone has a bunch images of text that they don't want to rotate one by one to their right orientation.

Solution

I decided to write a small script that auto detects the orientation of pages based on OCR recognition and dictionary matching. First I used open source tesseract as OCR for parsing text from images. Second, I wrote down a list of most probably occurring words in a text (there are only 5-6 in example below, feel free to write your own). Finally the images rotate and parse every rotation through OCR and test with dictionary. As the OCR accuracy isn't 100% I used some small deviation on comparing words. See code below.

Just copy these 3 files listed below into your ~/bin directory and run tesseract_rotate_all


Dependencies

You need to install perl re::engine::TRE - TRE regular expression engine

download here


recognize_good_rotation

perl script to evaluate the tesseracted text if it contains any of the word in our dictionary
       


#use lib "/root/perl5/lib";
# @author Miroslav Bodis 2014

use strict; 
use warnings;

my $file = shift;
 my $rotati
my $debug_mode = shift;

my $find = 0;

if (!defined $debug_mode){
 $debug_mode = 0; 
 # $debug_mode = 1; # TODO use for more details 
}

if ($debug_mode == 1){ 
 print "file:" . $file."\n";
 print $rotation . "\n";
 print "debug mode: " . $debug_mode."\n";
}

my @recognize_words = ('then', 'change', 'when', 'over', 'suddenly', 'another');

open(my $fh, "<", $file) or die "cannot open file";

while(<$fh>)  {
 chomp;
 
 my $line = $_;
 {
  use re::engine::TRE max_cost => 1;

  foreach (@recognize_words) {

   if ($line =~ /$_/i) {
    
    $find += 1;

    if ($debug_mode == 1){ 
     print "match word: " . $_ . "\n";
    }    
   }
  }
 }
}

close $fh;

if ($find > 2){
 exit 0;
}

exit 1;




tesseract_rotate

shell script to auto rotate one image
       

#! /bin/sh

# @author Miroslav Bodis 2014


if [ -z "$1" ]
then
 echo "
 @author Miroslav Bodis 2014
 
 #   script to autorotate image of printed text
 # - inpnut image try all 4 rotations (0, 90, 270, 180)
 # - tesseract current rotation
 # - use your dictionary to find word in tesseracted text (with some tollerance - used TRE max_cost => 1)
 #   - see log for results
 #   - TODO: copy script to your bin folder e.g.: \"~/bin/recognize_good_rotation.pl\"
 # \$1 -> \"input_image\"" 
 exit
fi;


# required 1 arguments
if [ -z "$1" ]
then
 echo "required 1 arguments \"image_name\""
 exit
fi;


help_rotated_img="rotation_help.jpg"
help_ocr_out="output_ocr"
help_ocr_out_txt="$help_ocr_out.txt"
find=1


# 0 - rotation
echo "image $1 try rotation 0"
tesseract -l slk $1 $help_ocr_out 
perl ~/bin/recognize_good_rotation.pl $help_ocr_out_txt 'rotation 0'
find=$?


# 90 - rotation clockwise
if [ $find -eq 1 ] 
then

 echo "image $1 try rotation 90"
 convert $1 -rotate 90 -quality 100 $help_rotated_img
 tesseract -l slk $help_rotated_img $help_ocr_out
 perl ~/bin/recognize_good_rotation.pl $help_ocr_out_txt 'rotation 90'
 
 find=$?
fi;


# 270 - rotation clockwise
if [ $find -eq 1 ] 
then
 
 echo "image $1 try rotation 270"
 convert $1 -rotate 270 -quality 100 $help_rotated_img 
 tesseract -l slk $help_rotated_img $help_ocr_out 
 perl ~/bin/recognize_good_rotation.pl $help_ocr_out_txt 'rotation 270' 

 find=$?
fi;

# 180 - rotation clockwise
if [ $find -eq 1 ] 
then
 
 echo "image $1 try rotation 180"
 convert $1 -rotate 180 -quality 100 $help_rotated_img
 tesseract -l slk $help_rotated_img $help_ocr_out
 perl ~/bin/recognize_good_rotation.pl $help_ocr_out_txt 'rotaiton 180'

 find=$? 
fi;


if [ $find -eq 1 ] 
then
 echo ">>>>>>>>>>>>>>>>> image $1 NOT ROTATED, please update dictionary ! <<<<<<<<<<<<<<<<"
else 
 echo "image $1 ROTATED"
 # if rotated replace new right rotation with old one
 cp $help_rotated_img $1
fi;

rm $help_rotated_img
rm $help_ocr_out_txt


tesseract_rotate_all

loop to do it on the whole folder
       

#! /bin/sh

# @author Miroslav Bodis 2014
# move to current folder with pictures and run "tesseract_rotate_all"

FILES=./*

for f in $FILES
do 
 echo "--- --- --- --- START FILE $f" 
 tesseract_rotate $f
done

echo "finished"


Share: