Bináris képek vizsgálata

Thresholding

Minden pixel értékét egy küszöbérték felett 1-re, alatta 0-ra változtatjuk. Képfeldolgozáskor általában 1 helyett 255 értékűre van írva az előtér, 0 értékűre a háttér, vagyis az objektumokat tartalmazó pixelek fehérek, a többi pedig fekete. Ekkor bináris kép keletkezik, amelyet könnyen lehet elemezni. A küszöbérték meghatározása a felhasználó feladata, de vannak algoritmusok annak automatikus megtalálására (pl. Otsu módszere).

Adaptív Thresholding

Ha a háttér intenzitása nem egyenletes a képen, akkor lokális, másnéven adaptív módszerrel eredményesebb lehet a bináris kép előállítása. Minden pixel egyedi küszöbértéket kap a környezeténben lévő intenzitások súlyozott átlaga alapján. Ez az adaptív módszer nyilvánvalóan sokkal lassabb, mint a globális.

Terület és középpont

Képek feldolgozásakor integrálás vagy deriválás helyett diszkrét műveletek vannak, mivel egy kép függvénye nem folytonos. Bináris képeken egy objektum területe a fehér képpontok száma, középpontjának koordinátái pedig a fehér képpontok koordinátáinak átlaga a két tengelyen.

$ A = \displaystyle\sum_{y=1}^{H} \displaystyle\sum_{x=1}^{W} I(x,y) $

$ \bar{x} = \dfrac{1}{A} ⋅ \displaystyle\sum_{y=1}^{H} \displaystyle\sum_{x=1}^{W} x ⋅ I(x,y) $

$ \bar{y} = \dfrac{1}{A} ⋅ \displaystyle\sum_{y=1}^{H} \displaystyle\sum_{x=1}^{W} y ⋅ I(x,y) $

Objektum dőlésszöge: PCA

Bináris képen lévő objektum dőlésszögének meghatározására több módszer létezik, ezekből az egyik a PCA, vagyis Principal Component Analysis. Ezt többdimenziós adatok dimenzióinak redukálására használják az adattudományban, de mivel az első főkomponens egy kétdimenziós képen a dőlésszög, így erre az egyszerű feladatra is megfelel. Matematikailag a dőlésszög annak az egyenesnek az x-tengellyel bezárt szöge, amely áthalad a középponton és az objektum pontjaitól mért távolságok összege minimális.

1. lépés: Pontok eltolása

Az első lépés a PCA során a pontok eltolása úgy, hogy az új koordináta-rendszer origója a középpont, vagyis minden pont koordinátáiból ki kell vonni a középpont koordinátáit. A számolás az új pontokon folytatódik.

$ (2,3) \boldsymbol{\rightarrow} (-2,-1.6) $

$ (3,3) \boldsymbol{\rightarrow} (-1,-1.6) $

$ (4,4) \boldsymbol{\rightarrow} (0,-0.6) $

$ (5,5) \boldsymbol{\rightarrow} (1,0.4) $

$ (6;8) \boldsymbol{\rightarrow} (2,3.4) $

2. lépés: Kovarianciamátrix

A kovarianciamátrix átlóján az egyes dimenziók varianciája, másnéven szórásnégyzete található, a mátrix többi eleme pedig az egyes dimenziópárok kovarianciája. Két dimenzió kovarianciája megmutatja, hogy azok mennyire mozognak együtt, vagyis mennyire van közöttük lineáris összefüggés. A mátrix tehát megpróbálja egyszerűen rögzíteni a dimenziók egymás közötti összefüggéseit. Képek esetén mérete 2×2.

$ C = \begin{pmatrix} Var(x') & Cov(x',y') \\ Cov(x',y') & Var(y') \end{pmatrix} $

$ Var(x') = \dfrac{1}{n} ⋅ \displaystyle\sum_{i=1}^{n} (x_i')^2 $

$ Var(y') = \dfrac{1}{n} ⋅ \displaystyle\sum_{i=1}^{n} (y_i')^2 $

$ Cov(x',y') = \dfrac{1}{n} ⋅ \displaystyle\sum_{i=1}^{n} x_i' ⋅ y_i' $

