之前幾篇是OpenCV操作圖片,這不是OpenCV的賣點,賣點是計算機視覺,所謂視覺,即識別。問題場景簡單描述為
輸入:圖片
輸出:對應(yīng)的物體的位置,數(shù)量等信息
計算機識別的整體步驟大概為:
預(yù)處理
分割
特征提取
機器學(xué)習(xí)分類
后期處理
去噪
- 鹽噪點(椒鹽類似)
Mat lena = Cv2.ImRead("lena.jpg");
// 添加椒鹽噪點
Mat salt = new Mat();
lena.CopyTo(salt);
Random rd = new Random();
int n = 5000;// 噪點個數(shù)
for (int k = 0; k < n; k++)
{
int i = rd.Next() % salt.Cols;
int j = rd.Next() % salt.Rows;
salt.At<Vec3b>(j, i) = new Vec3b(255, 255, 255);
}


- 均值濾波
在圖像上對目標像素給一個模板,該模板包括了其周圍的臨近像素(如以目標像素為中心的周圍8個像素,構(gòu)成一個濾波模板,即包括目標像素本身),再用模板中的全體像素的平均值來代替原來像素值。
// 均值濾波
Mat blur = new Mat();
Cv2.Blur(salt, blur, new Size(3, 3));
Cv2.ImShow("Blur", blur);

-
中值濾波
中值濾波法是一種非線性平滑技術(shù),它將每一像素點的灰度值設(shè)置為該點某鄰域窗口內(nèi)的所有像素點灰度值的中值.
中值濾波
// 中值濾波
Mat median = new Mat();
Cv2.MedianBlur(salt, median, 3);
Cv2.ImShow("median", median);

-
高斯濾波
高斯濾波就是對整幅圖像進行加權(quán)平均的過程,每一個像素點的值,都由其本身和鄰域內(nèi)的其他像素值經(jīng)過加權(quán)平均后得到。高斯濾波的具體操作是:用一個模板(或稱卷積、掩模)掃描圖像中的每一個像素,用模板確定的鄰域內(nèi)像素的加權(quán)平均灰度值去替代模板中心像素點的值。
高斯
// 高斯濾波
Mat gaussian = new Mat();
Cv2.GaussianBlur(salt, gaussian, new Size(3, 3), 0);// 第三個參數(shù) 必須為正奇數(shù),x,y可以不同
Cv2.ImShow("gaussian", gaussian);

-
盒式濾波
盒式濾波1
盒式濾波2
如果歸一化,則和均值濾波是一樣的
// 盒式濾波
Mat box = new Mat();
Cv2.BoxFilter(salt, box, MatType.CV_8UC3, new Size(5,5));
Cv2.ImShow("box", box);

- 形態(tài)學(xué) 濾波
-
膨脹
膨脹(dilate)就是求局部的最大值的操作。從數(shù)學(xué)的角度就是圖像與核進行卷積
核可以是任何形狀核大小,它擁有一個單獨定義出來的參考點,稱為錨點,可以把核視為模板或者掩碼
數(shù)學(xué)公式
膨脹 -
腐蝕
腐蝕就是求局部最小值的操作。
腐蝕 - 開閉運算
開運算:先腐蝕后膨脹
閉運算:先膨脹后腐蝕
-
總結(jié):
- 對二值化圖像:去掉像素用腐蝕,開運算;增加像素用膨脹,閉運算
- 對灰度圖像:灰度值降低,用腐蝕,開運算;灰度值增加用膨脹,閉運算
- 使用先開后比閉的操作,可以有效的去除噪聲
-
// 形態(tài)學(xué) Open
Mat morphotoOpen = new Mat();
Cv2.MorphologyEx(salt, morphotoOpen, MorphTypes.Open, new Mat());
Cv2.ImShow("morphotoOpen", morphotoOpen);

- 雙邊濾波
雙邊濾波(Bilateral filter)是一種非線性的濾波方法,是結(jié)合圖像的空間鄰近度和像素值相似度的一種折中處理,同時考慮空域信息和灰度相似性,達到保邊去噪的目的。具有簡單、非迭代、局部的特點 [1] 。雙邊濾波器的好處是可以做邊緣保存(edge preserving),一般過去用的維納濾波或者高斯濾波去降噪,都會較明顯地模糊邊緣,對于高頻細節(jié)的保護效果并不明顯。
雙邊濾波器顧名思義比高斯濾波多了一個高斯方差sigma-d,它是基于空間分布的高斯濾波函數(shù),所以在邊緣附近,離的較遠的像素不會太多影響到邊緣上的像素值,這樣就保證了邊緣附近像素值的保存。但是由于保存了過多的高頻信息,對于彩色圖像里的高頻噪聲,雙邊濾波器不能夠干凈的濾掉,只能夠?qū)τ诘皖l信息進行較好的濾波
Mat bilateral = new Mat();
Cv2.BilateralFilter(salt, bilateral, 9, 80, 80);
Cv2.ImShow("bilateral", bilateral);

