灰度變換指對圖像的單個像素進行操做,主要以對比度和閾值處理爲目的。其變換形式以下:html
s=T(r)ios
其中,T是灰度變換函數;r是變換前的灰度;s是變換後的像素。
圖像灰度變換的有如下做用:c++
其中,a爲直線的斜率,b爲在y軸的截距。選擇不一樣的a,b值會有不一樣的效果:ide
OpenCV的實現以下:函數
灰度圖實現:post
for (int i = 0; i < srcImg.rows; i++) { uchar *srcData = srcImg.ptr<uchar>(i); for (int j = 0; j < srcImg.cols; j++) { dstImg.at<uchar>(i, j) = srcData[j] * k + b; } }
彩色圖的實現只需拓展到三通道便可:ui
for (int i = 0; i < RowsNum; i++) { for (int j = 0; j < ColsNum; j++) { //c爲遍歷圖像的三個通道 for (int c = 0; c < 3; c++) { //使用at操做符,防止越界 dstImg.at<Vec3b>(i, j)[c] = saturate_cast<uchar> (k* (srcImg.at<Vec3b>(i, j)[c]) + b); } } }
#include "stdafx.h" #include <iostream> #include <opencv2\core\core.hpp> #include <opencv2\highgui\highgui.hpp> #include <opencv2\imgproc\imgproc.hpp> using namespace std; using namespace cv; int main() { Mat srcImg = imread("rice.png",0); if (!srcImg.data) { cout << "讀入圖片失敗" << endl; return -1; } double k, b; cout << "請輸入k和b值:"; cin >> k >> b; int RowsNum = srcImg.rows; int ColsNum = srcImg.cols; Mat dstImg(srcImg.size(), srcImg.type()); //進行遍歷圖像像素,對每一個像素進行相應的線性變換 for (int i = 0; i < srcImg.rows; i++) { uchar *srcData = srcImg.ptr<uchar>(i); for (int j = 0; j < srcImg.cols; j++) { dstImg.at<uchar>(i, j) = srcData[j] * k + b; } } imshow("原圖", srcImg); imshow("線性變換後的圖像", dstImg); waitKey(); return 0; }
程序運行結果以下:url
對數變換的通用公式是:spa
s=log(1+r)/b
其中,b是一個常數,用來控制曲線的彎曲程度,其中,b越小越靠近y軸,b越大越靠近x軸。表達式中的r是原始圖像中的像素值,s是變換後的像素值,能夠分析出,當函數自變量較低時,曲線的斜率很大,而自變量較高時,曲線的斜率變得很小。 正是由於對數變.net
換具備這種壓縮數據的性質,使得它可以實現圖像灰度拓展和壓縮的功能。即對數變換能夠拓展低灰度值而壓縮高灰度級值,讓圖像的灰度分佈更加符合人眼的視覺特徵。
假設r≥0,根據上圖中的對數函數的曲線能夠看出:對數變換,將源圖像中範圍較窄的低灰度值映射到範圍較寬的灰度區間,同時將範圍較寬的高灰度值區間映射爲較窄的灰度區間,從而擴展了暗像的值,壓縮了高灰度的值,可以對圖像中低灰度細節進行增
強。;從函數曲線也能夠看出,反對數函數的曲線和對數的曲線是對稱的,在應用到圖像變換其結果是相反的,反對數變換的做用是壓縮灰度值較低的區間,擴展高灰度值的區間。
經過OpenCV實現程序有三種,這裏就不一一列舉了,在示例中給出:
#include "stdafx.h" #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream> using namespace cv; // 對數變換方法1 cv::Mat logTransform1(cv::Mat srcImage, int c) { // 輸入圖像判斷 if (srcImage.empty()) std::cout << "No data!" << std::endl; cv::Mat resultImage = cv::Mat::zeros(srcImage.size(), srcImage.type()); // 計算 1 + r cv::add(srcImage, cv::Scalar(1.0), srcImage); // 轉換爲32位浮點數 srcImage.convertTo(srcImage, CV_32F); // 計算 log(1 + r) log(srcImage, resultImage); resultImage = c * resultImage; // 歸一化處理 cv::normalize(resultImage, resultImage, 0, 255, cv::NORM_MINMAX); cv::convertScaleAbs(resultImage, resultImage); return resultImage; } // 對數變換方法2 cv::Mat logTransform2(Mat srcImage, float c) { // 輸入圖像判斷 if (srcImage.empty()) std::cout << "No data!" << std::endl; cv::Mat resultImage = cv::Mat::zeros(srcImage.size(), srcImage.type()); double gray = 0; // 圖像遍歷分別計算每一個像素點的對數變換 for (int i = 0; i < srcImage.rows; i++) { for (int j = 0; j < srcImage.cols; j++) { gray = (double)srcImage.at<uchar>(i, j); gray = c * log((double)(1 + gray)); resultImage.at<uchar>(i, j) = saturate_cast<uchar>(gray); } } // 歸一化處理 cv::normalize(resultImage, resultImage, 0, 255, cv::NORM_MINMAX); cv::convertScaleAbs(resultImage, resultImage); return resultImage; } // 對數變換方法3 cv::Mat logTransform3(Mat srcImage, float c) { // 輸入圖像判斷 if (srcImage.empty()) std::cout << "No data!" << std::endl; cv::Mat resultImage = cv::Mat::zeros(srcImage.size(), srcImage.type()); srcImage.convertTo(resultImage, CV_32F); resultImage = resultImage + 1; cv::log(resultImage, resultImage); resultImage = c * resultImage; cv::normalize(resultImage, resultImage, 0, 255, cv::NORM_MINMAX); cv::convertScaleAbs(resultImage, resultImage); return resultImage; } int main() { // 讀取灰度圖像及驗證 cv::Mat srcImage = cv::imread("111.jpg", 0); if (!srcImage.data) return -1; // 驗證三種不一樣方式的對數變換速度 cv::imshow("srcImage", srcImage); float c = 2; cv::Mat resultImage; double tTime; tTime = (double)getTickCount(); const int nTimes = 10; for (int i = 0; i < nTimes; i++) { resultImage = logTransform3(srcImage, c); } tTime = 1000 * ((double)getTickCount() - tTime) / getTickFrequency(); tTime /= nTimes; std::cout << "第三種方法耗時:" << tTime << std::endl; cv::imshow("resultImage", resultImage); cv::waitKey(0); return 0; }
三種方法運行效果分別以下:
基於冪次變換的Gamma校訂是圖像處理中一種很是重要的非線性變換,它與對數變換相反,它是對輸入圖像的灰度值進行指數變換,進而校訂亮度上的誤差。一般Gamma校訂長應用於拓展暗調的細節。伽馬變換的公式爲:
s=crγ
其中c和 γ爲正常數.,伽馬變換的效果與對數變換有點相似,當 γ >1時將較窄範圍的低灰度值映射爲較寬範圍的灰度值,同時將較寬範圍的高灰度值映射爲較窄範圍的灰度值;當 γ <1時,狀況相反,與反對數變換相似。其函數曲線以下:
當γ<1時,圖像的高光部分被擴展而暗調備份被壓縮,γ的值越小,對圖像低灰度值的擴展越明顯;當γ>1時,圖像的高光部分被壓縮而暗調部分被擴展,γ的值越大,對圖像高灰度值部分的擴展越明顯。這樣就可以顯示更多的圖像的低灰度或者高灰度細節。
總之,r<1的冪函數的做用是提升圖像暗區域中的對比度,而下降亮區域的對比度;r>1的冪函數的做用是提升圖像中亮區域的對比度,下降圖像中按區域的對比度。因此Gamma變換主要用於圖像的校訂,對於灰度級總體偏暗的圖像,可使用r<1的冪函數增大動態範圍。對於灰度級總體偏亮的圖像,可使用r>1的冪函數增大灰度動態範圍。
基於OpenCV的實現:
Mat GammaTrans(Mat &srcImag, float parameter) { //創建查表文件LUT unsigned char LUT[256]; for (int i = 0; i < 256; i++) { //Gamma變換定義 LUT[i] = saturate_cast<uchar>(pow((float)(i / 255.0), parameter)*255.0f); } Mat dstImage = srcImag.clone(); //輸入圖像爲單通道時,直接進行Gamma變換 if (srcImag.channels() == 1) { MatIterator_<uchar>iterator = dstImage.begin<uchar>(); MatIterator_<uchar>iteratorEnd = dstImage.end<uchar>(); for (; iterator != iteratorEnd; iterator++) *iterator = LUT[(*iterator)]; } else { //輸入通道爲3通道時,須要對每一個通道分別進行變換 MatIterator_<Vec3b>iterator = dstImage.begin<Vec3b>(); MatIterator_<Vec3b>iteratorEnd = dstImage.end<Vec3b>(); //經過查表進行轉換 for (; iterator!=iteratorEnd; iterator++) { (*iterator)[0] = LUT[((*iterator)[0])]; (*iterator)[1] = LUT[((*iterator)[1])]; (*iterator)[2] = LUT[((*iterator)[2])]; } } return dstImage; }
#include "stdafx.h" #include <opencv2/core/core.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/imgproc/types_c.h> #include <opencv2/highgui/highgui.hpp> #include <opencv2/highgui/highgui_c.h> #include <iostream> using namespace cv; using namespace std; void MyGammaCorrection(Mat& src, Mat& dst, float fGamma) { // build look up table unsigned char lut[256]; for (int i = 0; i < 256; i++) { lut[i] = saturate_cast<uchar>(pow((float)(i / 255.0), fGamma) * 255.0f); } dst = src.clone(); const int channels = dst.channels(); switch (channels) { case 1: //灰度圖的狀況 { MatIterator_<uchar> it, end; for (it = dst.begin<uchar>(), end = dst.end<uchar>(); it != end; it++) //*it = pow((float)(((*it))/255.0), fGamma) * 255.0; *it = lut[(*it)]; break; } case 3: //彩色圖的狀況 { MatIterator_<Vec3b> it, end; for (it = dst.begin<Vec3b>(), end = dst.end<Vec3b>(); it != end; it++) { //(*it)[0] = pow((float)(((*it)[0])/255.0), fGamma) * 255.0; //(*it)[1] = pow((float)(((*it)[1])/255.0), fGamma) * 255.0; //(*it)[2] = pow((float)(((*it)[2])/255.0), fGamma) * 255.0; (*it)[0] = lut[((*it)[0])]; (*it)[1] = lut[((*it)[1])]; (*it)[2] = lut[((*it)[2])]; } break; } } } int main() { Mat image = imread("111.jpg"); if (image.empty()) { cout << "Error: Could not load image" << endl; return 0; } Mat dst; float fGamma = 1 / 2.2; MyGammaCorrection(image, dst, fGamma); imshow("Source Image", image); imshow("Dst", dst); waitKey(); return 0; }
程序運行結果以下:
圖像的對比度拉伸是經過擴展圖像灰度級動態範圍來實現的,它能夠擴展對應的所有灰度範圍。圖像的低對比度通常是因爲圖像圖像成像亮度不夠、成像元器件參數限制或設置不當形成的。提升圖像的對比度能夠加強圖像各個區域的對比效果,對圖像中感興趣的區域進行加強,而對圖像中不感興趣的區域進行相應的抑制做用。對比度拉伸是圖像加強中的重要的技術之一。這裏設點(x1,y1)與(x2,y2)是分段線性函數中折點位置座標。常見的三段式分段線性變換函數的公式以下:
其中
其圖像以下:
須要注意的是,分段線性通常要求函數是單調遞增的,目的是防止圖像中的灰度級不知足一一映射。
#include "stdafx.h" #include <iostream> #include <opencv2\core\core.hpp> #include <opencv2\highgui\highgui.hpp> #include <opencv2\imgproc\imgproc.hpp> using namespace cv; using namespace std; int main() { Mat srcImage = imread("111.jpg", 0); if (!srcImage.data) { cout << "讀入圖片錯誤!" << endl; return -1; } imshow("原始圖片", srcImage); Mat dstImage(srcImage); int rowsNum = dstImage.rows; int colsNum = dstImage.cols; //圖像連續性判斷 if (dstImage.isContinuous()) { colsNum = colsNum * rowsNum; rowsNum = 1; } //圖像指針操做 uchar *pDataMat; int pixMax = 0, pixMin = 255; //計算圖像像素的最大值和最小值 for (int j = 0; j < rowsNum; j++) { pDataMat = dstImage.ptr<uchar>(j); for (int i = 0; i < colsNum; i++) { if (pDataMat[i] > pixMax) pixMax = pDataMat[i]; if (pDataMat[i] < pixMin) pixMin = pDataMat[i]; } } //進行對比度拉伸 for (int j = 0; j < rowsNum; j++) { pDataMat = dstImage.ptr<uchar>(j); for (int i = 0; i < colsNum; i++) { pDataMat[i] = (pDataMat[i] - pixMin) * 255 / (pixMax - pixMin); } } imshow("對比度拉伸後的圖像", dstImage); waitKey(); return 0; }
灰度級分層的處理能夠突出特定灰度範圍的亮度,能夠應用於加強某些特徵。
下面使用OpenCV對灰度級分層進行一個實現
#include "stdafx.h" #include <iostream> #include <opencv2\core\core.hpp> #include <opencv2\highgui\highgui.hpp> #include <opencv2\imgproc\imgproc.hpp> using namespace cv; using namespace std; int main() { Mat srcImage = imread("111.jpg", 0); if (!srcImage.data) { cout << "讀入圖片錯誤!" << endl; return 0; } imshow("原圖像", srcImage); Mat dstImage = srcImage.clone(); int rowsNum = dstImage.rows; int colsNum = dstImage.cols; //圖像連續性判斷 if (dstImage.isContinuous()) { colsNum *= rowsNum; rowsNum = 1; } //圖像指針操做 uchar *pDataMat; int controlMin = 50; int controlMax = 150; //計算圖像的灰度級分層 for (int j = 0; j < rowsNum; j++) { pDataMat = dstImage.ptr<uchar>(j); for (int i = 0; i < colsNum; i++) { //第一種方法,二值映射 if (pDataMat[i] > controlMin) pDataMat[i] = 255; else pDataMat[i] = 0; //第二種方法:區域映射 //if (pDataMat[i] > controlMax && pDataMat[j] < controlMin) // pDataMat[i] = controlMax; } } imshow("灰度分層後的圖像", dstImage); waitKey(); return 0; }
運行結果以下所示:
像素是由比特組成的豎直。例如,在256級灰度圖像中,每一個像素的灰度是由8bit組成,替代突出灰度級範圍,咱們能夠突出比特來突出整個圖像的外觀。一副8比特灰度圖可考慮分層1到8個比特平面。很容易理解的是,4個高階比特平面,特別是最後兩個比特平面,包含了在視覺上很重要的大多數數據。而低階比特平面則在圖像上貢獻了更精細的灰度細節。
#include "stdafx.h" #include<opencv2/highgui/highgui.hpp> #include<opencv2/imgproc/imgproc.hpp> #include<iostream> using namespace std; using namespace cv; int b[8]; void binary(int num) { for (int i = 0; i < 8; i++) b[i] = 0; int i = 0; while (num != 0) { b[i] = num % 2; num = num / 2; i++; } } int main() { Mat srcImage = imread("111.jpg", 0); resize(srcImage, srcImage, cv::Size(), 0.5, 0.5); Mat d[8]; for (int k = 0; k < 8; k++) d[k].create(srcImage.size(), CV_8UC1); int rowNumber = srcImage.rows, colNumber = srcImage.cols; for (int i = 0; i < rowNumber; i++) for (int j = 0; j < colNumber; j++) { int num = srcImage.at<uchar>(i, j); binary(num); for (int k = 0; k < 8; k++) d[k].at<uchar>(i, j) = b[k] * 255; } imshow("src", srcImage); for (int k = 0; k < 8; k++) { imshow("level" + std::to_string(k), d[k]); } waitKey(0); return 0; }