直方圖原理就不說了,大家自行百度
直方圖可以幫助分析圖像中的灰度變化,進而幫助確定最優二值化的灰度閾值(threshold level)。如果物體與背景的灰度值對比明顯,此時灰度直方圖就會包含雙峰(bimodal histogram),即直方圖中一般會有兩個峰值,分別為圖像的前景和背景。
前景使得某個灰度區間的灰度值的數量急劇增加,就會產生一個峰值,同理背景會使另一個灰度區間的灰度值的數量急劇增加,就產生另外一個峰值,兩峰間的谷底對應于物體邊緣附近相對較少數目的像素點。
這兩個峰值之間的最小值一般就是最優二值化的分界點,通過這個分界點可以把前景和背景很好地分割開來。
有時這兩個峰值會有部分重疊,即左側峰值的下降部分和右側峰值的上升部分存在疊加。通常可以把自然界的信號看做高斯信號,即一個峰值對應一個高斯信號,當直方圖中的兩個高斯信號在某個灰度區域疊加的時候,其疊加區就形成了一個圓滑的谷底,就很難找到一個確切的位置(最優二值化的灰度值)把這兩個峰值分開。
?
float calculateThreshold(cv::Mat& img)
{cv::Mat temp = img.clone();// 計算直方圖cv::Mat hist;int histSize = 256; // 直方圖尺寸float range[] = { 0, 256 }; // 像素值范圍const float* ranges[] = { range };cv::calcHist(&img, 1, nullptr, cv::Mat(), hist, 1, &histSize, ranges);/*for (int i = 0; i < 21; i++)hist.at<float>(i, 0) = 0.0;*/cv::normalize(hist, hist, 0, 1, cv::NORM_MINMAX);//hist.convertTo(hist, CV_32S);cv::GaussianBlur(hist, hist, cv::Size(0, 0),3,3);//cv::blur(hist, hist, cv::Size(1, 9),cv::Point(-1,-1));std::vector<float> peaks; // 存儲峰值位置std::vector<float> valleys; // 存儲低谷位置for (int i = 1; i < histSize - 1; i++) {//std::cout << std::fixed << std::setprecision(4);float currentValue = hist.at<float>(i);float prevValue = hist.at<float>(i - 1);float nextValue = hist.at<float>(i + 1);/*if (currentValue < 0.001)continue;*/// 具體情況需要修改currentValue>0.005的閾值if ((currentValue > prevValue && currentValue > nextValue && currentValue>0.005)) {std::cout << prevValue << " " << currentValue << " " << nextValue << std::endl;peaks.push_back(i); // 峰值}else if (currentValue < prevValue && currentValue < nextValue && currentValue>0.001) {std::cout << prevValue << " " << currentValue << " " << nextValue << std::endl;valleys.push_back(i); // 低谷}}if(valleys.size()>0)cv::threshold(temp, temp, valleys[0], 255, cv::THRESH_BINARY);// 創建直方圖可視化圖像int histWidth = 512;int histHeight = 400;cv::Mat histImage(histHeight, histWidth, CV_8UC3, cv::Scalar(0, 0, 0));cv::Mat hist_temp;// 歸一化直方圖數據cv::normalize(hist, hist_temp, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat());// 繪制直方圖int binWidth = cvRound((double)histWidth / histSize);for (int i = 0; i < histSize; i++) {int binHeight = cvRound(hist_temp.at<float>(i));cv::line(histImage, cv::Point(i * binWidth, histHeight), cv::Point(i * binWidth, histHeight - binHeight), cv::Scalar(255, 255, 255));}if (valleys.size() > 0)return valleys[0];return 0;
}