Operațiunile pe biți se află în partea întunecată a informaticii și a programării. Acestea sunt creaturi arcane, puțin înfricoșătoare, dar uneori monstruos de utile. Acest articol se va concentra pe explicarea modului în care sunt aplicate în domeniul viziunii computerizate și cum le puteți utiliza cu OpenCV și Python.

Sper că o veți găsi de folos!

Vom începe prin a aplica aceste operații imaginilor binare înainte de a introduce măști pentru a vedea cum le putem folosi cu imaginile BGR.

Să mergem!

Cea mai simplă modalitate de a vizualiza și înțelege ce fac operațiunile pe biți și cum funcționează ele este să te uiți la transformările pe care le pot efectua pe imagini binare.
Există patru operații pe biți în OpenCV: și, sau, exclusiv sau — deseori notat xor — și nu. Și, sau și xor toate necesită doi biți și scot un bit, conform tabelului lor de adevăr. Nu preia un singur bit și returnează „opusul” acestuia: 0 devine 1 și invers.
Imaginile binare fiind matrici formate doar din 0 (de obicei pixeli negri) și 255 (de obicei pixeli albi), modul în care operațiunile binare funcționează cu ei este foarte simplu: tot ceea ce fac este să aplice operații pe biți fiecărui pixel.

Voi ilustra cele patru operații pe biți cu cele două imagini de mai jos:

Acestea au fost create folosind următorul cod:

import numpy as np
import cv2 as cv
# we create a black image of 400*400 pixels and draw a 100*100 white square in the middle.
black_one = np.zeros((400, 400), dtype=np.uint8)
black_one[150:250, 150:250] = 255
# same process, except that the white square has been gently pushed towards the bottom-right corner.
black_two = np.zeros((400, 400), dtype=np.uint8)
black_two[200:300, 200:300] = 255

Să începem cu operatorul și. Îl putem aplica celor două imagini ale noastre trecându-le la funcția cv.bitwise_anddupă cum urmează:

one_and_two = cv.bitwise_and(black_one, black_two)

Rezultatul operației și pe biți este intersecția ambelor pătrate albe. Pixelii care sunt negri în cel puțin una dintre cele două imagini rămân negri.

De asemenea, putem folosi operatorul sau pentru a găsi uniunea dintre cele două pătrate albe: când un pixel este alb în cel puțin una dintre cele două imagini — când cel puțin unul dintre cei doi operanzi este „adevărat”. ” — , rămâne alb în imaginea rezultată:

În mod similar, am folosit funcția bitwise_or pentru a efectua această operație.

Sau este inclusiv, ceea ce înseamnă că pixelii care sunt albi în ambele imagini rămân albi. Dacă doriți să selectați pixeli care sunt albi pe oricare dintre imagini, dar nu pe ambele, atunci veți dori să utilizați operatorul xor:

Funcția care face această transformare este cv.bitwise_xor și funcționează exact în același mod ca cele două anterioare.

În cele din urmă, funcția cv.bitwise_not preia o singură imagine și schimbă pixelii albi și negri. Iată cum arată black_one după ce a fost nued.

Acum că știm cum funcționează operatorii pe biți pe imagini binare, este ușor să-l transferăm în imaginile BGR. În loc să opereze pe fiecare pixel în ansamblu, operațiunile pe biți aplică modificări canalelor. Să luăm un exemplu folosind o imagine „verde pur”, pe care o generăm folosind următorul cod:

green = np.zeros((400, 400, 3), dtype=np.uint8)
green[:, :, 1] = 255

Fiecare pixel este format din trei canale, care sunt numere întregi între 0 și 255 care reprezintă cantitatea lor de albastru, verde și, respectiv, roșu. Prin urmare, aplicarea operațiunilor pe bit la imaginile BGR dă aceleași rezultate ca înainte, dar pe a treia dimensiune.

Să încercăm un exemplu cu operatorul not:

Nu dezactivează canalul verde și activează canalele roșu și albastru.

Pentru a generaliza comportamentul operatorului not, putem spune că pixelii sunt înlocuiți cu simetricul lor față de 127. De exemplu, 0 devine 255 și 200 devine 55.