- 高斯噪點
Mat gaussianNoise = new Mat(lena.Size(), MatType.CV_64FC1);
RNG rng = new RNG((ulong)DateTime.Now.Ticks);
rng.Fill(gaussianNoise, DistributionType.Normal, 0, 15);
Mat yccImg = lena.CvtColor(ColorConversionCodes.BGR2YCrCb);
Mat[] sigMats =Cv2.Split(yccImg);
sigMats[0].ConvertTo(sigMats[0], MatType.CV_64FC1);
sigMats[0] = sigMats[0] + gaussianNoise;
sigMats[0].ConvertTo(sigMats[0], MatType.CV_8UC1); //Y通道加高斯噪聲后圖像,自動截斷小于零和大于255的值
Mat gaussianImg = new Mat(lena.Size(), MatType.CV_8UC3); //添加高斯噪聲的圖像;
Cv2.Merge(sigMats, gaussianImg);
gaussianImg = gaussianImg.CvtColor(ColorConversionCodes.YCrCb2BGR);
Cv2.ImShow("gaussianImg", gaussianImg);

此處采用的YCrCb格式,然后噪點疊加在Y通道。具體機制,百度YCrCb的介紹即可。
-
均值濾波均值
-
中值濾波中值
-
高斯濾波高斯
-
雙邊濾波雙邊
對于不同的噪點,需要調(diào)整對應(yīng)的去噪方式。
去除背景
采用算法去除背景,只留下關(guān)注的信息。一張背景圖,一張當前圖,兩張圖采用減法或者除法可以去掉背景信息。
- 減法 R=L-I
- 除法 R=255*(1-I/L)
此種方法并不是一個非常好的方法,后續(xù),在動態(tài)識別中會介紹更多的去除背景的方法。
二值化
二值圖像:只有兩種顏色,黑和白,1白色,0黑色
//
// 摘要:
// Applies a fixed-level threshold to each array element.
//
// 參數(shù):
// src:
// input array (single-channel, 8-bit or 32-bit floating point).
//
// dst:
// output array of the same size and type as src.
//
// thresh:
// threshold value.
//
// maxval:
// maximum value to use with the THRESH_BINARY and THRESH_BINARY_INV thresholding
// types.
//
// type:
// thresholding type (see the details below).
//
// 返回結(jié)果:
// the computed threshold value when type == OTSU
public static double Threshold(InputArray src, OutputArray dst, double thresh, double maxval, ThresholdTypes type);
//
// 摘要:
// Thresholding type
[Flags]
public enum ThresholdTypes
{
//
// 摘要:
// \f[\texttt{dst} (x,y) = \fork{\texttt{maxval}}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{0}{otherwise}\f]
Binary = 0,
//
// 摘要:
// \f[\texttt{dst} (x,y) = \fork{0}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{maxval}}{otherwise}\f]
BinaryInv = 1,
//
// 摘要:
// \f[\texttt{dst} (x,y) = \fork{\texttt{threshold}}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{src}(x,y)}{otherwise}\f]
Trunc = 2,
//
// 摘要:
// \f[\texttt{dst} (x,y) = \fork{\texttt{src}(x,y)}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{0}{otherwise}\f]
Tozero = 3,
//
// 摘要:
// \f[\texttt{dst} (x,y) = \fork{0}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{src}(x,y)}{otherwise}\f]
TozeroInv = 4,
Mask = 7,
//
// 摘要:
// flag, use Otsu algorithm to choose the optimal threshold value
Otsu = 8,
//
// 摘要:
// flag, use Triangle algorithm to choose the optimal threshold value
Triangle = 16
}
- 二值化方式
-
Origin
Origin -
Binary
binary_math.png
binary -
BinaryInv
binarayinv_math
binarayinv.png -
Trunc
trunc_math
trunc -
Tozero
tozero_math
tozero
*TozeroInv
tozeroinv_math
threshtozeroinv
-
- 閾值取值方式
- 手動
Threshold中指定第三個值,即閾值 - Otsu
不用自己指定thresh值,系統(tǒng)會進行計算并且作為返回值返回。
最大類間方差法是由日本學(xué)者大津于1979年提出的,是一種自適應(yīng)的閾值確定的方法,又叫大津法,簡稱OTSU。它是按圖像的灰度特性,將圖像分成背景和目標2部分。背景和目標之間的類間方差越大,說明構(gòu)成圖像的2部分的差別越大,當部分目標錯分為背景或部分背景錯分為目標都會導(dǎo)致2部分差別變小。因此,使類間方差最大的分割意味著錯分概率最小。- 原理
- 先計算圖像的直方圖,即將圖像所有的像素點按照0~255共256個bin,統(tǒng)計落在每個bin的像素點數(shù)量
- 歸一化直方圖,也即將每個bin中像素點數(shù)量除以總的像素點,使其限制在0~1之間
- 在這里設(shè)置一個分類的閾值i,也即一個灰度級,開始從0迭代
- 通過歸一化的直方圖,統(tǒng)計0~i 灰度級的像素(假設(shè)像素值在此范圍的像素叫做前景像素) 所占整幅圖像的比例w0,并統(tǒng)計前景像素的平均灰度u0;統(tǒng)計i~255灰度級的像素(假設(shè)像素值在此范圍的像素叫做背景像素) 所占整幅圖像的比例w1,并統(tǒng)計背景像素的平均灰度u1;在這里,設(shè)圖像的總平均灰度為u2,類間方差記為g。
其中:
u2=ω0?u0+ω1?u1,g=ω0(u0?μ2)2+ω1(u1?u2)2
將u2帶入g中,可得:
g=ω0ω1(u0?u1)2 - ++i,閾值的灰度值加1,并轉(zhuǎn)到第4個步驟,直到i為256時結(jié)束迭代
- 將最大g相應(yīng)的<script type="math/tex" id="MathJax-Element-948">i</script>值作為圖像的全局閾值
- 原理
- 手動
參照:https://blog.csdn.net/qq_29462849/article/details/81022607
- Triangle
不用自己指定thresh值,系統(tǒng)會進行計算并且作為返回值返回。
最適用于單個波峰,最開始用于醫(yī)學(xué)分割細胞等。- 原理:
- 圖像轉(zhuǎn)灰度
- 計算圖像灰度直方圖
- 尋找直方圖中兩側(cè)邊界
- 尋找直方圖最大值
- 檢測是否最大波峰在亮的一側(cè),否則翻轉(zhuǎn)
- 計算閾值得到閾值T,如果翻轉(zhuǎn)則255-T
- 原理:
參照https://blog.csdn.net/qq_41498261/article/details/102770535
- 測試:
- 手動
- 代碼
- 手動
Mat lena = Cv2.ImRead("lena.jpg", ImreadModes.Grayscale);
Cv2.ImShow("lena", lena);
Mat Binary = new Mat();
Cv2.Threshold(lena, Binary, 140, 255, ThresholdTypes.Binary);
Cv2.ImShow("Binary", Binary);
Mat BinaryInv = new Mat();
Cv2.Threshold(lena, BinaryInv, 140, 255, ThresholdTypes.BinaryInv);
Cv2.ImShow("BinaryInv", BinaryInv);
Mat Tozero = new Mat();
Cv2.Threshold(lena, Tozero, 140, 255, ThresholdTypes.Tozero);
Cv2.ImShow("Tozero", Tozero);
Mat TozeroInv = new Mat();
Cv2.Threshold(lena, TozeroInv, 140, 255, ThresholdTypes.TozeroInv);
Cv2.ImShow("TozeroInv", TozeroInv);
Mat Trunc = new Mat();
Cv2.Threshold(lena, Trunc, 140, 255, ThresholdTypes.Trunc);
Cv2.ImShow("Trunc", Trunc);
Cv2.WaitKey();
-
結(jié)果
lena.jpg
binray.jpg
binaryinv.jpg
trunc.jpg
tozero.jpg
tozeroinv.jpg - OSTU
- 代碼
Mat lena = Cv2.ImRead("lena.jpg", ImreadModes.Grayscale);
Cv2.ImShow("lena", lena);
Mat Binary = new Mat();
double thresh = Cv2.Threshold(lena, Binary, 140, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
Console.WriteLine(thresh);
Cv2.ImShow("Binary", Binary);
Mat BinaryInv = new Mat();
thresh = Cv2.Threshold(lena, BinaryInv, 140, 255, ThresholdTypes.BinaryInv | ThresholdTypes.Otsu);
Console.WriteLine(thresh);
Cv2.ImShow("BinaryInv", BinaryInv);
Mat Tozero = new Mat();
thresh = Cv2.Threshold(lena, Tozero, 140, 255, ThresholdTypes.Tozero | ThresholdTypes.Otsu);
Console.WriteLine(thresh);
Cv2.ImShow("Tozero", Tozero);
Mat TozeroInv = new Mat();
thresh = Cv2.Threshold(lena, TozeroInv, 140, 255, ThresholdTypes.TozeroInv | ThresholdTypes.Otsu);
Console.WriteLine(thresh);
Cv2.ImShow("TozeroInv", TozeroInv);
Mat Trunc = new Mat();
thresh = Cv2.Threshold(lena, Trunc, 140, 255, ThresholdTypes.Trunc | ThresholdTypes.Otsu);
Console.WriteLine(thresh);
Cv2.ImShow("Trunc", Trunc);
Cv2.WaitKey();
-
效果
lena
binray
binaryinv
trunc
tozero
tozeroinv
console- Triangle
代碼與OSTU基本一致,只是第5個參數(shù)后面是| ThresholdTypes.Otsu
其輸出值為114
- Triangle

































