Skip to content

Open CV trivial circle detection -- how to get least squares instead of a contour?

Wenn Sie ein Detail finden, das Sie nicht verstehen, können Sie es in den Kommentaren hinterlassen und wir werden Ihnen schnell helfen.

Lösung:

Hier ist eine weitere Möglichkeit, einen Kreis einzupassen, indem man den äquivalenten Kreismittelpunkt und Radius aus dem Binärbild mit Hilfe von verbundenen Komponenten ermittelt und daraus mit Python/OpenCV/Skimage einen Kreis zeichnet.

Eingabe:

enter image description here

import cv2
import numpy as np
from skimage import measure

# load image and set the bounds
img = cv2.imread("dark_circle.png")

# convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# blur
blur = cv2.GaussianBlur(gray, (3,3), 0)

# threshold
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# apply morphology open with a circular shaped kernel
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
binary = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)

# find contour and draw on input (for comparison with circle)
cnts = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
c = cnts[0]
result = img.copy()
cv2.drawContours(result, [c], -1, (0, 255, 0), 1)

# find radius and center of equivalent circle from binary image and draw circle
# see https://scikit-image.org/docs/dev/api/skimage.measure.html#skimage.measure.regionprops
# Note: this should be the same as getting the centroid and area=cv2.CC_STAT_AREA from cv2.connectedComponentsWithStats and computing radius = 0.5*sqrt(4*area/pi) or approximately from the area of the contour and computed centroid via image moments.
regions = measure.regionprops(binary)
circle = regions[0]
yc, xc = circle.centroid
radius = circle.equivalent_diameter / 2.0
print("radius =",radius, "  center =",xc,",",yc)
xx = int(round(xc))
yy = int(round(yc))
rr = int(round(radius))
cv2.circle(result, (xx,yy), rr, (0, 0, 255), 1)

# write result to disk
cv2.imwrite("dark_circle_fit.png", result)

# display it
cv2.imshow("image", img)
cv2.imshow("thresh", thresh)
cv2.imshow("binary", binary)
cv2.imshow("result", result)
cv2.waitKey(0)

Das Ergebnis zeigt die Kontur (grün) im Vergleich zur Kreisanpassung (rot):

enter image description here

Kreisradius und Zentrum:

radius = 117.6142467296168   center = 220.2169911178609 , 150.26823599797507

Eine Methode der kleinsten Quadrate (zwischen den Konturpunkten und einem Kreis) kann mit Scipy ermittelt werden. Siehe zum Beispiel:

https://gist.github.com/lorenzoriano/6799568.

https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html.

Ich würde vorschlagen, eine Maske wie in der Antwort von Nathancy zu berechnen, aber dann einfach die Anzahl der Pixel in der Maske zu zählen opening die er berechnet hat (was eine unverzerrte Schätzung der Fläche des Lochs ist), und dann die Fläche in einen Radius zu übersetzen mit radius = sqrt(area/pi). Auf diese Weise erhält man den Radius des Kreises, der die gleiche Fläche wie das Loch hat, und dies entspricht einer Methode zur Ermittlung des am besten passenden Kreises.

Eine andere Methode zur Ermittlung eines bestgeeigneten Kreises besteht darin, die Kontur des Lochs (wie in cnts von cv.findContours in der Antwort von nethancy), seinen Schwerpunkt zu finden und dann den mittleren Abstand jedes Scheitelpunkts zum Schwerpunkt zu berechnen. Dies würde ungefähr* einer Anpassung eines Kreises an den Lochumfang nach der Methode der kleinsten Quadrate entsprechen.

* Ich sage "ungefähr", weil die Scheitelpunkte der Kontur eine Annäherung an die Kontur sind und die Abstände zwischen diesen Scheitelpunkten wahrscheinlich nicht einheitlich sind. Der Fehler sollte aber sehr klein sein.


Hier ist ein Code-Beispiel mit DIPlib (Offenlegung: Ich bin ein Autor) (Hinweis: Die import PyDIP Anweisung unten erfordert die Installation von DIPlib, und Sie können es nicht mit pipEs gibt eine Binärversion für Windows auf der GitHub-Seite, ansonsten muss man sie aus den Quellen bauen).

import PyDIP as dip
import imageio
import math

img = imageio.imread('https://i.stack.imgur.com/szvc2.jpg')
img = dip.Image(img[:,2600:-1])
img.SetPixelSize(0.01, 'mm')      # Use your actual values!
bin = ~dip.OtsuThreshold(dip.Gauss(img, [3]))
bin = dip.Opening(bin, 25)
#dip.Overlay(img, bin - dip.BinaryErosion(bin, 1, 3)).Show()

msr = dip.MeasurementTool.Measure(dip.Label(bin), features=['Size', 'Radius'])
#print(msr)

print('Method 1:', math.sqrt(msr[1]['Size'][0] / 3.14), 'mm')
print('Method 2:', msr[1]['Radius'][1], 'mm')

Die MeasurementTool.Measure Funktion errechnet 'Size', das ist die area; und 'Radius'die den Höchstwert, den Mittelwert, den Mindestwert und die Standardabweichung der Entfernungen zwischen jedem Randpixel und dem Schwerpunkt liefert. Aus 'Radius'wird der 2. Wert, der mittlere Radius, entnommen.

Dies gibt aus:

Method 1: 7.227900647539411 mm
Method 2: 7.225178113501325 mm

Beachte aber, dass ich eine zufällige Pixelgröße (0,01mm pro Pixel) zugewiesen habe, du musst den richtigen Pixel-zu-mm-Umrechnungswert eingeben.

Beachten Sie, dass die beiden Schätzungen sehr nahe beieinander liegen. Beide Methoden sind gute, unverzerrte Schätzungen. Die erste Methode ist rechnerisch günstiger.

Bewertungen und Rezensionen

Wenn Sie einen Verdacht oder eine Möglichkeit haben, unseren Beitrag zu fördern, können Sie eine Chronik erstellen, und wir werden sie mit Interesse studieren.



Nutzen Sie unsere Suchmaschine

Suche
Generic filters

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.