Bilderkennung zur Qualitätskontrolle in der industriellen Produktion

Wie auf neuronalen Netzen basierende Methoden des maschinellen Sehens revolutionäre Möglichkeiten für die Bilderkennung bieten.

31/12/2019
|
Michael Welsch
&

Kameraanwendungen und Bilderkennung werden seit etwa 30 Jahren zur Qualitätskontrolle in der industriellen Produktion eingesetzt. Dies war schon immer mit einem erheblichen Aufwand an manueller Merkmalsextraktion verbunden. Deshalb werden sie bisher nur für besonders sensible Produktionsschritte eingesetzt. Der Engineering-Aufwand für jede Aufgabe ist individuell und übersteigt die Investitionskosten in Hard- und Software bei weitem. Gegenüber anderen Sensoren hat die Bilderkennung den Vorteil, dass sie für vielfältige Szenarien einsetzbar ist und berührungslos funktioniert. Computer Vision bzw. Methoden des maschinellen Sehens auf Basis neuronaler Netzwerke bieten neue Möglichkeiten. Einerseits werden Klassifikatoren bereits erfolgreich im industriellen Maßstab eingesetzt, allerdings werden manuell vorklassifizierte Daten verwendet. Die Kennzeichnung der Daten ist mit erheblichem Aufwand verbunden und die Ergebnisse bilden für den Nutzer eine Blackbox. Änderungen in der Produktionsumgebung erfordern eine Wiederholung des Vorgangs.

Seit einiger Zeit werden neuronale Netze erforscht, deren Struktur zu sogenannten Autoencodern führt. Im Grundprinzip werden neuronale Schichten zunächst verjüngt und anschließend wieder erweitert. Anstatt die Bezeichnung für einen neuen Datensatz korrekt zu bestimmen, werden die Netze darauf trainiert, das am Eingang am Ende des Netzes erzeugte Bild trotz des Engpasses korrekt wiederzugeben. Bei erfolgreichem Training stehen für jedes Bild im Engpass alle Bildinformationen bzw. Variationsmöglichkeiten in wenigen Zahlenwerten und damit in codierter bzw. komprimierter Form zur Verfügung. Darüber hinaus lässt sich die Qualität durch die Reproduktion der Eingaben leicht überprüfen und Schwankungen im Engpass geben Aufschluss über das Verhalten des Netzes. Dies wird auch als Unsupervised Learning bezeichnet, während unter Supervising die Bereitstellung weiterer Informationen zu den Daten, wie beispielsweise beispielhafte Labels, verstanden wird.

Abbildung1: Neuronales Netzwerk

Durch die Anwendung des trainierten Autoencoders wird jedes Bild nun durch einen Zahlenvektor im Feature Space dargestellt. Bei 2 Zahlenwerten ist der Raum zweidimensional. Jedes Bild entspricht einem Punkt.

Genauer gesagt werden die Freiheitsgrade einer Bildserie im Feature Space dargestellt, während die genaue Bildstruktur statisch in den Gewichten des neuronalen Netzwerks dargestellt wird.

Abbildung 2: Neuronales Netzwerk

Die Freiheitsgrade können jetzt anstelle einer manuell programmierten Funktion der klassischen Bilderkennung verwendet werden, zB zur Zustandsüberwachung. Der manuelle Engineering-Prozess entfällt. Somit können Kameras zur Qualitätskontrolle eingesetzt werden, ohne dass ein manueller Kompromiss zwischen Kosten und Nutzen eingegangen werden muss.

Um dies zu demonstrieren, wurde zunächst ein Versuchsaufbau konzipiert, der praxisrelevant ist, einen flexiblen Aufbau beinhaltet und sorgt für definierte Laborbedingungen. Der Aufbau ist eine miniaturisierte Tür, die kontinuierlich geöffnet und geschlossen werden kann. Gleichzeitig kann die Beleuchtungsintensität variiert werden, wodurch zwei unabhängige externe und zufällig generierte Freiheitslinien eingestellt werden können, die nun vom Auto-Encoder als solche, kontinuierliche Freiheitsgrade erkannt werden sollen.

< p>Abbildung 3 zeigt einen optimierten Aufbau mit Legosteinen und einer elektronischen Steuerung von Türwinkel und Beleuchtung, der kompakte Abmessungen von 25x25x25 hat. Die Steuerung des Motors und der Beleuchtung übernimmt ein Raspberry Pi und das Training und die Echtzeitinformationen erfolgen auf einem Nvidia Jetson Nano. Mit einer Webapp kann das Training ferngesteuert und interaktiv gesteuert werden.

legobox_annotations
Abbildung 3: Legobox einrichten
Abbildung 4: Legobox Setup

Das Bild der verwendeten Basler-Industriekamera wurde auf 200x200 Pixel verkleinert und ein geeignetes Objektiv ausgewählt, um die Szene ohne Autofokus aufzunehmen.

