Close

boost::python and Connected Components

A project log for PiMoCap

Turning an RPI into a MoCap Camera. Functional *and* Delicious...

bit-meddlerBit Meddler 07/07/2016 at 11:160 Comments

Boost Python update

This looks interesting, I'll have to take a deeper look at it S.P.E.A.D.. A fast protocol for throwing NumPY Arrays about. Berkeley, so you know they're serious. Could be an interesting reference for what I'm doing. And certainly, I want to throw NumPy arrays about!

So I've compiled the 'Hello World' example natively on the Pi...

#include <boost/python.hpp>
namespace bp=boost::python
int test() {
    return 42;
}
BOOST_PYTHON_MODULE(smoke_test) {
    bp::def( "test", test );
}
Real 0m27.47

Ouch, this is a journey back in time. But I can build on it.

Connected Components Update

Python implementation is done and predictably slow, ~0.55 sec for a basic image with 75 blobs on an i7 ~2.6GHz. I'm not sure what the slow set could be, as it's not doing much. Visiting every pixel of an image *is* going to be slow though.

The core implementation is as follows:

class Region( object ):
    def __init__( self ):
        # Reset does the job
        self.reset()
        
    def __str__( self ):
        return "x{} n{} r{} a{} ".format( self.last_x, self.last_n, self.last_row, self.area )
        
    def __repr__( self ):
        return self.__str__()
        
    def reset( self ):
        # zero out settings
        # BB
        self.bb_x = self.bb_y = 1e6
        self.bb_n = self.bb_m = 0
        # Line scanning / merging
        self.last_row = self.first_row = 0
        self.last_x = self.last_n = 0
        # Statistics about region
        self.area = 0
        
    def updateStats( self, area ):
        self.area += area
        
    def combineFrom( self, other ):
        self.updateBB( other.bb_x, other.bb_y, other.bb_n, other.bb_m )
        self.updateStats( other.area )
        # set the linedata explicitly
    
    def setLineData( self, x, n, r ):
        self.last_x   = x
        self.last_n   = n
        self.last_row = r
        
    def updateBB( self, x, y, n, m ):
        self.bb_x = min( self.bb_x, x )
        self.bb_y = min( self.bb_y, y )
        self.bb_n = max( self.bb_n, n )
        self.bb_m = max( self.bb_m, m )
        
        
def tidyList( regions, range_idx, row ):
    end = len( regions ) - 1
    scan_idx = range_idx
    while( scan_idx < end ):
        if (regions[range_idx].last_row < row ):
            range_idx += 1
        scan_idx += 1
    region_idx = end
    while( region_idx > range_idx ):
        if( regions[region_idx].last_row < row ):
            elem = regions[region_idx]
            regions.remove( elem )
            regions.insert( range_idx, elem )
            range_idx += 1
        region_idx -= 1
    # end
    return range_idx
   
def connectedComponents( img, threashold ):
    rows, cols = len(img), len(img[0]) # assumes row MJR, 1 channel (8-Bit Grey)
    # vars used in algo
    current_region = 0
    region_scan_start = 0
    in_line = False
    # TODO Replace with 'temp' region
    line_start = line_end = area = 0
    cidx = -1
    
    region_list = []
    for ridx in xrange( rows ):
        # do row
        current_region = region_scan_start
        cidx = 0
        in_line = False
        line_start = area = 0
        line_end = -1
        while( cidx < cols ):
            px = img[ridx][cidx]
            if( px > threashold ):
                in_line = True
                line_start = cidx
                area += 1
            else:
                cidx += 1
            while( in_line ):
                cidx += 1
                if( cidx < cols ):
                    if( img[ridx][cidx] < threashold ):
                        in_line = False
                        line_end = cidx
                    else:
                        area += 1
                else:
                    # run out of data
                    in_line = False
                    line_end = cidx
            # if line found
            if( line_end > line_start ):
                # scan regions for merge or insert
                regionscanning = True
                merge_mode = 0
                while( regionscanning ):
                    if( current_region >= len(region_list) ):
                        # end of list.
                        regionscanning = False
                        if( merge_mode == 0 ):
                            # the line hasn't merged, so append
                            newReg = Region()
                            newReg.setLineData( line_start, line_end, ridx )
                            newReg.updateBB( line_start, ridx, line_end, ridx )
                            newReg.updateStats( area )
                            # insert into list
                            region_list.append( newReg )
                            current_region += 1
                    elif( line_start > region_list[current_region].last_n ):
                        # skip towards a region
                        current_region += 1
                    elif( line_end < region_list[current_region].last_x ):
                        regionscanning = False
                        if( merge_mode == 0 ):
                            # insert new region at current_region, before next
                            newReg = Region()
                            newReg.updateBB( line_start, ridx, line_end, ridx )
                            newReg.updateStats( area )
                            newReg.setLineData( line_start, line_end, ridx )
                            # insert into list
                            region_list.insert( current_region, newReg )
                    else:
                        # merging situation
                        if( merge_mode == 0 ):
                            # merge line data into region
                            connectedR = region_list[ current_region ]
                            connectedR.updateBB( line_start, ridx, line_end, ridx )
                            connectedR.updateStats( area )
                            connectedR.setLineData( line_start, line_end, ridx )
                            merge_mode += 1
                            current_region += 1 #?
                        else:
                            # Multi-Merge
                            oldReg = region_list[ current_region ]
                            region_list[ current_region-1 ].combineFrom( oldReg )
                            region_list.remove( oldReg )
                            # update with line data
                            current_region -= 1
                            if( current_region == region_scan_start ):
                                regionscanning = False
                            region_list[ current_region ].setLineData( line_start, line_end, ridx )
                    # if line_end < last_x
                # tidy up
                line_start = area = 0
                line_end = -1
        # ended this row, tidy up region list
        region_scan_start = tidyList( region_list, region_scan_start, ridx )
    # scanned whole img
    return region_list

I could smarten up the 'scanning' section I think, and 'listTidy' keeps rebuilding the list by inserting and dropping elements, so if the job could be done 'in place' that would help. Of course, reimplementing in Threaded, Vectorized C would score some improvements too.

Discussions