最も包括的な要約: 12 の画像補正方法
1. コントラストと明るさの向上
使用シナリオ: 暗い (黒) 背景上の明るいスポット (白い点) の検出
詳細については、
コントラスト明るさ画像の強調と ConvertTo の詳細な説明を参照してください。
達成:
void adjust(const cv::Mat &src, cv::Mat &dst, const float brightness, const float contrast)
{
double B = brightness / 255.;
double c = contrast / 255.;
double k = tan((45 + 44 * c) / 180 * M_PI);
double alpha = k;
double beta = 127.5 * (1 + B) - k * 127.5 * (1 - B);
src.convertTo(dst, -1, alpha, beta);
}
2. ヒストグラムの等化
簡単に言えば、現時点では利用できません。
ヒストグラム イコライゼーションはイメージ全体をマッピングし、一部の領域を局所的にマッピングしないため、一部の領域が暗いまたは明るいイメージには適していません。同時に、ヒストグラム等化後の画像のグレー レベルが低下し、画像の一部の細部が失われます。
全体的に暗い画像や明るい画像に適しています。画像全体のグレー値をダイナミック レンジ [0,255] 全体内で均等に分散させることができるため、画像のコントラストが向上します。
1. カスタム累積周波数等化方法:
手順:
1. 画像内の各グレー値ピクセルの数を数えます。
2. 各グレー値ピクセルの頻度を計算し、累積頻度を計算します。
3. 画像をマッピングします。画像のグレー値 = 画像の元のグレー値です。画像 ※累積度数
単一チャンネル:
bool MyEqualizeHist(Mat gray, Mat & result)
{
//统计0~255像素值的个数
map<int, int>mp;
for (int i = 0; i < gray.rows; i++)
{
uchar* ptr = gray.data + i * gray.cols;
for (int j = 0; j < gray.cols; j++)
{
int value = ptr[j];
mp[value]++;
}
}
//统计0~255像素值的频率,并计算累计频率
map<int, double> valuePro;
int sum = gray.cols*gray.rows;
double sumPro = 0;
for (int i = 0; i < 256; i++)
{
sumPro += 1.0*mp[i] / sum;
valuePro[i] = sumPro;
}
//根据累计频率进行转换
for (int i = 0; i < gray.rows; i++)
{
uchar* ptr1 = gray.data + i*gray.cols;
for (int j = 0; j < gray.cols; j++) {
int value = ptr1[j];
double p = valuePro[value];
result.at<uchar>(i, j) = p*value;
}
}
return true;
}
マルチチャンネル (RGB):
void MyEqualizeHistRgb(Mat& image)
{
Mat imageRGB[3];
split(image, imageRGB);
for (int i = 0; i < 3; i++)
{
MyEqualizeHist(imageRGB[i], imageRGB[i]);
}
merge(imageRGB, 3, image);
imshow("result", image);
}
2. opencv に付属する equalizeHist()
qualizeHist(Mat src, Mat dst);
ステップ:
1. 入力画像 (8 ビット) のヒストグラム H を計算します;
2. ヒストグラム ビンの合計が 255 になるようにヒストグラム H を正規化します;
3. ヒストグラムの積分 (ヒストグラムの積分) を計算します; H'i =∑ 0≤j<i H(j)
4. H' をルックアップ テーブル (look-uptable) として使用して画像を変換します。具体的なピクセル値の変換式は次のとおりです。 dst(x,y)=H"( src(x,y))
このアルゴリズムは明るさを正規化し、画像のコントラストを高めます。
結果はカスタムのものとは異なります。
3. 適応型局所ヒストグラム等化
ローカルヒストグラム処理:
(1) 一定サイズのテンプレート(近傍長方形)を設定し、画像内をピクセル単位で移動する (
2) ピクセル位置ごとに、テンプレート領域のヒストグラムを計算し、ヒストグラム均等化または局所領域のヒストグラムを実行する画像マッチング変換。変換結果は、テンプレート領域の中心にあるピクセルのグレー値を修正するためにのみ使用されます。(3)
テンプレート (近傍) は、画像内で行ごとに移動し、すべてのピクセルを横断して、画像全体の局所ヒストグラム図形処理。
C++ コード:
//C++
cvtColor(img,gray,COLOR_BGR2GRAY);
Ptr<CLAHE> clahe = createCLAHE();
clahe->apply(gray, dst);
Pythonコード:
//python
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(4,4)) # 创建 CLAHE 对象
imgLocalEqu = clahe.apply(img) # 自适应的局部直方图均衡化
ClipLimit: カラー コントラストのしきい値、オプション、デフォルト値 8
titleGridSize: 部分ヒストグラム イコライゼーションのテンプレート (近傍) サイズ、オプション、デフォルト値 (8,8)
3. 指数変換の強化
指数変換 (べき乗則) の公式: S=c*R^r、c と r を合理的に選択することでグレー スケール範囲を圧縮でき、アルゴリズムは c=1.0/255.0、r=2 で実現されます。
暗い写真でテストしたところ、実際の効果はさらに暗くなります。この定型的な推論と一致して、実際のピクセル値はルックアップ テーブルの小さい方の値に置き換えられます。
カスタム指数関数的ブースティング アルゴリズム:
void Enhance::ExpEnhance(IplImage* img, IplImage* dst)
{
// 由于oldPixel:[1,256],则可以先保存一个查找表
uchar lut[256] ={
0};
double temp = 1.0/255.0;
for ( int i =0; i<255; i++)
{
lut[i] = (uchar)(temp*i*i+0.5);
}
for( int row =0; row <img->height; row++)
{
uchar *data = (uchar*)img->imageData+ row* img->widthStep;
uchar *dstData = (uchar*)dst->imageData+ row* dst->widthStep;
for ( int col = 0; col<img->width; col++)
{
for( int k=0; k<img->nChannels; k++)
{
uchar t1 = data[col*img->nChannels+k];
dstData[col*img->nChannels+k] = lut[t1];
}
}
}
}
// OpenCV2.1版本之前使用IplImage*数据结构来表示图像,2.1之后的版本使用图像容器Mat来存储。
// Mat dstImage = cvarrToMat(&dst1); // 将IplImage格式转换为Mat格式
// IplImage dst1= (IplImage)(dst);// 将Mat类型的图片转换为IplImage
// 以下为原创使用Mat的版本。
void Enhance::ExpEnhance(Mat & img,double c, int r)
{
if(img.channels() != 1)
{
cvtColor(img,img,COLOR_BGR2GRAY);
}
uchar lut[256] ={
0};
for ( int i =0; i<255; i++)
{
lut[i] = (uchar)( c * pow(i,r) +0.5);
cout << c*i*i+0.5<<endl;
}
for(int i = 0;i < img.rows; i++)
{
for(int j = 0;j < img.cols; j++)
{
int pv = img.at<uchar>(i,j);
img.at<uchar>(i,j) = lut[pv];
}
}
}
注: 一部のブログでは e の n 乗を計算するために expf(n) を使用していますが、これは間違っていると思います。expf(100) はすでに inf 無限です。
srcmat.at<uchar>(i, j) = (unsigned char)(255.0 *(expf(srcmat.at<uchar>(i, j) - low_bound) / expf(up_bound - low_bound)));
4番目、ガンマ強調
特別な指数関数的強調、べき乗変換に基づくガンマ補正は、画像処理において非常に重要な非線形変換であり、対数変換の逆であり、入力画像のグレー値を指数関数的に変換し、輝度差を補正します。
通常、ガンマ補正は暗い色調の細部を拡大するために適用されます。一般に、ガンマ補正の値が 1 より大きい場合、画像のハイライト部分は圧縮され、シャドウ部分は拡大され、ガンマ補正の値が 1 より小さい場合、画像のハイライト部分は逆に拡大されます。は展開され、シャドウ バックアップは圧縮されます。
出力 = L^γ γ
が 1 未満の場合、低階調範囲が伸張され、高階調範囲が圧縮され、
γ が 1 より大きい場合、低階調範囲が圧縮され、高階調範囲が伸張されます。
γ が 1 に等しい場合、恒等変換に簡略化されます。
1. 三次強化を修正:
void Enhance::gammaEhance(Mat& image)
{
Mat imageGamma(image.size(), CV_32FC3);
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
imageGamma.at<Vec3f>(i, j)[0] = (image.at<Vec3b>(i, j)[0])*(image.at<Vec3b>(i, j)[0])*(image.at<Vec3b>(i, j)[0]);
imageGamma.at<Vec3f>(i, j)[1] = (image.at<Vec3b>(i, j)[1])*(image.at<Vec3b>(i, j)[1])*(image.at<Vec3b>(i, j)[1]);
imageGamma.at<Vec3f>(i, j)[2] = (image.at<Vec3b>(i, j)[2])*(image.at<Vec3b>(i, j)[2])*(image.at<Vec3b>(i, j)[2]);
}
}
//归一化到0~255
normalize(imageGamma, imageGamma, 0, 255, CV_MINMAX);
//转换成8bit图像显示
convertScaleAbs(imageGamma, imageGamma);
imshow("gamma三次方增强", imageGamma);
}
2. カスタム係数の拡張:
Mat Enhance::gammaWithParameter(Mat &img, 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 = img.clone();
//输入图像为单通道时,直接进行Gamma变换
if (img.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;
}
5、ログ変換の強化
効率的。透明オブジェクトのぼやけた境界強調をテストしたところ、効果は良好でした。
対数変換により、低いグレースケール値を拡張し、高いグレースケール値を圧縮できるため、画像のグレースケール分布が人間の目の視覚特性により一致します。
適切な処理の後、元の画像の低グレー領域のコントラストが増加し、暗い部分の詳細が強調されます。
対数変換式は次のとおりです。
y = log(1+x)/b
このうち、bは曲線の曲率を制御するための定数であり、bが小さいほどy軸に近づき、bが大きいほどx軸に近づく。式の x は元の画像のピクセル値、y は変換されたピクセル値です。
達成:
void logEhance(Mat& image)
{
Mat imageLog(image.size(), CV_32FC3);
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
imageLog.at<Vec3f>(i, j)[0] = log(1 + image.at<Vec3b>(i, j)[0]);
imageLog.at<Vec3f>(i, j)[1] = log(1 + image.at<Vec3b>(i, j)[1]);
imageLog.at<Vec3f>(i, j)[2] = log(1 + image.at<Vec3b>(i, j)[2]);
}
}
//归一化到0~255
normalize(imageLog, imageLog, 0, 255, CV_MINMAX);
//转换成8bit图像显示
convertScaleAbs(imageLog, image);
//imshow("Soure", image);
imshow("Log", image);
}
6、laplaceEhance強化
シャープネス効果があり、ノイズに敏感なので、最初に滑らかにする必要があります。
ドロップモデルにフィットするため、拡散効果によるボケの改善に特に効果的です。拡散効果は、イメージング中によく発生する現象です。
ラプラシアン演算子は、二次導関数としてノイズに対して許容できないほど敏感であると同時に、その大きさによってエッジが生成され、複雑なセグメンテーションには望ましくないため、通常、ラプラシアン演算子は元の形式ではエッジ検出には使用されません。結果として、最終的にラプラシアン演算子はエッジの方向を検出できなくなります。
達成:
void Enhance::laplaceEhance(Mat& image)
{
Mat imageEnhance;
Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0, 0, 5, 0, 0, -1, 0);
//多种卷积核可选。
//Mat kernel = (Mat_<float>(3, 3) << 0, 1, 0, 1, -4, 1, 0, 1, 0);
//Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);
//Mat kernel = (Mat_<float>(3, 3) << 0, 1, 0, 1, 3, 1, 0, 1, 0);
//Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
filter2D(image, imageEnhance, CV_8UC3, kernel);
imshow("laplaceEhance", imageEnhance);
}
7、線形変換:
何も言うことはありません。線形変換を直接実行してください。
y = kx+b;
達成:
Mat Enhance::linearTransformation(Mat img, const float k, const float b)
{
Mat dst = img.clone();
for(int i=0; i<img.rows; i++)
{
for(int j=0; j<img.cols; j++)
{
for(int c=0; c<3; c++)
{
float x =img.at<Vec3b>(i, j)[c];
dst.at<Vec3b>(i, j)[c] = saturate_cast<uchar>(k* x + b); }
}
}
return dst;
}
8、区分的線形ストレッチング アルゴリズム:
画像のグレースケール変換で一般的に使用されるアルゴリズムは、市販の画像編集ソフトウェア Photoshop にも対応する機能があります。セグメント化された線形ストレッチは、主に画像のコントラストを改善し、画像の詳細を強調するために使用されます。
カスタムで強化され、弱められたピクセル間隔:
void Enhance::PiecewiseLinearTrans(cv::Mat& matInput, float x1, float x2, float y1, float y2)
{
//计算直线参数
//L1
float K1 = y1 / x1;
//L2
float K2 = (y2 - y1) / (x2 - x1);
float C2 = y1 - K2 * x1;
//L3
float K3 = (255.0f - y2) / (255.0f - x2);
float C3 = 255.0f - K3 * 255.0f;
//建立查询表
uchar LUT[256] ={
0};
for (int m = 0; m < 256; m++)
{
if (m < x1)
{
LUT[m] = m * K1;
}
else if (m > x2)
{
LUT[m] = m * K3 + C3;
}
else
{
LUT[m] = m * K2 + C2;
}
}
//灰度映射
for (int j = 0; j < matInput.rows; j++)
{
for (int i = 0; i < matInput.cols; i++)
{
//查表gamma变换
int x = matInput.at<uchar>(j,i);
matInput.at<uchar>(j,i) = LUT[x];
}
}
}
9、グレーレベルのレイヤリング
最も単純な例は、一般的に使用される opencv バイナリ化アルゴリズムです。thresh と inRange は両方とも使用できます。
ここでは自分で推論を行うことができます。
10. 露出オーバーで画像が反転する
文字通り: 値に対して 255-x 演算を実行するだけです。
達成:
void ExporeOver(IplImage* img, IplImage* dst)
{
for( int row =0; row height; row++)
{
uchar *data = (uchar*)img->imageData+ row* img->widthStep;
uchar *dstData = (uchar*)dst->imageData+ row* dst->widthStep;
for ( int col = 0; colwidth; col++)
{
for( int k=0; knChannels; k++)
{
uchar t1 = data[col*img->nChannels+k];
uchar t2 = 255 - t1;
dstData[col*img->nChannels+k] = min(t1,t2);
}
}
}
}
11. 高いコントラスト保持率
ハイコントラスト保存とは、主に明暗のコントラストが大きい画像の 2 つの部分の接合部分を保存することです。たとえば、画像内に人物と石がある場合、石の輪郭と輪郭が保存されます。人物の輪郭や顔、衣服などの輪郭がはっきりしている部分は保存され、明暗の変化がはっきりしないその他の広い部分はミディアムグレーになります。
その表現形式は次のとおりです。
dst = r*(img – ブラー(img))。
達成:
Mat HighPass(Mat img)
{
Mat temp;
GaussianBlur(img, temp,Size(7,7),1.6,1.6);
int r=3;
Mat diff = img + r*(img-temp); //高反差保留算法
return diff;
}
12.Masic アルゴリズム (モザイク)
日常生活では、機密保持などの理由で画像にモザイクをかける必要がある場合があります。次のアルゴリズムにより画像モザイク機能が実現されます (原理: 中心ピクセルを使用して隣接ピクセルを表す)。
uchar getPixel( IplImage* img, int row, int col, int k)
{
return ((uchar*)img->imageData + row* img->widthStep)[col*img->nChannels +k];
}
void setPixel( IplImage* img, int row, int col, int k, uchar val)
{
((uchar*)img->imageData + row* img->widthStep)[col*img->nChannels +k] = val;
}
// nSize:为尺寸大小,奇数
// 将邻域的值用中心像素的值替换
void Masic(IplImage* img, IplImage* dst, int nSize)
{
int offset = (nSize-1)/2;
for ( int row = offset; row <img->height - offset; row= row+offset)
{
for( int col= offset; col<img->width - offset; col = col+offset)
{
int val0 = getPixel(img, row, col, 0);
int val1 = getPixel(img, row, col, 1);
int val2 = getPixel(img, row, col, 2);
for ( int m= -offset; m<offset; m++)
{
for ( int n=-offset; n<offset; n++)
{
setPixel(dst, row+m, col+n, 0, val0);
setPixel(dst, row+m, col+n, 1, val1);
setPixel(dst, row+m, col+n, 2, val2);
}
}
}
}
}
13. まとめ
実際の戦闘では上記の方法が単独で使用されない場合もあり、フィルタリングやさまざまな方法を組み合わせることでより良い結果が得られることがよくあります。