Skip navigation

Hisztogram meghatározása

Hisztogram számítása

Az OpenCV a calcHist() függvénnyel biztosítja a hisztogram meghatározást.

hist = cv2.calcHist([images], [channels], mask, [histSize], [ranges])

Paraméterként meg kell adnunk:

  • a képmátrixo(ka)t Python tömbként felsorolva ([images]);
  • a felhasználandó csatornák indexeit, szintén Python tömbként ([channels]);
  • egy maszkot (mask), ha a kép csak egy részére végeznénk el a számítást (None, ha a teljes kép érdekes);
  • a hisztogram rekeszeinek számát ([histSize]),
  • valamint a figyelembe veendő intenzitástartományt, jellemzően a teljeset ([ranges]). A záró értéknél a maximum + 1 értékel kell megadni (például [0, 255] esetén 256-ot)!

Amennyiben az intenzitástartomány bővebb, mint a megadott hisztogram rekeszek száma, akkor a függvény összevon intenzitásértékeket.

Az eredmény egy histSize méretű NumPy oszlopvektor lesz (hist).

Hisztogram ábrázolása grafikonon

A hisztogram vektort grafikonon ábrázolhatjuk például a matplotlib csomag pyplot alcsomagjával, aminek a definícióit plt néven illesztjük be a forráskódba.

A figure() függvényével kezdhetünk új ábrát rajzolni, megadva a méreteit.

Az ábrázolandó adatot többféle módon jeleníthetjük meg.

  • A vlines() függvény függőleges vonallal ábrázolja az értékeket. Egycsatornás hisztogram ábrázolására általános esetben a legjobban használható.
  • A plot() függvénnyel a függvényértékek egyenes vonallal kerülnek összekötésre. Többcsatornás hisztogram ábrázolásra megfelelő, csatornánként különböző színnel, amennyiben nincs nagy "ugrás" a szomszédos intenzitásértékek előfordulási gyakoriságában.
  • A scatter() segítségével pontfelhőként rajzolhatjuk ki. Többcsatornás hisztogram ábrázolásoknál hasznos, ahol például több intenzitásérték is 0 gyakorisággal fordul elő, így függvényként plot()-tal nem ábrázolható szépen.
  • A bar() oszlopdiagramot rajzol. Használata kisebb darabszámú hisztogramok esetén célszerű. Túl sűrű megjelenítésnél az oszlopok átfedik egymást.

Az xlim() és ylim() az X- és Y-tengelyek menti ábrázolandó minimum és maximum értékeket adja meg.

A show() jeleníti meg az előkészített ábrát. A program futása felfüggesztődik, míg az ábrát be nem zárjuk.

05_01_a_calcHist.py

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('GolyoAlszik_rs.jpg', cv2.IMREAD_GRAYSCALE)

cv2.imshow('img', img)

print('Min: {}'.format(np.min(img)))
print('Max: {}'.format(np.max(img)))

# 256 elemű hisztogram
hist_gray = cv2.calcHist([img], [0], None, [256], [0, 256])
print(hist_gray.shape)

plt.figure(figsize=(4,2), dpi=100)
# plt.plot(hist_gray)
# plt.scatter(np.arange(256), hist_gray, s=1)
# plt.bar(np.arange(256), np.transpose(hist_gray.astype(int))[0])
plt.vlines(np.arange(256), 0, hist_gray)

# plt.xlim([0, 255])
plt.ylim([0, np.max(hist_gray)])
fig.tight_layout(pad=0)
plt.show()

# 16 elemű hisztogram
hist_gray2 = cv2.calcHist([img], [0], None, [16], [0, 256])
print(hist_gray2.shape)


plt.figure(figsize=(4,2), dpi=100)
# plt.plot(hist_gray2)
# plt.scatter(np.arange(16), hist_gray2, s=5)
# plt.bar(np.arange(16), np.transpose(hist_gray2.astype(int))[0])
plt.vlines(np.arange(16), 0, hist_gray2)
# plt.xlim([0, 15])
plt.ylim([0, np.max(hist_gray2)])
plt.show()

Megjegyzés

Az előző anyagrészben láthattuk, hogy a küszöbölés esetén az OpenCV például az Otsu-algoritmussal meg tud határozni egy javasolt küszöbértéket. Részletekbe most nem megyünk, de ez a módszer az úgynevezett bimodális (két csúccsal rendelkező) hisztogramú képek esetén ad a két csúcs közötti optimális vágó küszöbértéket.

Otsu-módszer leírása (angolul):

  • Otsu's method (Wikipedia). A működést animációval is szemlélteti.