Funcțiile OpenCV bitwise_xx pot fi apelate cu un parametru suplimentar pe care nu l-am folosit încă: mask. Măștile vă permit să selectați anumite regiuni ale imaginii dvs. Un exemplu valorează cât o mie de cuvinte, așa că iată unul care aplică operatorul și unei imagini, folosind black_one ca mască:

Iată codul care a produs acest rezultat:

image: np.ndarray = cv.imread(image_path)
mask = np.zeros(image.shape[:2], dtype=np.uint8)
mask[150:250, 150:250] = 255
masked_image = cv.bitwise_and(image, image, mask=mask)

Există două lucruri de știut când folosiți măști:

  • trebuie să fie bidimensionale;
  • i se aplică un prag binar, ceea ce înseamnă că toți pixelii al căror coeficient de gri este între 0 și 127 sunt rotunjiți la 0, iar cei mai întunecați sunt rotunjiți la 255.

Acum că știm cum să folosim măști pentru a extrage regiuni dintr-o imagine BGR, este timpul să punem toate acestea împreună pentru a lucra la un exemplu final, mai concret și puțin mai complex. Scopul nostru este să facem următoarea imagine:

din aceste două imagini:

Rețineți că pictograma vreme este pe un fundal alb solid, ceea ce înseamnă că mai întâi trebuie să extragem soarele și norul.

Vă las să rezolvați singur această problemă înainte de a citi soluția de mai jos...

Soluție: primul lucru pe care trebuie să-l facem este să calculăm forma ambelor imagini. Deoarece fundalul și prim-planul au o dimensiune diferită, trebuie să selectăm o regiune de interes(ROI)din prima imagine. ROI este partea din imagine la care vom lucra. Vom folosi indexarea încorporată NumPy pentru a selecta un dreptunghi de dimensiunea pictogramei noastre din colțul din stânga sus al imaginii. Acest pas este esențial pentru a efectua operațiuni pe biți atât pe imaginea originală, cât și pe pictogramă, deoarece necesită ca toate imaginile și masca să aibă aceleași dimensiuni. Iată codul pe care îl putem folosi:

weather = cv.imread(weather_path)
weather_height, weather_width = weather.shape[:2]
region_of_interest = image[:weather_height, :weather_width]

Apoi trebuie să despărțim icoana în două părți: cea colorată, care va fi în prim-plan; iar fundalul alb care va fi înlocuit cu poza lacului. Vom începe prin a aplica un prag binar imaginii noastre, astfel încât să putem distinge clar pictograma reală de fundal:

greyscale_sun = cv.cvtColor(weather, cv.COLOR_BGR2GRAY)
_, threshold = cv.threshold(greyscale_sun, 254, 255, cv.THRESH_BINARY)

Nu trebuie să stocăm primul element returnat de funcția prag. Deoarece fundalul este complet alb, am stabilit limita inferioară la 254. Aceasta înseamnă că toți pixelii care nu sunt complet albi vor fi înnegri. Iată imaginea rezultată:

Folosind această imagine ca mască, acum putem insera imaginea lacului, lăsând un spațiu gol în care va fi plasată pictograma:

background = cv.bitwise_and(region_of_interest, region_of_interest, mask=threshold)

Ceea ce ne oferă următoarea imagine:

Acum trebuie să selectăm partea pictogramei care se potrivește cu secțiunea neagră a imaginii de mai sus. Pentru a face acest lucru, luăm inversul măștii și o aplicăm întregii pictograme:

threshold_inverse = cv.bitwise_not(threshold)
icon = cv.bitwise_and(weather, weather, mask=threshold_inverse)

Iată cum arată pictograma:

Putem folosi funcția OpenCV addpentru a pune cele două imagini împreună, ceea ce generează această imagine:

Ultimul pas este înlocuirea colțului din stânga sus al imaginii originale cu ROI editat:

image[:weather_height, :weather_width] = cv.add(background, icon)

și Bob este unchiul tău, primești acest mic colaj minunat!