Ein klassischer Auto-Encoder ist zeichnet sich durch gute Komprimierungsfähigkeit und gute Rekonstruktionsqualität aus. Es entstehen jedoch Diskontinuitäten im Merkmalsraum. Dies drückt sich beispielsweise dadurch aus, dass die Öffnung der Tür nicht kontinuierlich abgebildet wird, sondern eine Diskontinuität aufweist und den Merkmalsraum in zwei Segmente abbildet. Merkmalsvektoren, die nicht genau auf den Kurven liegen, liefern undefinierte und unbrauchbare Rekonstruktionen. Um beliebige Kombinationen von Merkmalsvektoren nutzen zu können und somit den gesamten Merkmalsraum nutzen zu können, wurde das Konzept des GAN (Generative Adversarial Networks) eingeführt. Für die Zustandsüberwachung ist dies jedoch nicht geeignet. Für diesen speziellen Anwendungsfall steht nicht eine qualitativ hochwertige Rekonstruktion und beliebige Kombination von Merkmalen im Vordergrund, sondern ein möglichst kompakter Merkmalsraum, der Diskontinuitäten verhindert, aber nicht jede Kombination abbilden soll. Für diesen Kompromiss hat sich ein klassischer Auto-Encoder bewährt, der um ein zweites Diskriminator-Netzwerk erweitert wird, das für eine definierte Verteilung des Feature Space sorgt und so für Kontinuität sorgt. Man könnte sagen, dass dieser Adversarial Autoencoder die Vorteile beider Ansätze vereint. Im Folgenden wird der Code zum Aufbau des Netzwerks in Keras gezeigt, einem Framework zur Modellierung neuronaler Netze, das die Verwendung verschiedener Frameworks im Backend ermöglicht. Hier wurden sowohl Tensorflow als auch PlaidML alternativ verwendet, es wurden jedoch keine großen Unterschiede festgestellt:

classAAE():



\# --------- -------------------------------------------------- -------------------

\# Erstellen Sie den gegnerischen Autoencoder und laden Sie die Funktionen aus der letzten

\# Trainingssitzung.



def\_\_init\_\_(self,epochs=5,batchSize=32):

self.imageShape=(200,200,3)
< br>self.latentDim=2

self.channels=3

self.epochs=epochs

self.batchSize=batchSize

self.latentPoints= \[]



optimizer=Adam(0.0002,0.5)

metric='accuracy'

binaryLoss='binary_crossentropy'



\# build diskriminator

self.discriminator=self.buildDiscriminator()

self.discriminator.compile(optimizer=optimizer,loss=binaryLoss,
< br>metrics=\[metric])



\# Build-Encoder und -Decoder

self.encoder=self.buildEncoder()

self. decoder=self.buildDecoder()



\# Bildeingabe definieren

image=Input(shape=self.imageShape)


< br>\# EncoderResult sind die Features, die

\# vom Encoder aus den Bildern extrahiert wurden

encoderResult=self.encoder(image)



\# decoderResult sind die Bilder, die vom Decoder

\# aus dem gesetzten EncoderResult

decoderResult=self.decoder(encoderResult)



\# rekonstruiert wurden Diskriminator zu Untrainable

forlayerinself.discriminator.layers:

layer.trainable=False



\# diskriminatorResult ist die Auswertung des EncoderResult

\# durch den Diskriminator

discriminatorResult=self.discriminator(encoderResult)



\# Erstellen Sie das Autoencoder-Modell, indem Sie Bilder als Eingabe zuweisen und
< br>\# rekonstruierte Bilder (decoderResult) & diskriminatorResult als Ausgaben

self.autoencoder=Model(image, \[decoderResult, diskriminatorResult])



\# Autoencoder kompilieren, Verlustfunktion für die Differenz festlegen
< br>\# zwischen Eingabebild und rekonstruiertem Bild zu MSE &

\# Verlustfunktion des Diskriminatorergebnisses zu binärer Kreuzentropie

self.autoencoder.compile(optimizer=optimizer,

loss=\['mse','binary_crossentropy'],

loss_weights=\[0.999,0.001])



self.loadLatentPoints()
< br>

\# -------------------------- -------------------------------------

\# Erstellen Sie den Encoder und kehren Sie zurück das Modell



defbuildEncoder(self):



encoder=Sequential()



encoder. add(Conv2D(32,kernel_size=5,

input_shape=self.imageShape,padding='same',

kernel_initializer='random_uniform',

bias_initializer='zeros '))



encoder.add(LeakyReLU(alpha=0.2))

encoder.add(MaxPooling2D(pool_size=(2,2)))



encoder.add(Conv2D(64,kernel_size=5,padding='same',

kernel_initializer='random_uniform',

bias_initializer='zeros' ))

\# encoder.add(BatchNormalization(axis=1))



encoder.add(LeakyReLU(alpha=0.2))

Encoder.add(MaxPooling2D(pool_size=(5,5)))



encoder.add(Conv2D(128,kernel_size=5,padding='same',

kernel_initializer='random_uniform',

bias_initializer='zeros'))

\# encoder.add(BatchNormalization(axis=1))



encoder.add(LeakyReLU(alpha=0.2))

encoder.add(MaxPooling2D(pool_size=(5,5)))