Feladat

Egészítsük ki az előző példaprogramot úgy, hogy határozzuk meg a bemeneti szürke képre ezt az optimális küszöbértéket, és egy vörös függőleges vonallal ábrázoljuk a hisztogram grafikonon!

Segítség

  • Optimális küszöbérték számítás az Otsu algoritmussal, például (két eredményt kapunk, az első a számított küszöbérték, a második a küszöbölt kép; ez utóbbira most nincs szükségünk):
    th_val, im_thresh = cv2.threshold(src, -1, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
  • Függőleges vonalat a grafikonon az alábbi függvénnyel rajzolhatunk (th_val az érték, ahol a rajzolás történjen, esetünkben a küszöb): 
    plt.axvline(th_val, color='r')

PyPlot diagram képként való elérése

Az előző példában a diagramot a PyPlot segítségével alapértelmezetten, új ablakban jelenítettük meg. Az a probléma, hogy a programunk futása mindaddig felfüggesztődik, ameddig az ablak látható, csak annak becsukásával folytatódik. Ez sokszor kényelmetlen lehet.

A PyPlot képes a diagram ábrát képként is elkészíteni, amit aztán saját OpenCV ablakunkban, a szokásos módon megjeleníthetünk. A szükséges függvény, amit a plt.show() helyett hívhatunk a programunkban az alábbi.

def get_diagram_as_image(fig):
fig.canvas.draw()
data = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,))
data_bgr = cv2.cvtColor(data, cv2.COLOR_RGB2BGR)

return data_bgr

A tényleges használatát a 05_01_a_calcHist_plot_as_image.py példaprogramban láthatjuk.

05_01_b_calcHist_bgr.py

Többcsatornás képek esetén vagy egycsatornásra alakítunk, vagy csatornánként készítünk hisztogramot. Ezeket külön, vagy akár egy diagramban is ábrázolhatjuk, ahogyan az alábbi példában láthatjuk.

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('GolyoAlszik_rs.jpg', cv2.IMREAD_COLOR)

cv2.imshow('img', img)


hist_b = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_g = cv2.calcHist([img], [1], None, [256], [0, 256])
hist_r = cv2.calcHist([img], [2], None, [256], [0, 256])

img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hist_gray = cv2.calcHist([img_gray], [0], None, [256], [0, 256])

plt.figure(figsize=(4,2), dpi=100)
plt.vlines(np.arange(256), 0, hist_r, color='r')
# plt.xlim([0, 255])
plt.ylim([0, np.max(hist_r)])
plt.show()

plt.figure(figsize=(4,2), dpi=100)
plt.vlines(np.arange(256), 0, hist_g, color='g')
# plt.xlim([0, 255])
plt.ylim([0, np.max(hist_g)])
plt.show()

plt.figure(figsize=(4,2), dpi=100)
plt.vlines(np.arange(256), 0, hist_b, color='b')
# plt.xlim([0, 255])
plt.ylim([0, np.max(hist_b)])
plt.show()

plt.figure(figsize=(4,2), dpi=100)
plt.plot(hist_b, color='b')
plt.plot(hist_g, color='g')
plt.plot(hist_r, color='r')
plt.plot(hist_gray, color='k')
# plt.xlim([0, 255])
plt.ylim([0, max(np.max(hist_b), np.max(hist_g), np.max(hist_r))])
plt.show()

# RGB hisztogram a 0 és 255 elemek kihagyásával
plt.figure(figsize=(4,2), dpi=100)
plt.plot(hist_b[1:255], color='b')
plt.plot(hist_g[1:255], color='g')
plt.plot(hist_r[1:255], color='r')
plt.plot(hist_gray, color='k')
# plt.xlim([0, 255])
plt.ylim([0, max(np.max(hist_b[1:255]), np.max(hist_g[1:255]), np.max(hist_r[1:255]))])
plt.show()

Hisztogram elemzési feladatok

Hajtsuk végre a hisztogramszámítást és grafikon megjelenítést az alábbi, szürkeárnyalatosként betöltött képekre:

  • screen01_h.png
  • Sudoku_j.jpg
  • FrenchCardShapes.png
  • histogram/Picture5.png

Kérdések:

  • Mi magyarázza a kiugró értéket a screen01_h.png kép esetén?
  • Hogyan javíthatnánk a Sudoku_h.jpg kép megjelenésén?
  • Miért csak 3 kiugró értéket látunk a FrenchCardShapes.jpg kép hisztogramján?
  • Mit állapíthatunk meg a Picture5.png kép hisztogramjából?