Perimeter is relatively easy and from Bernd Jähne book ( Digital Image Processing 5th: 508-511 ), it can be calculated as below code.
double CalculatePerimeter( const ChainCode& chains ) { double p = 0; double sqrt2 = sqrt( 2.0 ); for( size_t i=0; i < chains.size(); ++i ) { if( chains[i]%2 == 0 ) { p += 1.0; } else { p += sqrt2; } } return p; }Bernd Jähne also showed how to get the area using a chain code but it doesn't work well when the chain code is 1 pixel width or height e.g. ( 0 0 4 4 ) will return 2 instead of 3. Refer below image how the area end up 1 pixel short.
Cris Luengo ( Cris's Image Analysis Blog : More chain code measures ) shows a matlab code that works well with 1 pixel wide chain code. In above case of 3 pixels, it adds two more addition when the direction changes which add up 1 more pixel. Refer below image. Here is the C/C++ code snippet that is converted from the above Cris Luengo's matlab code.
// making a circular chain code by inserting the last code to the front ChainCode cc; cc.push_back( chains[ chains.size()-1 ] ); for( size_t i=0; i < chains.size(); ++i ) { cc.push_back( chains[i] ); } m_Area = CalculateArea( cc ); int CalculateArea( const ChainCode& cc ) // a circular chain { int (&M)[8][8]( TableAreaIncrement ); int B = 10, A = 0; for( size_t i=1; i < cc.size(); ++i ) { uint8_t c_i = cc[i]; uint8_t c_im1 = cc[i-1]; switch( M[c_im1][c_i] ) { case 'X' : throw Exception( StringFormat::As( _T("Invalid chain code found as %d->%d"), c_im1, c_i )); case 'A': A += -B+1; break; case 'B': A += -B; break; case 'C': A += B; break; case 'D': A += B-1; break; } switch( c_i ) { case 0: A+=B; break; case 1: A+=(++B); break; case 2: B++; break; case 3: A+=-(++B)+1; break; case 4: A+=-B+1; break; case 5: A+=-(--B)+1; break; case 6: B--; break; case 7: A+=(--B); break; } } return A; } int ChainCodeProperties::TableAreaIncrement[8][8] = { {'0','1','B','X','A','A','6','7'}, {'0','1','B','B','X','A','6','7'}, {'C','C','2','3','4','X','C','C'}, {'C','C','2','3','4','5','X','C'}, {'C','C','2','3','4','5','D','X'}, {'X','C','2','3','4','5','D','D'}, {'0','X','A','A','A','A','6','7'}, {'0','1','X','A','A','A','6','7'}, };The center of mass can be calculated by summing up x and y coordinates. But just simply adding up does not cover 1 pixel wide chain code as below image. The (1,1) pixel added two times instead one time. The compensation can be done by adding more pixels when a direction changes in opposite way as the idea of Cris Luengo's area calculation. Below is the C/C++ code snippet that calculate the centroid.
void CalculateCentroid( const ChainCode& cc, double* cx, double *cy ) { int (&O)[8][2]( TableOffsetIncrement ); int vpc = 0; // visitied pixel count int sx = 0, sy = 0; // sum of x and y int px = 0, py = 0; for( size_t i=1; i < cc.size(); ++i ) { uint8_t c_i = cc[i]; uint8_t c_im1 = cc[i-1]; if( c_i != c_im1 && (c_i & 0x3 ) == ( c_im1 & 0x3 ) ) { // when direction changes in opposite direction // e.g. 0 -> 4, 4 -> 0, 1 -> 5, ... vpc++; sx += px; sy+=py; } px += O[ c_i ][0]; py += O[ c_i ][1]; vpc++; sx += px; sy+=py; } *cx = (double)sx / (double)vpc; *cy = (double)sy / (double)vpc; } int ChainCodeProperties::TableOffsetIncrement[8][2] = { { 1, 0 }, { 1, -1 }, { 0, -1 }, { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, 1 }, { 1, 1 }, };It works well most of the case but it fails if there is a pixel which is visited more than 2 times as below image. It has (1,1) pixel counted 3 times but it should be counted 2 times.