@@ -100,7 +100,7 @@ def __str__(self):
100100 :return: compact string representation
101101 :rtype: str
102102 """
103- return f"Blob[{ self .id } ](area={ self ._moments .m00 :.2g} , color={ self .color } , parent={ self .parent .id if self .parent else None } "
103+ return f"Blob[{ self .id } ](area={ self .moments .m00 :.2g} , color={ self .color } , parent={ self .parent .id if self .parent else None } "
104104
105105 def __repr__ (self ):
106106 return str (self )
@@ -302,13 +302,110 @@ def __init__(self, image=None, kulpa=True, **kwargs):
302302
303303 blob .contourpoint = blob .perimeter [:, 0 ]
304304
305- ## For a single set pixel OpenCV returns all moments as zero, skip such blobs
306- ## TODO handle this situation by setting m00=1, m10=x, m01=y etc.
305+ ## For a single set pixel OpenCV returns all moments as zero, let's fix it
307306 if blob ._moments ["m00" ] == 0 :
308307 runts += 1
309- else :
310- self .data .append (blob )
308+
309+ # Raw moments
310+ for uv in contour :
311+ for p , q in [
312+ (0 , 0 ),
313+ (1 , 0 ),
314+ (0 , 1 ),
315+ (2 , 0 ),
316+ (1 , 1 ),
317+ (0 , 2 ),
318+ (3 , 0 ),
319+ (2 , 1 ),
320+ (1 , 2 ),
321+ (0 , 3 ),
322+ ]:
323+ u = uv [0 ]
324+ v = uv [1 ]
325+ blob ._moments [f"m{ p } { q } " ] += u ** p * v ** q
326+
327+ # Central moments
328+ blob ._moments ["mu10" ] = blob ._moments ["mu01" ] = 0
329+ blob ._moments ["mu20" ] = (
330+ blob ._moments ["m20" ]
331+ - blob ._moments ["m10" ] ** 2 / blob ._moments ["m00" ]
332+ )
333+ blob ._moments ["mu02" ] = (
334+ blob ._moments ["m02" ]
335+ - blob ._moments ["m01" ] ** 2 / blob ._moments ["m00" ]
336+ )
337+ blob ._moments ["mu11" ] = (
338+ blob ._moments ["m11" ]
339+ - blob ._moments ["m10" ] * blob ._moments ["m01" ] / blob ._moments ["m00" ]
340+ )
341+
342+ # Third-order central moments
343+ blob ._moments ["mu30" ] = (
344+ blob ._moments ["m30" ]
345+ - 3
346+ * blob ._moments ["m20" ]
347+ * blob ._moments ["m10" ]
348+ / blob ._moments ["m00" ]
349+ + 2 * (blob ._moments ["m10" ] ** 3 ) / (blob ._moments ["m00" ] ** 2 )
350+ )
351+ blob ._moments ["mu03" ] = (
352+ blob ._moments ["m03" ]
353+ - 3
354+ * blob ._moments ["m02" ]
355+ * blob ._moments ["m01" ]
356+ / blob ._moments ["m00" ]
357+ + 2 * (blob ._moments ["m01" ] ** 3 ) / (blob ._moments ["m00" ] ** 2 )
358+ )
359+ blob ._moments ["mu21" ] = (
360+ blob ._moments ["m21" ]
361+ - 2
362+ * blob ._moments ["m11" ]
363+ * blob ._moments ["m10" ]
364+ / blob ._moments ["m00" ]
365+ - blob ._moments ["m20" ] * blob ._moments ["m01" ] / blob ._moments ["m00" ]
366+ + 2
367+ * (blob ._moments ["m10" ] ** 2 * blob ._moments ["m01" ])
368+ / (blob ._moments ["m00" ] ** 2 )
369+ )
370+ blob ._moments ["mu12" ] = (
371+ blob ._moments ["m12" ]
372+ - 2
373+ * blob ._moments ["m11" ]
374+ * blob ._moments ["m01" ]
375+ / blob ._moments ["m00" ]
376+ - blob ._moments ["m02" ] * blob ._moments ["m10" ] / blob ._moments ["m00" ]
377+ + 2
378+ * (blob ._moments ["m01" ] ** 2 * blob ._moments ["m10" ])
379+ / (blob ._moments ["m00" ] ** 2 )
380+ )
381+
382+ # Normalised central moments
383+ blob ._moments ["nu20" ] = blob ._moments ["mu20" ] / (
384+ blob ._moments ["m00" ] ** (1 + (2 / 2 ))
385+ )
386+ blob ._moments ["nu02" ] = blob ._moments ["mu02" ] / (
387+ blob ._moments ["m00" ] ** (1 + (2 / 2 ))
388+ )
389+ blob ._moments ["nu11" ] = blob ._moments ["mu11" ] / (
390+ blob ._moments ["m00" ] ** (1 + (2 / 2 ))
391+ )
392+
393+ # Third-order normalised moments
394+ blob ._moments ["nu30" ] = blob ._moments ["mu30" ] / (
395+ blob ._moments ["m00" ] ** (1 + (3 / 2 ))
396+ )
397+ blob ._moments ["nu03" ] = blob ._moments ["mu03" ] / (
398+ blob ._moments ["m00" ] ** (1 + (3 / 2 ))
399+ )
400+ blob ._moments ["nu21" ] = blob ._moments ["mu21" ] / (
401+ blob ._moments ["m00" ] ** (1 + (3 / 2 ))
402+ )
403+ blob ._moments ["nu12" ] = blob ._moments ["mu12" ] / (
404+ blob ._moments ["m00" ] ** (1 + (3 / 2 ))
405+ )
406+
311407 allblobs .append (blob )
408+ self .data .append (blob )
312409
313410 ## second pass: equivalent ellipse
314411
0 commit comments