$ C = \begin{pmatrix} 2 & 2.4 \\ 2.4 & 3.44 \end{pmatrix} $

3. lépés: Sajátértékek

Ha egy vektor és egy mátrix össze van szorozva, az eredmény egy újabb vektor, amely az eredetihez képest kétféle változást szenvedhetett el: megváltozhatott a hossza és iránya. Ha adott a mátrix és keresünk hozzá egy olyan vektort, aminek nem fog a szorzás hatására megváltozni az iránya, akkor a mátrix sajátvektorát keressük. Cserébe a sajátvektor az a vektor, amelynek hossza a legnagyobbat változik. A sajátérték azt adja meg, hogy milyen mértékű ez a hosszváltozás. Először a sajátértékeket kell kiszámolni.

$ det(C - \lambda ⋅ I) = 0 $

$ I = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix} $

$ det \begin{pmatrix} 2 - \lambda & 2.4 \\ 2.4 & 3.44 - \lambda \end{pmatrix} = 0 $

A fenti képletekből végül egy másodfokú egyenlet alakul ki, amelynek két megoldása közül a dőlésszöget a nagyobbikból, vagyis a nagyobb jelentősséggel bíró sajátvektornak a sajátértékéből lehet majd kiszámolni.

$ \lambda_1 = 5.226 $

$ \lambda_2 = 0.214 $

4. lépés: Dőlésszög meghatározása

Ismert a sajátérték, így ki kell számolni a hozzá tartozó sajátvektort. Behelyettesítés után a képlet megadja az első főkomponenst (PC.1), de fontos, hogy a sajátvektor nem lehet nullvektor. Az első főkomponens, vagyis a legnagyobb sajátértékhez tartozó sajátvektor egy bináris képen az objektum irányvektora, amelyből könnyen számolható a dőlésszög is. A méret, középpont és dőlésszög ismeretében például egy kamerával szerelt robot magabiztosan rá tud fogni objektumokra. A PCA számítógéppel milliszekundumok alatt lefuthat.

$ (C - \lambda ⋅ I) ⋅ \vec{v} = 0 $

$ \begin{pmatrix} 2 - \lambda & 2.4 \\ 2.4 & 3.44 - \lambda \end{pmatrix} ⋅ \begin{pmatrix} v_1 \\ v_2 \end{pmatrix} = \begin{pmatrix} 0 \\ 0 \end{pmatrix} $

$ \begin{pmatrix} -3.226 & 2.4 \\ 2.4 & -1.786 \end{pmatrix} ⋅ \begin{pmatrix} v_1 \\ v_2 \end{pmatrix} = \begin{pmatrix} 0 \\ 0 \end{pmatrix} $

$ PC. 1 = \begin{pmatrix} 1 \\ 1.344 \end{pmatrix} $

$ \varphi = atan2(1.344, 1) = 53.35° $

Segédlet

Csavarkulcs fénykép: Letöltés

Képszekvencia: Letöltés

Forráskód: Középpont

        
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

# Open the image and convert to grayscale
image = Image.open("path-to-resources/wrench.png").convert("L")

# Convert image to a NumPy array
data = np.array(image, dtype=np.uint8)

# Apply thresholding
threshold = 135
data = np.where(data > threshold, 255, 0)

# Convert image from {0, 255} to {0, 1} (Normalize)
data = data / 255

# Helper arrays to calculate the center
x_range = np.arange(0, data.shape[1])
y_range = np.arange(0, data.shape[0])

# Calculate area and center
area = data.sum()
x_cntr = np.matmul(data, x_range).sum() / data.sum()
y_cntr = np.matmul(data.T, y_range).sum() / data.sum()

# Display the image
plt.imshow(data, cmap="gray")
plt.plot(x_cntr, y_cntr, "og", markersize=5)  # Mark center with green circle
plt.title("Center point")
plt.axis('off')  # Hide the axis
plt.show()
          
        

Forráskód: Dőlésszög

        
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

# Open the image and convert to grayscale
image = Image.open("path-to-resources/wrench.png").convert("L")

# Convert image to a NumPy array
data = np.array(image, dtype=np.uint8)

# Apply thresholding
threshold = 135
data = np.where(data > threshold, 255, 0)

# Convert image from {0, 255} to {0, 1} (Normalize)
data = data / 255

