作為最容易上手之一的語言,python擁有著大量的第三方庫,這些第三方庫的存在使得很多人可以專注于業務邏輯、數學邏輯而忽略繁瑣的代碼操作,python的opencv第三方庫就是其中之一,
一、第三方庫的安裝和簡單使用
安裝
簡單的pip安裝就可以了,opencv庫的使用,很經常地涉及一些矩陣運算,所以numpy算是和它一家親了,
pip install opencv-python
安裝好了以后,就可以簡單地打開圖片、打開視頻了,來個簡單試驗:
讀取圖片
import cv2
# 讀取影像,第一種是正常讀取,第二種是讀取灰度影像
img = cv2.imread("D:\img\among.png")
gray = cv2.imread("D:\img\among.png", 0)
# 顯示影像
cv2.imshow("colorful", img)
cv2.imshow("gray", gray)
# 不再等待鍵盤輸入事件,直接顯示
cv2.waitKey(0)
# 關閉所有顯示視窗
cv2.destroyAllWindows()
顯示效果如下:

讀取視頻并播放
import cv2
# 讀取視頻
video = cv2.VideoCapture('badapple_color.mp4')
# 獲取視頻物件的幀數
fps = video.get(cv2.CAP_PROP_FPS)
# 設定回圈條件
while(video.isOpened()):
_, frame = video.read()
cv2.imshow("video", frame)
# 設定退出條件是輸入'q'
if cv2.waitKey(int(fps)) in [ord('q'), 27]:
break
cv2.destroyAllWindows()
video.release()
注意:這里的播放是沒有正常播放器那樣的進度條和音頻的,因為這里是讀取的視頻的每一幀,然后回圈播放而已,并沒有讀取音頻,而且設定了退出條件是輸入q,

獲取攝像頭并保存為視頻檔案
import cv2
video = cv2.VideoCapture(0)
while(True):
# 獲取一幀
_, frame = video.read()
# 將這幀轉換為灰度圖
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.imshow('frame', gray)
if cv2.waitKey(1) == ord('q'):
break
video.release()
然后它就輸出持續輸出攝像頭排到的每一幀畫面,因為讀取的是灰度的,這里也是灰度的,你可以由著它正常輸出,不修改,也就是注釋掉cvtColor那條轉換陳述句,順便把imshow輸出物件換回開始的frame(讀取到的幀),就可以得到彩圖了,
|
改一下,保存為視頻:
import cv2
video = cv2.VideoCapture(0)
# 定義編碼方式并創建VideoWriter物件
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
outfile = cv2.VideoWriter('res.mp4', fourcc, 25., (640, 480))
while(video.isOpened()):
flag, frame = video.read()
if flag:
outfile.write(frame) # 寫入檔案
cv2.imshow('frame', frame)
if cv2.waitKey(1) == ord('q'):
break
else:
break
video.release()

二、影像基礎
計算機中的影像,由一個個帶有顏色的小方塊組成,這些小方塊就是基本的處理單位,稱為像素,它的大小取決于計算機的解析度,解析度越高,像素點越小,簡單的二值圖片,其像素點的值只有0和1,用來標識黑白兩種顏色;在此更進一步的灰度圖片,則是把黑白色更細膩化,使得影像生動許多,也就是表示黑白的值由0-255存盤,0為純黑,1為純白,中間的黑白色的漸變就更加細膩了;再進一步,就是彩色影像,顏色的構成基本都是由三原色以不同的比例調和出來的,而彩色影像就是有三種值共同表示三原色的值,也就是說一個彩色影像的像素點,它的顏色由三種值共同構成,這種模式也被稱為RGB色彩空間,比如[0, 0, 0]表示純黑,[255, 255, 255]表示純白,而[255, 0, 0]、[0, 255, 0]和[0, 0, 255]分別表示紅綠藍三原色,因此RGB色彩空間也被稱為三通道,在opencv中,三通道順序為BGR逆序,一幅彩色影像由三通道構成的矩陣而成,這也是處理彩色圖的時候,常常引入Numpy來進行配合的原因,處理影像,也是考究數學的,
簡單的例子:
import cv2
import numpy as np
# 黑白圖
b = np.zeros((100, 100), dtype=np.uint8)
w = np.zeors((100, 100), dtype=np.uint8)
w[:100, :100] = 255
print(b, w, sep="\n\n")
cv2.imshow("black", b)
cv2.imgshow("white", w)
cv2.waitKey()
# 三個三原色圖片
r = np.zeros((300, 300, 3),dtype=np.uint8)
g = np.zeros((300, 300, 3),dtype=np.uint8)
b = np.zeros((300, 300, 3),dtype=np.uint8)
r[:,:, 2] = 255
g[:,:, 1] = 255
b[:,:, 0] = 255
cv2.imshow("red", r)
cv2.imshow("green", g)
cv2.imshow("blue", b)
cv2.waitKey()
# 包含三原色的圖片
img = np.zeros((300, 300, 3), dtype=np.uint8)
img[:, 0:100, 2] = 255
img[:, 100:200, 1] = 255
img[:, 200:300, 0] = 255
cv2.imshow("RGB", img)
# 紅橙黃綠藍靛紫
img = np.zeros((300, 700, 3), dtype=np.uint8)
# 紅
img[:,0:100,2] = 255
# 橙
img[:,100:200,2] = 255
img[:,100:200,1] = 97
# 黃
img[:,200:300,1] = 255
img[:,200:300,2] = 255
# 綠
img[:,300:400,1] = 255
# 藍
img[:,400:500,0] = 255
# 靛
img[:,500:600,0] = 84
img[:,500:600,1] = 46
img[:,500:600,2] = 8
# 紫
img[:,600:700,0] = 240
img[:,600:700,1] = 32
img[:,600:700,2] = 160
# 輸出
cv2.imshow("seven", img)
cv2.waitKey()
輸出結果就不顯示了,就那樣,其實想要搞個顏色對照表也可以一條通道一條通道的疊代,然后疊代完整個序列就輸出,那樣就有個顏色表了,不過要想準備給值還是要參考一下真正的命名,
隨機圖
整一個隨機灰度影像,就是以前那種電視沒信號的狀態,
import cv2
import numpy as np
img = np.random.randint(0, 256, size=[300, 300], dtype=np.uint8)
cv2.imshow("老花", img)
cv2.waitKey()
img = np.random.randint(0, 256, size=[300, 300], dtype=np.uint8)
cv2.imshow("彩色老花", img)
cv2.waitKey()
|
上面已經描述了,RGB彩圖是有著三通道的,而opencv提供了通道拆分的實作
import cv2
import numpy as np
img = cv2.imread(r"D:\img\among.png")
# b, g, r = cv2.split(img) 等同
cv2.imshow("0", img[:,:,0])
cv2.imshow("1", img[:,:,1])
cv2.imshow("2", img[:,:,2])
# 同樣的拆分功能
b, g, r = cv2.split(img)
# 合并成原圖
img_mer = cv2.merge([b, g, r])
cv2.waitKey()
效果:

