學習記錄…
前文說到大津閾值法是一種自適應的基于全域的閾值分割演算法,只有在影像直方圖分布為雙峰的情況下才會呈現出一種比較好的分割效果,但是待分割影像直方圖分布并不是每次都是理想的結果,可能會是光照的影響改變了原本為雙峰的直方圖分布,或者說背景本身就呈現出了兩個灰度級,加上前景那就是三個灰度級了,等等一些情況都會造成使用Otsu分割失敗,
試驗用圖如下:

如圖所示:直接利用Otsu演算法對原圖進行分割,因為高灰度級分布的影響,我們想要的前景數字部分被歸結為了背景,導致分割失敗,期望分割圖是利用上圖中掩膜部分直方圖而得到的,而在本實驗中,直接用Otsu分割圖取反形成mask掩膜(即對應于原圖只有在掩膜影像中為白色的區域才參與運算),
所以因為有掩膜的參與,我們可以實作任意形狀區域的大津閾值法,不再是局限于全域影像,

其實方法本身不難,難點在你該通過什么方式才能得到合適的掩膜區域,而掩膜區域中必須包含待分割的前景,本實驗中,采用先對影像進行閾值處理,從結果中提取掩膜,然后利用掩膜部分采用Otsu演算法,得到正確分割影像,獲取掩膜的方法多種,讀者自行嘗試,更多應用,歡迎一起交流學習!
試驗代碼如下,和前一節的內容基本上沒有太大的出入:
int main()
{
string path = "F:\\Speedlimit\\RGBCutImages10\\S825.jpg";
Mat SrcImage = imread(path);
if (!SrcImage.data) {
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
cv::Mat grayImage, OtsuImage, MaskImage;
cvtColor(SrcImage, grayImage, COLOR_BGR2GRAY);
threshold(grayImage, MaskImage, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
//直方圖引數定義
cv::Mat gray_hist;
const int histSize = 256;
float range[] = { 0, 256 };
const float* histRange[] = { range };
bool uniform = true, accumulate = false;
calcHist(&grayImage, 1, 0, MaskImage, gray_hist, 1, &histSize, histRange, uniform, accumulate);
//獲得歸一化影像直方圖
cv::Mat norm_gray_hist = cv::Mat::zeros(gray_hist.size(), gray_hist.type());
for (int i = 0; i < histSize; ++i)
norm_gray_hist.at<float>(i) = gray_hist.at<float>(i) / cv::countNonZero(MaskImage);
//全域均值和全域方差
Mat mat_mean, mat_stddev;
double gray_mean;
meanStdDev(grayImage, mat_mean, mat_stddev, MaskImage);
gray_mean = mat_mean.at<double>(0, 0);
//遍歷所有灰度,計算類間方差
std::vector<double>sigma_ks(histSize);
for (int k = 0; k < histSize; ++k)
{
double p1 = 0.0; //p1類發生概率
double m_k = 0.0; //高達k階累計平均灰度
for (int i = 0; i <= k; ++i)
{
p1 += norm_gray_hist.at<float>(i);
m_k += i * norm_gray_hist.at<float>(i);
}
if (p1 == 0.f || (1 - p1) == 0.f) //分母不能為0
sigma_ks[k] = 0.0;
else
sigma_ks[k] = (gray_mean * p1 - m_k) * (gray_mean * p1 - m_k) / (p1 * (1 - p1));
}
double max_Sigma_k = 0.0;
std::vector<int>maxval_Ts;
int Threshold_T = 0; //最終輸出的閾值T
//找類間方差最大值
for (int i = 0; i < sigma_ks.size(); ++i)
{
if (sigma_ks[i] > max_Sigma_k)
max_Sigma_k = sigma_ks[i];
}
//找極大值對應的所有灰度值
for (int i = 0; i < sigma_ks.size(); ++i)
{
if (max_Sigma_k == sigma_ks[i])
maxval_Ts.push_back(i);
}
//如果極大值點不唯一,那么取對應各個極大值的各個k的平均值來得到最終閾值threshold_T
for (int i = 0; i < maxval_Ts.size(); ++i)
Threshold_T += maxval_Ts[i];
Threshold_T = Threshold_T / maxval_Ts.size();
//輸出背景底色根據實際情況改為0或255
cv::Mat dstImage(grayImage.size(), grayImage.type(), cv::Scalar(255));
int width = grayImage.cols;
int height = grayImage.rows;
for (int i = 0; i < height; ++i)
{
for (int j = 0; j < width; ++j)
{
if (MaskImage.at<uchar>(i, j) == 255)
{
if (grayImage.at<uchar>(i, j) < Threshold_T)
dstImage.at<uchar>(i, j) = 0;
}
}
}
imshow("src", SrcImage);
cv::waitKey(0);
return 0;
}
另外一張測驗圖片:

代碼僅是一個小小功能的實作,測驗的影像數量不是很多,有時間可以好好封裝一下,后續不斷更新一些經典的傳統影像處理演算法功能實作和改進,如有小伙伴需要類似的測驗圖片素材,可以私聊我,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/346049.html
標籤:其他
上一篇:apache重寫:如何檢查查詢引數是否為空或不存在?
下一篇:力扣——對稱二叉樹