# Helper arrays to calculate the center
x_range = np.arange(0, data.shape[1])
y_range = np.arange(0, data.shape[0])

# Calculate area and center
area = data.sum()
x_cntr = np.matmul(data, x_range).sum() / area
y_cntr = np.matmul(data.T, y_range).sum() / area

# --- PCA (Principal Component Analysis) --- 
# Extract foreground pixel coordinates
y, x = np.nonzero(data)
coords = np.column_stack((x, y))
# Centered array
cntr_coords = coords - (x_cntr, y_cntr)
# Covariance matrix
cov_matrix = np.cov(cntr_coords, rowvar=False)
# Eigen value decomposition (EVD) to find the principal components
eig_vals, eig_vecs = np.linalg.eigh(cov_matrix)
# Eigenvector corresponding to the largest eigenvalue
pr_eig_vec = eig_vecs[:, np.argmax(eig_vals)]
# Orientation angle in radians
orientation = np.arctan2(pr_eig_vec[1], pr_eig_vec[0])

# Start and End point for the orientation line
half_len = 500
x_line = [x_cntr - half_len * np.cos(orientation), x_cntr + half_len * np.cos(orientation)]
y_line = [y_cntr - half_len * np.sin(orientation), y_cntr + half_len  * np.sin(orientation)]

# Display the image
plt.imshow(data, cmap="gray")
plt.plot(x_line, y_line, color="red", linewidth=3)  # Draw a red line on the image
plt.plot(x_cntr, y_cntr, "og", markersize=5)  # Mark center with green circle
plt.title("Center and orientation")
plt.axis('off')  # Hide the axis
plt.show()                    
          
        

Forráskód: Dőlésszög (szekvencia)

        from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import os

# Folder containing the image sequence
folder_path = "path-to-resources/wrench-sequence"

# Set threshold value
threshold = 175

# Loop through each image in the folder
for filename in sorted(os.listdir(folder_path)):
    if filename.startswith("wrench00108"):
        # Open the image and convert to grayscale
        image_path = os.path.join(folder_path, filename)
        image = Image.open(image_path).convert("L")

        # Convert image to a NumPy array
        data = np.array(image, dtype=np.uint8)

        # Apply thresholding
        data = np.where(data > threshold, 255, 0)

        # Convert image from {0, 255} to {0, 1} for center calculation
        data = data / 255

        # Helper arrays to calculate the center
        x_range = np.arange(0, data.shape[1])
        y_range = np.arange(0, data.shape[0])

        # Calculate area and center
        area = data.sum()
        x_cntr = np.matmul(data, x_range).sum() / area
        y_cntr = np.matmul(data.T, y_range).sum() / area

        # --- PCA (Principal Component Analysis) --- 
        # Extract foreground pixel coordinates
        y, x = np.nonzero(data)
        coords = np.column_stack((x, y))
        # Centered array
        cntr_coords = coords - (x_cntr, y_cntr)
        # Covariance matrix
        cov_matrix = np.cov(cntr_coords, rowvar=False)
        # Eigen value decomposition (EVD) to find the principal components
        eig_vals, eig_vecs = np.linalg.eigh(cov_matrix)
        # Eigenvector corresponding to the largest eigenvalue
        pr_eig_vec = eig_vecs[:, np.argmax(eig_vals)]
        # Orientation angle in radians
        orientation = np.arctan2(pr_eig_vec[1], pr_eig_vec[0])

        # Start and end point for the orientation line
        half_len = 300
        x_line = [x_cntr - half_len * np.cos(orientation), x_cntr + half_len * np.cos(orientation)]
        y_line = [y_cntr - half_len * np.sin(orientation), y_cntr + half_len  * np.sin(orientation)]

        # Display the image
        plt.figure(1); plt.clf() # This is needed to refresh the image
        plt.imshow(image, cmap="gray")
        plt.plot(x_line, y_line, color="red", linewidth=3)  # Draw a red line on the image
        plt.plot(x_cntr, y_cntr, "og", markersize=5)  # Mark center with green circle
        plt.title(f"Orientation of {filename}")
        plt.axis('off')  # Hide the axis
        plt.pause(.033)  # This is needed to refresh the image