三個屬性值
- shape,img.shape,表示img序列的長寬和深度,
- size ,img.size,表示像素數,行x列x通道
- dtype,影像資料型別
三、色彩空間和轉換
色彩空間,表述顏色的模式,常見的色彩空間是RGB色彩空間,但在opencv中是反過來的BGR通道,除此以外的,還有GRAY即八位灰度圖、XYZ色彩空間、YCrCb色彩空間、HSV色彩空間、HLS色彩空間、Bayer色彩空間,,,,,,針對不同需要的色彩空間,在需要的時候還可以進行轉換,這里就是針對它們的特性和相互轉換進行學習,
GRAY色彩空間
8位灰度圖,對應8位二進制的數值范圍就是0-255,剛好表示256個灰度,0表示純黑,255表示純白,中間數值就是從純黑到純白的漸變,所以是灰度,在opencv中,RGB色彩空間轉變為GRAY這種灰度色彩空間,它的轉變公式如下:
$Gray = 0.299R+0.587G+0.114*B$
而GRAY灰度轉為RGB色彩空間就比較簡單,RGB三通道的值直接就是GRAY的值,也就是
$$R = Gray\G = Gray\B = Gray\$$
關于GRAY和BGR之間的互轉,其實在opencv讀取的時候就已經可以實作,在應用上暫時沒啥想法,這里主要是玩一下例子,
>>> import cv2
>>> import numpy as np
>>>
>>>
>>> mong = cv2.imread("among.png")
>>> gray = cv2.cvtColor(mong, cv2.COLOR_BGR2GRAY)
>>> cv2.imshow("source", mong)
>>> cv2.imshow("gray", gray)
>>> bgr_img = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR)
>>> cv2.imshow("change again", bgr_img)
>>> cv2.waitKey()
-1
>>>
>>>
>>> img = np.random.randint(0, 256, size=[2,3], dtype=np.uint8)
>>> res = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
>>> res_change = cv2.cvtColor(res, cv2.COLOR_BGR2GRAY)
>>> img
array([[ 48, 27, 228],
[ 94, 144, 234]], dtype=uint8)
>>> res
array([[[ 48, 48, 48],
[ 27, 27, 27],
[228, 228, 228]],
[[ 94, 94, 94],
[144, 144, 144],
[234, 234, 234]]], dtype=uint8)
>>> res_change
array([[ 48, 27, 228],
[ 94, 144, 234]], dtype=uint8)
>>>
玩脫了,沒有轉換回來原本的顏色,

