1. OpenCV
1.1 opencv概念
- OpenCV是一個基于BSD許可(開源)發行的跨平臺計算機視覺庫
- 可以運行在Linux、Windows、Android和Mac OS操作系統上
- 它輕量級而且高效 – 有一系列C函數和少量 C++ 類構成
- 同時提供了 Python、Ruby、MATLAB等語言的接口
- 實現了圖像處理和計算機視覺方面的很多通用算法
opencv:
- 是一個第三方庫,處理圖形和視頻
- 是 c 和 c++ 語言實現
- 可以運行在多種平臺上: 跨平臺
- 可以被多種語言直接調用
- 實現圖像處理方法多種通用的算法
基礎知識:
? 熟悉python語言編程 c/c++編程基礎 矩陣運算
1.2 開發環境搭建
-
windows下用python語言
-
IDEA: pycharm
-
安裝opencv-python庫:
pip install opencv-python
2. 主要內容
2.1 讀取圖片
import cv2 as cvimg = cv.imread("../pics/20.jpg")cv.imshow("img", img)cv.waitKey() # 等待用戶操作
cv.destroyAllWindows() # 釋放窗口
注意: cv.imread會將圖片讀成矩陣格式
2.2 main
- 演示一個opencv項目
- 圖形圖像處理基礎知識
- 圖像幾何變換: 大小, …
- 圖像平滑處理
- 圖形形態學操作
- 輪廓監測
- 圖像對象識別(機器學習)
3. 圖像操作基礎函數
3.1 圖像處理的基礎知識
3.1.1 讀取圖片
格式: img = imread(圖片路徑名字, 讀取方式(0: 灰度圖片, 1: 彩色圖片))
參數1: 讀取的圖片的路徑
參數2: 讀取的方式, 灰度(0)還是彩色(1)
返回值: 圖片的矩陣形式
功能: 打開文件,讀取文件內容, 將文件轉換成矩陣形式
import cv2 as cv# 讀取路徑為../pics/20.jpg, 讀取方式是一個灰度
img = cv.imread("../pics/20.jpg", 0)
3.1.2 創建一個窗口
cv.namedWindow("窗口名字")
3.1.3 在指定的窗口中顯示圖片
cv.imshow("窗口名字", 圖片的矩陣格式)
3.1.4 讓窗口等待一定時間
cv.waitKey(時間)# 時間單位是ms(0表示無限制等待,直到用戶按任意鍵結束)
cv.destroyAllWindows()# 釋放所有窗口# 釋放指定窗口
# cv.destroyWindow("窗口名字")
4. 圖像表示
圖像處理基本函數: imread()、imshow()、waitKey()、destroyAllWindows()、imwrite()
處理圖像就是操作img
img是通過什么方式表示一副圖像?
圖像的表示:
-
二值圖像: 圖像中僅有兩種顏色: 黑或白
? 圖像: 由多個像素點構成,按照行列分布
?
? 用一個矩陣表示圖像,
? 例如 圖像 512 * 512 大小,則用 512 * 512(二維矩陣) 矩陣表示圖像
? 像素點是黑色,則對應的矩陣值是 0
? 像素點是白色,則對應的矩陣值是 255
? 每一個像素點用一個字節來存儲, 矩陣(二維數組) 每一個元素取值范圍 0~255
-
灰度圖像: 圖像中顏色, 共計由256級 0~255
? 黑色 – 白色
? 圖像 --> 用矩陣表示
? 矩陣中每一個元素存儲圖像中對應的像素點的值
?
5. 彩色圖像表示
-
圖像的表示方法:
img = cv.imread()
-
二值圖像: 0(黑色) 、 255(白色)
-
灰度圖像: 黑色 -> 白色 共計分了256等級 ( 0 黑色 --> 255 白色)
-
彩色圖像:
- 彩色圖像是更常見的一類圖像,能表現更加豐富的細節信息
- 神經生理學實驗發現: 在視網膜上存在三種不同的顏色感受器能夠識別三種不同的顏色(紅色、綠色 和藍色,即三基色)
- 自然界中常見的各種色光都可以通過三基色按照一定的比例混合構成
- 在RGB色彩空間,存在R(紅色)通道,G(綠色)通道和B(藍色)通道,共三個通道
- 每一個色彩通道值得范圍都在[0~255]之間,利用三個色彩通道組合表示顏色
結論:
- 彩色圖像由R、G、B三個通道構成
- 每個通道都可以理解位一個獨立得灰度圖像
- 一個彩色圖像對應三個矩陣
- 用一個三位數組表示一副RGB色彩空間得彩色圖像
【圖像表示方法總結】:
- 一般情況下, 在RGB得色彩空間,圖像得通道是R-G-B,但是在opencv中,通道得順序是 B-G-R
- 在圖像處理中可以根據需要對圖像得通道順序進行轉換,也可以根據需要對不同色彩空間的圖像進行類型轉換
- 將灰度圖像轉為二值圖像
- 將彩色圖像轉為灰度圖像
【像素處理】:
- 像素是構成圖像的基本單位,像素處理是圖像的基本操作
- 通過位置索引的形式對圖像內的元素進行訪問、處理
一、二值圖像及灰度圖像
- opencv中,最小的數據類型是無符號的8位數,沒有二值圖像這種數據類型,使用0表示黑色,使用255表示白色
- 將二值圖像理解位特殊的灰度圖像,處理方式和灰度圖像一致
- 灰度圖像是一個二維數組,通過使用img[i, j]的形式訪問其中像素點,表示第i行和第j列上的像素點對應的像素值
小栗子: 讀取一個灰度圖像,對其中的像素訪問和修改
import cv2 as cvimg = cv.imread("../pics/20.jpg")
img[0:100, 0:100] = 0cv.imshow("img", img)cv.waitKey(0)
cv.destroyAllWindows()
5.1 彩色圖像的修改
- RGB模式的彩色圖像在讀入進行處理時: 按照行的方式一次讀取RGB圖像的 B 通道、G通道、R通道像素點, 存儲為一個三位數組
- 一幅 R行*C列的原始RGB圖像,以BGR模式的三維數組形式存儲
- img[0,0,0]表示第0行第0列B通道像素值
- img[0,0]表示第0行第0列像素點的BGR值,例如數值為: [0, 0, 255]
【栗子】: 使用numpy生成一個藍色(綠色、紅色)的 300 *300 的矩陣
import numpy as np
import cv2 as cv# 藍色矩陣
blue = np.zeros((300, 300, 3), dtype=np.uint8)
blue[:, :, 0] = 255
print("blue")
cv.imshow("blue", blue)# 綠色矩陣
green = np.zeros((300, 300, 3), dtype=np.uint8)
green[:, :, 1] = 255
print("green")
cv.imshow("green", green)# 紅色矩陣
red = np.zeros((300, 300, 3), dtype=np.uint8)
red[:, :, 2] = 255
print("red")
cv.imshow("red", red)cv.waitKey(0)
cv.destroyAllWindows()
【栗子2】: 畫一個條紋狀的 藍綠紅 300 * 300 的圖像
import numpy as np
import cv2 as cvbgr = np.zeros((300, 300, 3), dtype=np.uint8)
# 藍色
bgr[:, 0:100, 0] = 255
# 綠色
bgr[:, 100:200, 1] = 255
# 紅色
bgr[:, 200:300, 2] = 255cv.imshow("bgr", bgr)cv.waitKey(0)
cv.destroyAllWindows()
5.2 圖像操作小結
打印某一個像素bgr的值
img = cv.imread("../pics/20.jpg", 1)
# 讀取第100行,100列像素位置的bgr值
(b, g, r) = img[100, 100]
print(b, g ,r)
【栗子】: 在圖像(彩色)中畫一個水平和垂直線
import cv2 as cvimg = cv.imread("../pics/20.jpg", 1)# B通道
img[:, 0, 0] = 0
img[0, :, 0] = 0# G通道
img[:, 0, 1] = 0
img[0, :, 1] = 0# R通道
img[:, 0, 2] = 0
img[0, :, 2] = 0print(img.shape)cv.imshow("img", img)cv.waitKey(0)
cv.destroyAllWindows()
【栗子】: 增強圖像的像素值
import cv2 as cvimg = cv.imread("../pics/20.jpg", 0)
(row, col) = img.shapecv.imshow("pre", img)for i in range(0,row):for j in range(0,col):tmp = img[i][j] +90if(tmp > 255):tmp = tmp-255img[i][j] = tmpcv.imshow("after", img)cv.waitKey(0)
5.3 使用numpy.array訪問像素
使用numpy.array提供的item()和itemset()函數訪問和修改像素.
兩個函數經過優化處理,能夠大幅度提高處理效率
比直接用索引訪問快的多、可讀性更好
-
訪問灰度圖像:
- item(行, 列)
- itemset((行,列), 值)
-
訪問彩色圖像:
- item(行,列,通道)
- itemset(三元組索引, 新值)
【栗子1】: 修改和訪問灰度圖像
import numpy as npimg = np.random.randint(10, 99, size=[5, 5], dtype=np.uint8)
print(img)
print(img.item(3, 2))
img.itemset((3, 2), 255)
print(img.item(3, 2))
【栗子2】: 生成一個灰度圖像,其中的像素值為隨機數
import numpy as np
import cv2 as cvimg = np.random.randint(0,255, size=[256, 256], dtype=np.uint8)
cv.imshow("img", img)cv.waitKey(0)
cv.destroyAllWindows()
【栗子3】: 讀取一個灰度圖片,并對其進行像素值訪問
import numpy as np
import cv2 as cvimg = cv.imread("../pics/20.jpg", 0)cv.imshow("before", img)for i in range(0,255):for j in range(10,40):img.itemset((i,j), 255)cv.imshow("after", img)cv.waitKey(0)
cv.destroyAllWindows()
【栗子4】: 生成一個彩色圖像
# 生成一個256 * 256的隨機彩色圖像
import numpy as np
import cv2 as cvimg = np.random.randint(0, 255, size=[256, 256, 3], dtype=np.uint8)cv.imshow("img", img)cv.waitKey(0)
cv.destroyAllWindows()
5.4 感興趣區域(ROI)
處理圖像過程中,可能回對圖像某一個特定區域感興趣,該區域稱為感興趣區域(Region of Interest, ROI)
設定感興趣區域后,就可以對該區域進行整體操作:
- 將一個感興趣的區域A賦值給變量B
- 將變量B賦值給另一個區域C,達到區域C內賦值道區域A的目的
【栗子】: 對臉部打碼
import cv2 as cv
import numpy as npimg = cv.imread("../../pics/20.jpg", 0)# head = img[15:215, 150:330]
mask = np.random.randint(0, 255, size=[200, 180], dtype=np.uint8)img[15:215, 150:330] = mask
cv.imshow("img", img)cv.waitKey(0)
cv.destroyAllWindows()
5.5 通道操作
RGB圖在opencv中按照BGR的形式存儲,在圖像處理過程中根據實際,對通道進行拆分和合并
通道拆分:
- 通過索引拆分:
b = img[:,:,0]
g = img[:,:,1]
r = img[:,:,2]
- 通過函數拆分:
b,g,r = cv.splite(img)
5.5.1 通道的拆分
【栗子】: 將彩色圖像中的RGB通道分離處理,并單獨成圖顯示
import cv2 as cvimg = cv.imread("../../pics/20.jpg")b, g, r = cv.split(img)
cv.imshow("b", b)
cv.imshow("g", g)
cv.imshow("r", r)cv.waitKey(0)
cv.destroyAllWindows()
5.5.2 通道的合并
【栗子】: 將分離出來的bgr通道合并成 rgb通道并顯示出來
import cv2 as cvimg = cv.imread("../../pics/20.jpg")b, g, r = cv.split(img)rgb = cv.merge([r, g, b])cv.imshow("rgb", rgb)
cv.imshow("bgr", img)cv.waitKey(0)
cv.destroyAllWindows()
5.6 圖像屬性
shape:
- 如果是彩色圖像,返回包括 行數、列數、通道個數
- 如果是二值圖像或者灰度圖像: 返回 行數、列數
size: 返回圖像的大小: 行數 * 列數 * 通道個數
dtype: 返回圖像數據類型
import cv2 as cvimg = cv.imread("../../pics/20.jpg")
# 矩陣的維度
print(img.shape)
# 所占字節數
print(img.size)
# 元素的類型
print(img.dtype)img_grey = cv.imread("../../pics/20.jpg", 0)
# 矩陣的維度
print(img_grey.shape)
# 所占字節數
print(img_grey.size)
# 元素的類型
print(img_grey.dtype)
6. 圖像運算
- 圖像的加法運算、位運算
- 使用基礎運算實現: 位平面分解、圖像異或加密、數字水印、臉部打碼/解碼 等功能
6.1 圖像的加法運算
import cv2 as cv# 讀取一個灰度圖像
img = cv.imread("../../pics/20.jpg", 0)img[:,:] += 20cv.imshow("img", img)cv.waitKey(0)
cv.destroyAllWindows()
6.1.1 使用cv.add進行加法運算
-
處理圖像過程中,進程需要對圖像進行加法運算.可以它通過"+"或者 cv2.add()函數實現
-
用加號預運算符對圖像a和圖像b進行求和運算規則如下:
a+b = mod(a+b, 255)
- 像素值之和大于255處理為255的模,否則像素值為加的結果
-
使用函數cv2.add()計算像素值和規則:
- 計算結果 = cv2.add(圖像a, 圖像b)
- 像素之和大于255處理為最大值255,否則像素值為加的結果
【栗子】: 使用add添加像素
import cv2 as cvimg = cv.imread("../../pics/20.jpg")cv.imshow("before", img)
img = cv.add(img, 90)cv.imshow("img", img)
cv.waitKey(0)
cv.destroyAllWindows()
【栗子】: 使用add進行兩張圖片相加
import cv2 as cvimg_16 = cv.imread("../../pics/16.jpg")
img_18 = cv.imread("../../pics/18.jpg")
img_20 = cv.imread("../../pics/20.jpg")
shape_500_300_20 = img_20[0:300, 0:500]
shape_500_300_16 = img_16[0:300, 0:500]comb = cv.add(shape_500_300_20, shape_500_300_16)cv.imshow("comb", comb)cv.waitKey(0)
cv.destroyAllWindows()
注意: add函數是 兩個相加,如果值超過255就取255 .
6.1.2 圖像圖像加權和
-
加權和: 計算兩幅圖像的像素之和時,將每幅圖像權重考慮進來,使用公式
dst = saturate(src1 * a + src2 * b + r)
-
對應opencv()中的函數:
dst = cv2.addWeighted(src1, a, src2, b, r)
import cv2 as cvimg_16 = cv.imread("../../pics/16.jpg")
img_20 = cv.imread("../../pics/20.jpg")
dali = img_20[0:300, 0:500]
mao = img_16[0:300, 0:500]comb = cv.addWeighted(dali, 0.1, mao, 0.9, 0)cv.imshow("comb", comb)cv.waitKey(0)
cv.destroyAllWindows()
6.2 圖像的位運算
按位邏輯運算在圖形處理中是一種非常重要的處理方式:
函數 | 作用 |
---|---|
cv.bitwise_and() | 按位與 |
cv.bitwise_or() | 按位或 |
cv.bitwise_xor() | 按位異或 |
cv.bitwise_not() | 按位取反 |
【掩模】:
使用掩模參數時,操作只會在掩模值為空的像素點上執行,在opencv中的函數如下:
img3 = cv.add(img1, img2, mask=mask)
import numpy as npimg = cv.imread("../../pics/20.jpg")w, h, c = img.shape
mask = np.zeros((w, h), dtype=np.uint8)mask[40:160, 80:160] = 255
mask[40:200, 40:80] = 255exam = cv.bitwise_and(img, img, mask=mask)cv.imshow("mask", mask)
cv.imshow("exam", exam)cv.waitKey(0)
cv.destroyAllWindows()
6.2.1 位運算的作用
位平面分解:
-
概念: 將灰度圖中處于同一比特位上的二進制像素值進行組合,得到一幅二進制圖像,該圖像稱為灰度圖像的一個位平面
-
在8位灰度圖中,每一個像素使用8位二進制來表示,其值范圍【0~255】
- 表示為:
value = a7* 2^7 + a6 * 2^6 + a5 * 2^5 + a4* 2^4 + a3 * 2^3 + a2 * 2^2 + a1 *2^1 + a0 * 2^0
- a0~a7的可能值是0或者1 ,各個權重不一樣,a7權重最高,a0最低。a7的值對圖像影響最大,而a0得值對圖像影響最小
- 表示為:
-
通過提取灰度像素點二進制像素值得每一個比特位組合,可以得到8個位平面,組成8個圖像.
- a7權重最高,所構成得位平面與原始圖像相關性最高,最類似a0權重最低,所構成得位平面與原始圖像相關性最低,該平面是雜亂無章
6.2.2 位平面分解的步驟
- 圖像預處理:
- 讀取原始圖像,獲取原始圖像的寬度和高度
- 構造提取矩陣
- 使用按位與操作將一個數值指定位上的數字提取出來
- 提取第0位: xxxxxxxx & 0000 0001 --> 0000 000x
- 提取第3位: xxxxxxxx & 0000 1000 --> 0000 x000
- 提取第5位: xxxxxxxx & 0001 0000 --> 000x 0000
- 建立一個值為 2^n的Mat作為提取矩陣,與原始圖像進行按位與運算 —> 提取第n個位平面
- 使用按位與操作將一個數值指定位上的數字提取出來
import numpy as np
import cv2 as cvimg = cv.imread("../../pics/20.jpg", 0)w, h = img.shapemat = np.ones((w, h), dtype=np.uint8)rest0 = cv.bitwise_and(img, mat) * 128cv.imshow("img", img)
cv.imshow("rest0", rest0)cv.waitKey(0)
cv.destroyAllWindows()
-
提取位平面
- 將灰度圖像與提取矩陣進行按位與運算,得到各個平面
-
閾值處理:
- 通過計算得到位平面是一個二值圖
- 直接顯示得到一張黑色圖像,默認顯示是8位灰度級,像素值小顯示位黑色
- 提取位平面后,讓圖像能夠以黑白顏色顯示出來,必須進行閾值處理
- 二值命名進行閾值處理: 將其中大于零的值處理為255
6.2.3 圖像的加密與解密
-
通過按位異或運算可以實現圖像的加密和解密
-
原始圖像與神秘圖像進行按位異或,可以實現加密
-
將加密后的圖像與密鑰圖像再次按位異或,實現解密
【栗子】: 生成一個隨機的密鑰,給圖片加密.
import cv2 as cv
import numpy as npimg = cv.imread("../../pics/20.jpg")
dst = np.random.randint(0, 255, img.shape, dtype=np.uint8)# 給圖像加密
img = cv.bitwise_xor(img, dst)cv.imshow("withkey", img)# 圖像解密
img = cv.bitwise_xor(img, dst)
cv.imshow("unshiftkey", img)cv.waitKey(0)
cv.destroyAllWindows()
6.2.4 數字水印
概念
- 最低有效位: 指的是一個二進制數中的第0位(最低位)
- 最低有效位信息隱藏: 將一個需要隱藏的二值圖像信息嵌入載體圖像最低有效位.將載體圖像的最低有效位替換為當前需要隱藏的二值圖像,實現二值圖像隱藏的目的
- 二值圖像處于載體圖像的最低有效位上,對這個圖像影響非常不明顯,具有較高的隱蔽性
- 需要將載體圖像的最低有效位層提取出來,即可得到嵌入在該位上的二值圖像達到提取秘密信息的目的
- 這種信息隱蔽也被稱為數字水印, 通過該方式可以實現: 信息隱藏、版權認證、身份認證等功能
- 如果嵌入式信息是秘密信息,則實現實心的隱藏
- 如果嵌入式載體圖像內的信息是版權信息,實現版權認證
- 如果嵌入式載體圖像內的信息是身份信息,實現數字簽名
- 被嵌入載體圖像內的信息也被稱為數字水印信息
將一幅二值圖像嵌入到一個載體圖像的第0個位平面
將載體圖像的第0個位平面提取出來,獲得二值圖像的信息
原理
-
嵌入過程: 將載體圖像的第0個位平面替換位數字水印信息(一幅二值圖像)
-
提取過程: 將載體圖像的最低有效位所構成的第0個位平面提取出來,得到數字水印信息
-
嵌入式過程:
- 載體圖像為灰度圖像,數字水印為二值圖像,則直接操作
- 二者均為彩色圖像,需要先進行通道分解、圖層分解后進行操作
實現
嵌入水印圖像:
- 將原始圖像每個像素的最低位全部清0
- 水印圖像 --> 二值圖像 --> 二進制二值圖像
- 將水印二進制圖像嵌入到原始圖像第0個位平面
提取水印圖像:
- 將載體圖像的第0個位平面提取出來 --> 二值圖像(1, 0)
- 二值圖像 --> 轉成一幅二值圖像(255, 0)
- 將載體圖像的第0個位平面全部置0
import cv2 as cv
import numpy as np# 載體圖像
img = cv.imread("../../pics/16.jpg", 0)
r, c = img.shape# 將第0個位平面全部置為0
t254 = np.ones((r, c), dtype=np.uint8) * 254
exam = cv.bitwise_and(img, t254)# 讀取數字水印
watermark = cv.imread("../../pics/18.jpg", 0)# 將水印 --> 二值圖像
w = watermark[:, :] > 1
watermark[w] = 1# 水印嵌入載體圖像
e = cv.bitwise_or(exam, watermark)# 水印的提取
r,c = e.shape
t1 = np.ones((r,c), dtype=np.uint8)
wm = cv.bitwise_and(e, t1)w = wm[:,:] > 0
wm[w] = 255cv.imshow("wm", wm)cv.waitKey(0)
cv.destroyAllWindows()