encoder.add(Flatten ())



encoder.add(Dense(self.latentDim,kernel_initializer='random_uniform',

bias_initializer='zeros'))



encoder.summary()



image=Input(shape=(self.imageShape))



latentCode=encoder (Bild)



returnModel(image, latentCode)



\# --------------- -------------------------------------------------- ------------

\# Erstellen Sie den Decoder und geben Sie das Modell zurück



defbuildDecoder(self):

decoder=Sequential()



decoder.add(Dense(128\*4\*4,activation="relu",

input_dim=self.latentDim,< br>
kernel_initializer='random_uniform',

bias_initializer='zeros'))



decoder.add(Reshape((4,4,128)))< br>
\# decoder.add(BatchNormalization())



decoder.add(LeakyReLU(alpha=0.2))



decoder.add(Conv2DTranspose(64,kernel_size=5,strides=5,

padding="same",

kernel_initializer='random_uniform',

bias_initializer='zeros '))

\# decoder.add(BatchNormalization())

decoder.add(LeakyReLU(alpha=0.2))



decoder. add(Conv2DTranspose(32,kernel_size=5,strides=5,

padding="same",

kernel_initializer='random_uniform',

bias_initializer='zeros') )

\# decoder.add(BatchNormalization())

decoder.add(LeakyReLU(alpha=0.2))



decoder.add( Conv2DTranspose(self.channels,kernel_size=5,strides=2,

padding="same",

kernel_initializer='random_uniform',

bias_initializer='zeros') )

\# decoder.add(BatchNormalization())

decoder.add(LeakyReLU(alpha=0.2))

decoder.add(Activation("tanh") )



decoder.summary()



z=Input(shape=(self.latentDim,))

reconstructionResult =decoder(z)



returnModel(z, constructionResult)



\# ------------- -------------------------------------------------- --------------

\# Erstellen Sie den Diskriminator und geben Sie das Modell zurück



defbuildDiscriminator(self):
< br>discriminator=Sequential()



discriminator.add(Dense(300,activation='relu',

input_dim=self.latentDim,

kernel_initializer='random_uniform',

bias_initializer='zeros'))



discriminator.add(Dense(300,activation='relu',
< br>kernel_initializer='random_uniform',

bias_initializer='zeros'))



discriminator.add(Dense(1,activation='sigmoid',

kernel_initializer='random_uniform',

bias_initializer='zeros'))



discriminator.summary()



encoderResult=Input(shape=(self.latentDim,))

discriminatorResult=discriminator(encoderResult)



returnModel(encoderResult, diskriminatorResult)



\# ---------------------------- -----------------------------------

\# Trainieren Sie das gegnerische Autoencoder-Modell und speichern Sie es die Funktionen & Modell

\#

\# EINGABE: - „Dateipfade“ Pfad zum Ordner mit den Trainingsdaten

\# relativ zum Ordner, in dem sich dieser Python-Code befindet

\# befindet sich in.



deftrain(self,filepaths):

forepochinrange(self.epochs):

random.shuffle( filepaths)

batches=len(filepaths)//self.batchSize



valid=np.ones((self.batchSize,1))

fake=np.zeros((self.batchSize,1))

foriinrange(batches):

print('epoch:{}batch:{}/{}'.format( epoch+1, i+1,

batches))

images=self.loadImages(

filepaths\[i\*self.batchSize:(i+1) \*self.batchSize])



latent_fake=self.encoder.predict(images)

latent_real=np.random.uniform(-2,2,size= (self.batchSize,

self.latentDim))



discriminator_loss_real=self.discriminator.train_on_batch(

latent_real, valid)
< br>discriminator_loss_fake=self.discriminator.train_on_batch(

latent_fake, fake)

discriminator_loss=0.5*np.add(

discriminator_loss_real, discriminator_loss_fake)



genLoss=self.autoencoder.train_on_batch(

images, \[images, valid])



print(discriminator_loss\[0], genLoss \[0])



self.drawLatentPoints(filepaths)

self.saveModel()



\# -- -------------------------------------------------- -------------------------

\# Zusätzliche Hilfsfunktionen sowie der gesamte Code für das Backend

\# finden Sie im Git-Repo

\# -------------------------------- ---------------------------------------------

Frontend wurde so konzipiert, dass auf Knopfdruck unterschiedliche Bilder im Backend angezeigt und mit einem vorkonfigurierten Netzwerk trainiert werden können. Die Funktionen wurden in Echtzeit in einem Diagramm abgebildet und eine rechteckige Fensterauswahl ermöglicht eine rudimentäre Zustandsüberwachung der Features.

ui_plot
Abbildung 5: UI-Plot
ui_gallery
Abbildung 6: UI-Galerie

Abbildung 7: Zustandsüberwachung
Abbildung 8: Zustandsüberwachung
Abbildung 9: Zustandsüberwachung
Folgt uns auf
We do not only optimize production processes, but also our website! For this, we use tools such as cookies for analysis and marketing purposes. You can change your cookie settings at any time. Information and Settings