YCrCb色彩空間
針對人眼視覺系統,由于人對顏色的感知要低于對光線亮度的感知,而RGB色彩空間專注于顏色,就少了亮度這一指標,所以有了YCrCb色彩空間,在這種色彩空間里,Y表示光源亮度,Cr表示紅色分量,Cb表示藍色分量,關于RGB轉換成YCrCb的轉換公式為:
$$Y = 0.299R + 0.587G + 0.114*B \ Cr = (R-Y)\times0.713+delta\Cb=(B-Y)\times0.564+delta$$
關于delta的值,對應不同數位影像有不同的值:
| delta值 | 影像數位 |
|---|---|
| 128 | 8位 |
| 32768 | 16位 |
| 0.5 | 單精度影像 |
| 反過來從YCrCb轉換成RGB的公式則是: |
$$R=Y+1.403\cdot(Cr-delta)\G=Y-0.714\cdot(cr-delta)-0.344\cdot(Cb-delta)\B=Y+1.773\cdot(Cb-delta)$$
它也叫YUV,Y表示亮度,U和V表示色度,在做膚色檢測上比HSV要好,
HSV色彩空間
聽說是針對視覺感知的顏色模型,在這個色彩空間里面,有著色調、飽和度、亮度三要素,色調是光的顏色,飽和度是色彩的深淺,亮度是人眼感受到的光的明暗,
- 色調H,紅黃綠青藍紅六種顏色對應圓周360度(所以才說這些創造概念的人就喜歡讓人費解);
- 飽和度S,一個比例,也就是一個小數,范圍時從0到1,表示顏色所占其顏色的最大純度的比例,飽和度為0就是灰色,最大也就是1是這個顏色本身;
- 亮度V,色彩明亮程度,同樣是[0, 1]的取值范圍,
RGB轉換為HSV的公式如下:
$$
V = max(R, G, B)\亮度:
S=
\begin{cases}
V-min(R, G, B), &V\ne0\
0,&其他
\end{cases}\色調:
H=
\begin{cases}
\cfrac{60(G-B)}{v-min(R, G, B)},&V=R\
120+\cfrac{60(B-G)}{V-min(R, G, B)}, &V=G\
240+\cfrac{60(R-G)}{V-min(R, G, B)}, &V=B
\end{cases}\色調:
H=
\begin{cases}
H+360,&H<0\
H,&其他
\end{cases}
$$
上面就是RGB轉換成HSV的公式,真可謂是賊麻煩了,知道原理也是為了方便除錯,實際上都可以由opencv的內部函式給搞定了,只需要關心上面重要的三要素意義即可,
>>> rgb_b = np.ones((2, 3, 3), dtype=np.uint8)
>>> rgb_g = np.ones((2, 3, 3), dtype=np.uint8)
>>> rgb_r = np.ones((2, 3, 3), dtype=np.uint8)
>>> rgb_b[:, :, 0], rgb_g[:, :, 1], rgb_r[:, :, 2] = 255, 255, 255
>>> hsv_b = cv2.cvtColor(rgb_b, cv2.COLOR_BGR2HSV)
>>> hsv_g = cv2.cvtColor(rgb_g, cv2.COLOR_BGR2HSV)
>>> hsv_r = cv2.cvtColor(rgb_r, cv2.COLOR_BGR2HSV)
>>> rgb_b
array([[[255, 1, 1],
[255, 1, 1],
[255, 1, 1]],
[[255, 1, 1],
[255, 1, 1],
[255, 1, 1]]], dtype=uint8)
>>> hsv_b
array([[[120, 254, 255],
[120, 254, 255],
[120, 254, 255]],
[[120, 254, 255],
[120, 254, 255],
[120, 254, 255]]], dtype=uint8)
>>> rgb_g
array([[[ 1, 255, 1],
[ 1, 255, 1],
[ 1, 255, 1]],
[[ 1, 255, 1],
[ 1, 255, 1],
[ 1, 255, 1]]], dtype=uint8)
>>> hsv_g
array([[[ 60, 254, 255],
[ 60, 254, 255],
[ 60, 254, 255]],
[[ 60, 254, 255],
[ 60, 254, 255],
[ 60, 254, 255]]], dtype=uint8)
>>> rgb_r
array([[[ 1, 1, 255],
[ 1, 1, 255],
[ 1, 1, 255]],
[[ 1, 1, 255],
[ 1, 1, 255],
[ 1, 1, 255]]], dtype=uint8)
>>> hsv_r
array([[[ 0, 254, 255],
[ 0, 254, 255],
[ 0, 254, 255]],
[[ 0, 254, 255],
[ 0, 254, 255],
[ 0, 254, 255]]], dtype=uint8)
>>>
HLS色彩空間
和HSV色彩空間類似,不過HLS色彩空間三要素為:色調H、亮度L、飽和度S,文中是這么描述的,所以不同在哪?描述亮度的單詞嗎?無語,
型別轉換函式
上面的各種色彩空間與RGB色彩空間的轉換,在opencv提供的型別轉換函式中都可以實作,
dst = cv2.cvtColor(src, code[, dstCn])
針對不同的型別轉換,傳入不同的code引數值,而dstCn是目標影像的通道數,默認為0,然后通道數會自動通過原始影像和code確定,
| code值 | 決議 |
|---|---|
| cv2.COLOR_BGR2RGB | opencv中BGR型別轉為RGB型別 |
| cv2.COLOR_RGB2BGR | opencv中RGB型別轉為BGR型別 |
| cv2.COLOR_BGR2GRAY | BGR轉為GRAY |
| cv2.COLOR_GRAY2BGR | GRAY轉為BGR |
| cv2.COLOR_BGR2XYZ | BGR轉為XYZ |
| cv2.COLOR_XYZ2BGR | XYZ轉為BGR |
| cv2.COLOR_BGR2YCrCb | BGR轉為YCrCb |
| cv2.COLOR_YCrCb2BGR | YCrCb轉為BGR |
| cv2.COLOR_BGR2HSV | BGR轉為HSV |
| cv2.COLOR_HSV2BGR | HSV轉為BGR |
| cv2.COLOR_BGR2HLS | BGR轉為HLS |
| cv2.COLOR_BayerBG2BGR | 逆馬賽克,也是Bayer的BG模式 |
在上面的引數中,出現了RGB和BGR的互轉,在opencv中,通道的順序一般是BGR,是逆序的,那這個RGB的轉換會咋樣?實踐操作的結果來看,會使得B、R兩通道的值互相替換,就是兩條通道換了過來,但opencv渲染圖片還是那個通道順序,所以如果顯示圖片,顏色就會發生變化,
>>> import cv2
>>> import numpy as np
>>>
>>>
>>> img = np.random.randint(0, 256, size=(2, 3, 3), dtype=np.uint8)
>>> rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
>>> img
array([[[ 69, 184, 11],
[193, 4, 194],
[239, 139, 146]],
[[188, 30, 44],
[ 60, 145, 133],
[ 46, 181, 139]]], dtype=uint8)
>>> rgb_img
array([[[ 11, 184, 69],
[194, 4, 193],
[146, 139, 239]],
[[ 44, 30, 188],
[133, 145, 60],
[139, 181, 46]]], dtype=uint8)
>>> mong = cv2.imread("among.png")
>>> rgb_mong = cv2.cvtColor(mong, cv2.COLOR_BGR2RGB)
>>> cv2.imshow("source", mong)
>>> cv2.imshow("rgb_res", rgb_mong)
>>> cv2.waitKey()
-1
從上面的矩陣和下面圖片的變化就能很清楚地看出內部變化和宏觀變化,就算是替換了兩通道,opencv的默認圖片的通道順序應該是沒有變化,或者說imshow這一函式的內部規則依然是從bgr的通道順序,從而使得圖片顏色不一,

