关于灰度拉伸的一点思考
? ? ? ? 最近在做項目,涉及到車道的檢測。由于在日光下,原本就是白色或者是黃色的車道線會比較得很不清晰,于是很自然的想到了灰度拉伸的方案,從少量數據來看的結果,是好的。以下圖為例,左圖是三通道圖,中間是利用opencv的cvtColor函數轉換的灰度圖,可以看到在右側日光照射下的車道顯得很不清晰,所以對高灰度值區域進行灰度拉伸,可以得到有圖,顯然車道清晰了很多。雖然由此左側的車道沒有原本那么明顯了,但也還在可以接受的范圍。
? ? ? ? ?然后今天在探索能否實現自適應的拉伸方案時,用了自己手機拍攝的一張圖片觀察處理前后的灰度直方圖,發現了一些特殊的情況。繪制灰度直方圖和實現灰度拉伸的函數如下。
void GrayLinearTransform(Mat input, Mat &output, double x1 = 160, double y1 = 120, double x2 = 220, double y2 = 240); void CheckGrayHist(Mat img) {if (img.channels() == 3) {/*三通道圖像,暫時不符合我們單通道灰度直方的需求.*/cout << "Error" << endl;}else {/*圖像是單通道的*///Mat gray;//cvtColor(img, gray, COLOR_BGR2GRAY);int histsize = 256;float range[] = { 0, 256 };const float * histrange(range);//存放直方圖的計算結果,是一個256*1的矩陣Mat gray_hist;calcHist(&img, 1, { 0 }, Mat(), gray_hist, 1, &histsize, &histrange, true, false);//cout << gray_hist.cols << "," << gray_hist.rows << endl;//定義每個灰度值在圖上的寬度int bin_w = 4;int histImageHeight = 500;int histImageWidth = bin_w*histsize;//畫出來是1024x500的圖片,定義的時候是先行后列,與后續讀取數值是不同的!!Mat histImage(histImageHeight, histImageWidth, CV_8UC3, Scalar(0, 0, 0));//對結果進行歸一化,才可以繪制到圖像中normalize(gray_hist, gray_hist, 0, histImageHeight, NORM_MINMAX, -1, Mat());for (int i = 1; i < histsize; i++){//繪制直方圖,用折線圖來替代通常的直方圖,意義是一致的line(histImage, Point(bin_w *(i - 1), histImageHeight - cvRound(gray_hist.at<float>(i - 1))),Point(bin_w * i, histImageHeight - cvRound(gray_hist.at<float>(i))), Scalar(255, 255, 255), 2);}//打印圖片namedWindow(OUTPUT_TITLE, WINDOW_NORMAL);imshow(OUTPUT_TITLE, histImage);//imwrite("hist01.jpg", histImage);waitKey(0);destroyAllWindows();} } void GrayLinearTransform(Mat input, Mat & output, double x1, double y1, double x2, double y2) {/*灰度拉伸。經過幾次試驗發現,車道線無論是白色還是黃色,總會是圖像中的高亮區域,無論白天或者是有路燈照射的晚上,所以可以把拉伸區域定在高灰度值領域。我們將通過兩個點(x1, y1), (x2, y2),其中 255 > x2 > x1, 255 > y2 > y1來確定我們的分段函數的新形式。| y1/x1*old_gray_value, if old_gray_value <= x1new_gray_value = | (y2 - y1)/(x2 - x1)*(old_gray_value - x1) + y1, if x1 < old_gray_value <= x2| (255 - y2)/(255 - x2)*(old_gray_value - x2) + y2, if x2 < old_gray_value*/for (int col = 0; col < input.cols; ++col){for (int row = 0; row < input.rows; ++row){//ogv = old gray value, 即原圖的灰度值double ogv = input.at<uchar>(col, row);if (ogv < x1) {output.at<uchar>(col, row) = static_cast<uchar>(y1 / x1 * ogv);}else if (x1 <= ogv && ogv < x2) {output.at<uchar>(col, row) = static_cast<uchar>((y2 - y1) / (x2 - x1)*(ogv - x1) + y1);}else {output.at<uchar>(col, row) = static_cast<uchar>((255 - y2) / (255 - x2)*(ogv - x2) + y2); }}} }? ? ? ?? ? ? ?將前后兩張直方圖進行比較如上。我設置的劃分多段函數的兩個點分別是(160, 120),(220, 240),但從圖片上看也確實可以看出把圖片的高亮區域給拉伸得更加清晰了,但是直方圖上卻出現了很多奇怪的波形。雖然說灰度直方圖,并不是完全連續的,沒有必須是如第一張圖那么連續的道理,但是通常來說這種很嚴重的波動必然是人為干預引起的。
? ? ? ? 原因也很顯然,因為在做灰度拉伸時,是對灰度值帶入到函數中,計算的結果基本都不是一個整數,所以需要取整。例如我們設置的兩個點是(160, 120),(220, 240),就意味著把0-160的數值壓縮到0-120,簡單的就是0-4壓縮到0-3。[0, 1, 2, 3, 4] ---> [0, 0.75, 1.5, 2.25, 3],如果是簡單的向下取整,那么結果是[0, 0, 1, 2, 3],也就是把1的所有值都加到0中去,所以原本連續的0-4就變得不連續了,變得有一個明顯的突起。那如果是把小的范圍拉伸到大的范圍呢?160-220拉伸到120-240,也就是原本60的跨度變成了120,例如160 --> 120, 161 ---> 122,所以121這個位置就空了出來,也就出現了上圖中有一段有一半的點為0的區域,正是對應了120-240中的奇數區域。
? ? ? ?說不會有什么影響,這個似乎倒不至于。對于肉眼所看到的圖像并沒有什么特別的邊緣或者干擾,興許有人會說那不要截斷用cvRound有沒有用?并沒有哈哈哈,因為實質上都是把某個灰度值的所有點都給調整到另一個灰度值,只要是這種"全部"的操作,就會對灰度直方圖有如上的影響。不過這種圖像結果,實際上更多是因為我畫圖,并不是真正的直方圖,而是將一個一個的點連接起來的折線圖。如果畫成直方圖的形式應該就好了。
/* for (int i = 1; i < histsize; i++) { line(histImage, Point(bin_w *(i - 1), histImageHeight - cvRound(gray_hist.at<float>(i - 1))),Point(bin_w * i, histImageHeight - cvRound(gray_hist.at<float>(i))), Scalar(255, 255, 255), 2); } */ for (int i = 0; i < histsize; i++) {//繪制直方圖line(histImage, Point(bin_w * i, histImageHeight),Point(bin_w * i, histImageHeight - cvRound(gray_hist.at<float>(i))), Scalar(255, 255, 255), 2); }? ? ? ??
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的关于灰度拉伸的一点思考的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CP2102N高度集成USB全速带电池充
- 下一篇: Linux复制文件夹