抽取特定顏色
針對圖片中的特定顏色塊,在我們需要的時候,可以疊代圖片并產出一個只包含該顏色塊的圖片,比如上面的哆啦A夢的顏色磁區就很明顯,很適合用來進行這個嘗試,另外,這個想法的實作是依賴于opencv的inRange函式,
dst = cv2.inRange(src, lowerb, upperb)
上面的函式就是針對圖片中[lowerb, upperb]這一區域的顏色進行抽取,不過要注意的是,如果是灰度圖片,lowerb就只是一個整型值就行,但如果是一個RGB顏色空間的圖片,lowerb就需要一個矩陣來描述顏色了,upperb同上,嗯,不過三通道共同表述顏色這種針對硬體的特性,實在是讓我頭疼,在HSV中,表述顏色的只有一種,這種就很對用戶胃口,好,用這個試試,
>>> import cv2
>>> import numpy as np
>>>
>>> mong = cv2.imread("among.png")
>>> mong_hsv = cv2.cvtColor(mong, cv2.COLOR_BGR2HSV)
>>> bmin, bmax = np.array((100, 43, 46)), np.array((125, 255, 255))
>>> mask = cv2.inRange(mong_hsv, bmin, bmax)
>>> blue = cv2.bitwise_and(mong, mong, mask=mask)
>>> ymin, ymax = np.array((26, 43, 46)), np.array((34, 255, 255))
>>> ymask = cv2.inRange(mong_hsv, ymin, ymax)
>>> rmin, rmax = np.array((0, 43, 46)), np.array((10, 255, 255))
>>> rmask = cv2.inRange(mong_hsv, rmin, rmax)
>>> yellow = cv2.bitwise_and(mong, mong, mask=ymask)
>>> red = cv2.bitwise_and(mong, mong, mask=rmask)
>>> cv2.imshow("source", mong)
>>> cv2.imshow("blue", blue)
>>> cv2.imshow("yellow", yellow)
>>> cv2.imshow("red", red)
>>> cv2.waitKey()
-1

上面是根據搜到的這個HSV的對照表進行的實驗,應該圖片中的顏色不是完全按照正規的顏色對照而來,所以得到的也是顏色塊的碎碎塊塊,實在讓人心累,

不過針對水印的提取倒是讓我在這部分有意外的識訓,
>>> import cv2
>>> import numpy as np
>>>
>>>
>>> watermark = img[850:, 580:]
>>> wm_abstract = cv2.inRange(watermark, (230, 230, 230), (255, 255, 255))
>>> cv2.imshow("source", img)
>>> cv2.imshow("watermark_part", watermark)
>>> cv2.imshow("watermark", wm_abstract)
>>> cv2.waitKey()
-1
>>>
這樣就算提取出來了水印的那部分,對于這種純色的水印,RGB的inRange反而更好用的感覺,

膚色標記和檢測
和上面的確定顏色范圍一致,對應人類照片進行膚色的顏色范圍進行標記,然后就可以得到對應區域,然后抽取,書本中用的是HSV進行,不過有資料說YCrCb應用這種場景更好,我看看能不能兩個都試試,
書本中把膚色的色調和飽和度定在[5, 170]和[25, 166]之間,我不確定是否是針對書中例子圖片,但就先用著,雖然我用其他圖片做實驗.
>>> import cv2
>>> import numpy as np
>>>
>>>
>>> img = cv2.imread("eason.png")
>>> hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
>>> h, s, v = cv2.split(hsv)
>>> hmask = cv2.inRange(h, 5, 170)
>>> smask = cv2.inRange(s, 25, 166)
>>> mask = hmask & smask
>>> roi = cv2.bitwise_and(img, img, mask=mask)
>>> cv2.imshow("source", img)
>>> cv2.imshow("skin", roi)
>>> cv2.waitKey()
-1

就結果來看,膚色的檢測還是可以的,接下來試試YCrCb的檢測,上面的效果來看的話,其實邊緣部分沒處理好,所以下面使用opencv的四種模糊技術之一的高通濾波,
# 引入部分和上面的一致
>>> ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCrCb)
>>> y, cr, cb = cv2.split(ycrcb)
>>> cr = cv2.GaussianBlur(cr, (5, 5), 0)
>>> _, skin = cv2.threshold(cr, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
>>> cv2.imshow("res", skin)
>>> cv2.imshow("source", img)
>>> cv2.waitKey()
-1

效果還是蠻不錯的,但七竅通了六竅的我只能照抄例子來進行,沒辦法得到有顏色的圖片,
alpha通道
在RGB色彩空間基礎上,添加A通道,表示透明或者不透明,這就是RGBA色彩空間,A通道的取值范圍可以是[0, 1]也可以是[0, 255],在常見的影像處理中,一般都是RGB三通道,所以要使用RGBA色彩空間就需要cvtColor進行轉換,這里只是進行一下記錄,并不拓展,
四、影像運算
據說,啊據說啊,影像的加法運算、位運算是針對影像的位平面分解、影像異或加密、數字水印、臉部加碼/解碼等使用的,那如果想要逆向,那就暫時不清楚了,這一部分的處理暫時都以python的互動式編程作為演示,
加法運算
主要有兩種,一種是簡單的+運算子引領的加法,另一種是opencv提供的add加法函式,描述一下規則:
- 加號運算子,在影像a和影像b使用簡單的加號運算子進行加號運算時,它們的結果一旦超過灰度最大值255,就把這個值對256進行取模,加法的值就是這個余數,沒有超出就正常運算,
$$
a+b
\begin{cases}
a+b,&a+b\le255\
mod(a+b, 256),&a+b>255
\end{cases}
$$- cv2.add(a, b),影像a和影像b用這個函式進行加法運算,和上面的加號運算子主導的加法一樣,同樣有兩種結果,第一種就是當兩者之和大于255,那就讓它作為最大值保留,255就是飽和值,封頂了;如果沒有大于255,就正常相加,
$$
cv2.add(a, b)
\begin{cases}
a+b, &a+b\le255\
255, &a+b>255
\end{cases}
$$
來個簡單例子
import cv2
import numpy as np
# 灰度圖模式讀取
img = cv2.imread('among.png', 1)
# 加法處理
a = img + img
b = a + a
c = cv2.add(img, img)
c = cv2.add(c, c)
cv2.imshow("a", a)
cv2.imshow("b", b)
cv2.imshow("c", c)
cv2.imshow("d", d)
cv2.waitKey()
運算結果如下:

a和b都是進行加法運算子的影像加法,c和d則是用cv2的add函式進行加法運算,可以明顯看得出來,前者在越來越進一步的加法運算中,線條感更明顯,有顏色的越來越黑,而后者則是反過來,越來越飽和,顯得越來越白,這是一個對于影像的簡單處理,但如果是兩個影像之間乃至數值和影像之間的處理就不甚明了,因為沒有參考,
加權和
加權和,是在計算兩影像像素值之和時,把影像權重的要素也考慮進去,兩個進行加權和的影像,需要大小、型別相同,但通道和具體是什么型別沒有要求,不過找到的博客資料基本都是照本宣科,沒有適合人類參考的說法,mdzz,暫時這里也照抄書本概念吧,上公式:
$dst = saturate(src1\times\alpha + src2\times\beta+\gamma)$
這個加權和的實作是用的opencv中的函式addWeighted,對應上面公式,它也要傳入五個引數:src1、alpha、src2、beta、gama,所謂權重在我看來就是兩個影像所占比例,在最后的影像的結果來看誰更明顯,所以上面可以理解成:結果圖=影像1x系數1+影像2x系數2+亮度調節量,
簡單的圖片混合例子:
import cv2
import numpy as np
a = cv2.imread('blena.png')
b = cv2.imread('bboat.png')
c = cv2.addWeighted(a, 0.2, b, 0,8, 0)
d = cv2.addWeighted(a, 0.5, b, 0,5, 0)
e = cv2.addWeighted(a, 0.8, b, 0,2, 0)
cv2.imshow("c", c)
cv2.imshow("d", d)
cv2.imshow("e", e)
cv2.waitKey()
因為實在沒資源,我只好百度書本上的兩張圖來截圖保存來做實驗,然后按照人臉像所占比例越來越大來進行顯示,結果如下:

結果很明顯,隨著人臉像的比例越來越大,圖片人臉也越來越明顯,這就是混合影像的例子,聽說如果大小不一樣的影像,可以使用resize來進行調整,再試一下,換了一下圖片,然后代碼調整為:
import cv2
import numpy as np
a = cv2.imread("ayanamirei.jpg")
b = cv2.imread("asikaj.jpg")
# 比例調整大小,本來size并不一樣,但調整后以外發現一致了,應該是演算法問題
a = cv2.resize(a, None, fx=0.1, fy=0.1, interpolation=cv2.INTER_LINEAR)
b = cv2.resize(b, None, fx=0.1, fy=0.1, interpolation=cv2.INTER_LINEAR)
c = cv2.addWeighted(a, 0.2, b, 0.8, 0)
d = cv2.addWeighted(a, 0.5, b, 0.5, 0)
e = cv2.addWeighted(a, 0.8, b, 0.2, 0)
cv2.imshow("c", c)
cv2.imshow("d", d)
cv2.imshow("e", e)
cv2.waitKey()
調整效果以外得好,然后又能夠進行混合影像處理了,得到的彩圖如下:
相對來說,應該0.4和0.3的比例是比較好的,在背景不是那么明顯的情況下,清底應該比較好,resize最后的引數的選擇對出圖影響非常大:
- INTER_NEAREST:最近鄰插值
- INTER_LINEAR:線性插值(默認)
- INTER_AREA:區域插值
- INTER_CUBIC:三次樣條插值
- INTER_LANCZOS4:Lanczos 插值
縮小時推薦使用 cv2.INTER_AREA;擴展放大時推薦使用 cv2.INTER_CUBIC 和 cv2.INTER_LINEAR
按位邏輯運算
關于邏輯運算,存在與或非、異或等運算,這里的按位邏輯運算,就是把一個數轉換成二進制數,每一個對應位的數進行邏輯運算,具體邏輯運算沒有展開的必要,不做記錄,opencv提供的按位邏輯運算函式分別是:
- 按位與,dst = cv2.bitwise_and(src1, src2[, mask])
- 按位或,dst = cv2.bitwise_or(src1, src2[, mask])
- 按位非,dst = cv2.bitwise_not(src[, mask])
- 按位異或,dst = cv2.bitwise_xor(src1, src2[, mask])
mask,可選操作掩碼,8位單通道array值
按位與運算的使用
針對按位與運算,當處理灰度影像時,像素點與數值0按位與,得到的只有0,與255按位與得到的是本來的值,當一個具有大量0值和255值的影像與其按位與,得到的就是一個部分被"黑化"的影像,就像被摳掉了一樣,說白了就是用另一個影像來對目標影像進行部分乃至全部遮擋,
import cv2
import numpy as np
img = cv2.imread("blena.png")
# 制作一個和原圖同尺寸的陣列
mask = np.zeros(img.shape, dtype=np.uint8)
# 固定區域設定純白
mask[100:280, 100:250] = 255
a = cv2.bitwise_and(img, mask)
# 三圖進行顯示
cv2.imshow("source", img)
cv2.imshow("mask", mask)
cv2.imshow("res", a)
cv2.waitKey()

嗯,除了按位與運算的應用外,暫時另外兩三個好像沒啥子應用,不過要注意的是,兩個進行按位邏輯運算的矩陣,應當是相同size的,不然會出錯,所以有很多使用的時候會調整圖片的大小,
位平面的分解
彩圖根據RGB三通道可以拆分成三個矩陣,這是通道的拆解;把影像位于同一位元位上的像素進行組合,得到的影像,又被稱為位平面,這個程序就叫位平面的分解,在灰度圖中,一個像素點的值的范圍為0-255,是一個位元組8bit的范圍,每個位上的值抽取出來,得到一個位平面,加上原圖,一共會有9張圖,如果把彩圖進行位平面的分割,未免就太多了,所以這里的例子只是灰度圖,這樣好搞一點,
針對一個灰度圖片,展開成二進制數并切割處位平面的情況下,位平面所在數位的權重越高,位平面和原圖的相關性就越高,相對的,位平面所在數位的權重越低,對應的和原圖的相關性就越低,說白了,就是2的0次方對應數位切出去的位平面就越看不出原圖的痕跡,2的7次方對應數位切出去的位平面就越是和原圖相似,
針對RGB彩圖,它拆成三通道,三通道對應的顏色也是一個8位二進制數,將他們拆成三通道然后同步對應數位切出一個基于通道的位平面,再組合起來,也就是原圖的位平面,嗯,好像彩圖的位平面分割也不是太難,
位平面分割步驟:
- 抽取原圖寬和高,構造一個同樣規模的矩陣;
- 把上面矩陣構造成一個像素點的值均為$2^n$的矩陣做提取用;
- 將提取矩陣和原圖做按位與運算得到位平面
- 為了讓對應數位較小的位平面不至于顯示成純黑,需要對其進行閾值化處理,使得最后得到的只有0后者255這樣非黑即白的值,或者說是非真既假
import cv2
import numpy as np
img = cv2.imread("alian.jpg", 0)
img = cv2.resize(img, None, fx=0.4, fy=0.4, interpolation=cv2.INTER_LINEAR)
cv2.imshow("source", img)
w, h = img.shape
# 創建8層同規模的矩陣,每個矩陣用來放置對應數位的提取矩陣,在后面的回圈中給對應矩陣賦值
arrays = np.zeros((w, h, 8), dtype=np.uint8)
for i in range(8):
x[:, :, i] = 2**i
# 回圈對原圖進行按位與運算提取位平面,然后進行閾值處理,最后輸出影像
for i in range(8):
temp = cv2.bitwise_and(img, x[:, :, i])
# 將temp中大于0的值轉為True,除此以外的值轉換為False
mask = temp>0
# 將temp中True換成255
temp[mask] = 255
cv2.imshow("res"+str(i+1), temp)
cv2.waitKey()



從上面就可以看得很清楚,關于一個灰度圖的位平面抽取和顯示(用的是阿連的頭像,可惜太白了,而且本體太大了,需要修改尺寸),這樣一來,想要處理彩圖,在上面的程序中添加彩圖拆分三通道和最后合成三通道即可,如下:
import cv2
import numpy as np
img = cv2.imread("alian.jpg")
img = cv2.resize(img, None, fx=0.4, fy=0.4, interpolation=cv2.INTER_LINEAR)
cv2.imshow("source", img)
w, h = img.shape[:2]
b, g, r = cv2.split(img)
b_arr = np.zeros((w, h, 8), dtype=np.uint8)
g_arr = np.zeros((w, h, 8), dtype=np.uint8)
r_arr = np.zeros((w, h, 8), dtype=np.uint8)
for i in range(8):
b_arr[:, :, i], g_arr[:, :, i], r_arr[:, :, i] = 2**i, 2**i, 2**i
for i in range(8):
t1 = cv2.bitwise_and(b, b_arr[:, :, i])
t2 = cv2.bitwise_and(g, g_arr[:, :, i])
t3 = cv2.bitwise_and(r, r_arr[:, :, i])
mask1 = t1 >0
mask2 = t2 >0
mask3 = t3 >0
t1[mask1], t2[mask2], t3[mask3] = 255, 255, 255
temp = cv2.merge([t1, t2, t3])
cv2.imshow("res"+str(i+1), temp)
cv2.waitKey()



水印
各大網站都很注視自己的著作權問題,恨不得打上各種印記,影像上的水印就是一個體現,有時候是你上傳上去的,到了網站上面,顯示出來,就有了人家的印記,就很惡心,不過針對私人而言,這也是要保護的知識產權的一部分,是個公說公有理婆說婆有理的事,
上面有介紹位平面是一幅影像的像素點基于同一二進制上數位的數值集合,數位越大,對應的位平面影像就越是貼合原圖,數位越小就越是和原圖相差大,因此在二進制數中最低位也就是2的0次方位,也叫最低有效位(LSB, Least Significant Bit),而當把資訊存在這個數位上,再合進原來的影像,這個資訊就成了隱藏資訊,水印就屬于這種隱藏資訊,那如何做到?
首先就是讀取需要加水印的圖片和一個水印清晰的圖片,把后者的最低有效位對應位平面提取,然后根據圖片尺寸進行適當或放大或縮小的調整,再粘貼到需要添加水印的圖片上,這一步的進行可以使用cv2的加權和函式也可以用PIL的粘貼函式,
自制水印圖
首先,因為我沒有水印的原圖,所以就想生成兩張帶有自己標簽的水印原圖,分別是黑底白字和白底黑字兩張,關于生成黑底圖和白底圖是很簡單的,像素值為0就是純黑,像素值為255就是純白,至于添加文字,就可以用cv2自帶的putText函式,來,開造,
import cv2
import numpy as np
# 制造黑色和白色背景
black_w = np.zeros((300, 450), dtype=np.uint8)
white_b = np.ones((300, 450), dtype=np.uint8)*255
# 呼叫putText函式添加手寫體的標簽,距離左上角150的位置,字體大小為3,粗細為3
cv2.putText(black_w, 'JackSAMA', (0, 150), cv2.FONT_HERSHEY_SCRIPT_SIMPLEX, 3, 255, 2)
cv2.putText(white_b, 'JackSAMA', (0, 150), cv2.FONT_HERSHEY_SCRIPT_SIMPLEX, 3, 0, 2)
cv2.imshow("black", black_w)
cv2.imshow("white", white_b)
cv2.waitKey()

水印圖片就制作好了,其實還可以根據實際情況來進行生成,只要定制好水印函式就好,還能根據圖片大小來進行調節,當然,有實圖也可以同步調整,關于putText的函式引數主要如下:
img = cv2.putText(img, text, org, fontFace, fontScale, color[, thickness[, lineType[, bottomLeftOrigin]]])
img, 操作影像物件
text,添加的文本,一般都是英文,中文使用會亂碼,暫時也還沒解決
fontFace,用過標簽語言的應該都知道這是字體型別的意思
fontScale,字體大小
color,對于灰度圖,簡單的0-255表示即可,如果是rgb彩圖就要適用(b, g, r)進行表示
thickness,線條粗細,默認是1
lineType,線條型別,默認是8連接型別
bottomLeftOrigin,默認為False,這樣文本就是橫著來;輸入為True就是文本豎著來
| fontFace | 決議 |
|---|---|
| cv2.FONT_HERSHEY_SIMPLEX | 正常的sans-serif字體,就是常用的英文字體 |
| cv2.FONT_HERSHEY_PLAIN | 小號sans-serif字體 |
| cv2.FONT_HERSHEY_DUPLEX | 正常大小的sans-serif |
| cv2.FONT_HERSHEY_COMPLEX | 正常的serif字體 |
| cv2.FONT_HERSHEY_TRIPLEX | 正常大小的serif字體 |
| cv2.FONT_HERSHEY_COMPLEX_SMALL | serif簡化版 |
| cv2.FONT_HERSHEY_SCRIPT_SIMPLEX | 手寫風格的字體 |
| cv2.FONT_HERSHEY_SCRIPT_COMPLEX | 手寫字體復雜版 |
| cv2.FONT_ITALIC | 斜體標記 |
| lineType | 決議 |
|---|---|
| cv2.FILLED | 填充型 |
| cv2.LINE_4 | 4連接型 |
| cv2.LINE_8 | 8連接型 |
| cv2.LINE_AA | 抗鋸齒,讓線條更平滑 |
上面就是opencv中字體引數和線條型別的一些解讀,聽說能換進去自我設計得庫,不知道具體怎樣,不過好像加了個抗鋸齒的線條引數看起來舒服多了,
嵌入水印
把水印給添加到哆啦a夢的圖片上去,
>>> import cv2
>>> import numpy as np
>>>
>>> mong = cv2.imread("among.png")
>>> mong.shape
(347, 272, 3)
>>> watermark = cv2.imread("white_black_sign.png")
>>> watermark.shape
(300, 450, 3)
# 調整水印原圖大小和對應待處理圖片補充空白
>>> watermark = cv2.resize(watermark, None, fx=0.3, fy=0.3, interpolation=cv2.INTER_AREA)
>>> cv2.imshow("", watermark)
>>> cv2.waitKey()
-1
>>> watermark.shape
(90, 135, 3)
>>> temp = np.ones(mong.shape, dtype=np.uint8)*255
>>> temp[210:300, 137:272] = watermark
>>> cv2.imshow("", temp)
>>> cv2.waitKey()
-1
# 進行加權和拼接,實作圖片添加水印
>>> res1 = cv2.addWeighted(mong, 0.9, temp, 0.1, 0)
>>> cv2.imshow("", res1)
>>> cv2.waitKey()
-1

五、一些有意思的使用
去水印
很多去水印的辦法,都是讓美工給搞一張純色的像我上面生成的那種水印圖,然后再來識別顏色范圍,再來給兩圖合并,也就是給paste上去,所以有不少使用是用到PIL庫的
import cv2
import PIL import Image
import numpy as np
img = cv2.imread("./iamfine.png")
h, w, _ = img.shape[0:3]
#切割,根據實際水印位置而定,[y0:y1, x0:x1]為下面裁剪用法,裁剪完后可以用上面的方法輸出查看一下
cropped = img[int(h*0.9):h, int(w*0.75):w]
# 對圖片進行閾值化處理,把由后面兩個引數劃定的RGB色彩空間范圍外的色彩輸出為0或者255,由圖片底色確定這個范圍
thresh = cv2.inRange(cropped, np.array([230, 230, 230]), np.array([250, 250, 250]))
#創建結構和尺寸的資料元素
kernel = np.ones((3, 3), np.uint8)
# 擴展待修復區域
watermask = cv2.dilate(thresh, kernel, iterations=10)
specular = cv2.inpaint(cropped, watermask, 5, flags=cv2.INPAINT_TELEA)
#保存去除水印的殘圖
cv2.imwrite("new.png", specular)
# 用PIL的paste函式把殘圖粘貼在原圖上得到新圖
i = Image.open("./img/iamfine.png")
i2 = Image.open("./img/new.png")
i2.paste(i, (int(w*0.75), int(h*0.9), w, h))
i2.save("final.png")
兩圖對比:
|
其實用上面給添加水印的方法反推就行,
生成字符圖片
圖片是由一個個像素點構成的,而計算機存盤圖片一樣是用的二進制存盤,而存盤像素點用到的位元位就是圖片的深度,用1bit來存盤,圖片要么是黑要么是白,因為它只有0和1的選擇;用一位元組(8bit)來存盤那就是0-255的值,顏色有三原色:紅綠藍,這三色可以交疊在一起表示其他顏色,而這個圖片的像素點的顏色的確定就是有RGB三色確定,一般稱為三通道,因為它們分別用三個位元組分別表示三原色的單獨的值,疊在一起就是該像素點的顏色,是以像素點的表示通常是(0-255,0-255,0-255),
而我們要生成的字符圖片,其實就是要建立一個像素點到字符的映射,因為字符集也是很大的,就連最基本的ASCII集也是128個字符,不過,我們也可以不需要用那么多,我們可以用一些簡單字符湊成一個字符集,然后與像素點的顏色表配對即可(因為映射規則是自定義的嘛),
道理是這樣,不過別人的例子我沒有悟透,倒是另外一個方法讓我有了識訓,那就是先將圖片轉換成灰度圖,那就是黑白色圖片了,這時候像素點轉換成字符會簡單很多,
簡單操作如下:
import cv2
import numpy as np
str = "#+-."
img = cv2.imread("among.png", 0)
# 此時就只有height和width兩個值,沒有depth
h, w = img.shape[0:2]
for_change = np.ndarray([h, w])
font = cv2.FONT_HERSHEY_SIMPLEX
for i in range(0, h, 5):
for j in range(0, w, 5):
t = str[round(3-img[i, j]/255*3)]
cv2.putText(for_change, t, (j, i), font, 0.1, color=(255, 255, 255))
cv2.imshow("", for_change)
cv2.waitKey(0)
cv2.imwrite("asciiPic.png", for_change)

嗯,好歹是實作了,不過這種有點問題,那就是連帶著底圖也一起更換了,然后就有點傷眼,
生成字符視頻
因為視頻就是一幀一幀的圖片,所以生成字符圖片就走完了生成字符視頻一半的路,把上面的邏輯進行疊代就可以生成字符視頻了,
import cv2
import numpy as np
def pixel2char(pixel):
char_list = "@#$%&erytuioplkszxcv=+---. "
index = int(pixel / 256 * len(char_list))
return char_list[index]
def get_char_img(img, scale=4, font_size=5):
# 調整圖片大小
h, w = img.shape
re_im = cv2.resize(img, (w//scale, h//scale))
# 創建一張圖片用來填充字符
char_img = np.ones((h//scale*font_size, w//scale*font_size), dtype=np.uint8)*255
font = cv2.FONT_HERSHEY_SIMPLEX
# 遍歷圖片像素
for y in range(0, re_im.shape[0]):
for x in range(0, re_im.shape[1]):
char_pixel = pixel2char(re_im[y][x])
cv2.putText(char_img, char_pixel, (x*font_size, y*font_size), font, 0.5, (0, 0, 0))
return char_img
def generate(input_video, output_video):
# 1、讀取視頻
cap = cv2.VideoCapture(input_video)
# 2、獲取視頻幀率
fps = cap.get(cv2.CAP_PROP_FPS)
# 讀取第一幀,獲取轉換成字符后的圖片的尺寸
ret, frame = cap.read()
char_img = get_char_img(cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY), 4)
# 創建一個VideoWriter,用于保存視頻
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
writer = cv2.VideoWriter(output_video, fourcc, fps, (char_img.shape[1], char_img.shape[0]))
while ret:
# 讀取視頻的當前幀,如果沒有則跳出回圈
ret, frame = cap.read()
if not ret:
break
# 將當前幀轉換成字符圖
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
char_img = get_char_img(gray, 4)
# 轉換成BGR模式,便于寫入視頻
char_img = cv2.cvtColor(char_img, cv2.COLOR_GRAY2BGR)
writer.write(char_img)
writer.release()
if __name__ == '__main__':
generate('in.mp4', 'out.mp4')
嗯,這個生成,,,,,,有點受不了,還沒生成完,電腦就開始抽煙了!!!魯大師抽煙了!!!斷開后,視頻還沒到原視頻的一半時長,但大小就是原視頻的好幾倍!!!后面再優化吧,不能奶不能奶,
末,上面用上的圖片







先這樣吧,以后再來更新,暫時沒時間了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/539034.html
標籤:其他
上一篇:編譯器優化丨Cache優化
