關于邊緣改進全局門檻值處理基本實作是:
1.先計算其邊界,利用拉普拉斯或者梯度變換都以。
2.計算變化後邊界圖像的絕對值
3.指定一個門檻值(一般以百分比的形式指定,比如我指定90%,如果存在有灰階k,灰階小于K的像素總數占全部像素的90%,那麼K就是我們要求的灰階)
4.對2中計算完的圖像進行門檻值變換,轉化為2值圖像
5.用4中計算得到的二值圖像乘原始圖像。
6.計算5中計算的到的圖像中灰階大于0的直方圖。
7.對6中得到的直方圖進行全局分隔。
8.提出全局分隔出來的灰階,用該灰階對原始圖檔進行門檻值分割,即可得到結果。
c#算法實作
主函數:
/// <summary>
/// 邊界修飾自适應門檻值變換
/// </summary>
/// <param name="image">輸入圖像</param>
/// <param name="percent">百分比</param>
/// <returns></returns>
public static Mat EdgeModifyOtsu(Mat image,double percent=0.99)
{
Mat _m = new Mat();//拉普拉斯變換後的圖像
Mat _m1 = new Mat();
Mat _Rmat = new Mat();//要傳回的圖像
//拉普拉斯變換
CvInvoke.Laplacian(image, _m, Emgu.CV.CvEnum.DepthType.Cv16S,3);
//取絕對值
_m1 = AbsCv16s(_m);
//門檻值變換
double max = PercentGary(_m1, percent);
CvInvoke.Threshold(_m1, _m1, max, 1, Emgu.CV.CvEnum.ThresholdType.Binary);
//轉換為8位元組
_m1 = Cv16sTo8u(_m1);
//與原圖像相乘
CvInvoke.Multiply(_m1, image, _m1, 1, Emgu.CV.CvEnum.DepthType.Cv8U);
//尋找相乘後的圖像灰階大于0的直方圖的自适應門檻值
int _K = OtsuThreshold(_m1, 1);
CvInvoke.Threshold(image, _Rmat, _K, 255, Emgu.CV.CvEnum.ThresholdType.Binary);
return _Rmat;
}
取絕對值函數:
/// <summary>
/// 将16位像素的灰階值絕對值化
/// </summary>
/// <param name="image">圖檔</param>
/// <returns>轉換好的圖檔</returns>
public static Mat AbsCv16s(Mat image)
{
Mat _m_ = new Mat(image.Size, Emgu.CV.CvEnum.DepthType.Cv16S, 1);
unsafe
{
Int16* dataImage = (Int16*)image.DataPointer.ToPointer();
Int16* data_m_ = (Int16*)_m_.DataPointer.ToPointer();
for (int row = 0; row < image.Height; row++)
{
//data = data + row * image.Cols;
for (int col = 0; col < image.Width; col++)
{
Int16 _ii = *dataImage;
* data_m_ = Math.Abs(_ii);
dataImage++;
data_m_++;
}
}
}
return _m_;
}
用百分比計算分割函數:
/// <summary>
/// 計算圖檔百分比像素的灰階值
/// </summary>
/// <param name="image">輸入圖檔</param>
/// <param name="percent">百分比像素</param>
/// <returns>傳回灰階值</returns>
public static double PercentGary(Mat image, double percent)
{
long[] _his = Histogram(image, 0,16);
long _count = 0;//表示在k中存在的像素
for (int _index = 0; _index < Math.Pow(2,16); _index++)//若所有像素都在k内,就将其方差置為0
{
_count += _his[_index];
if ((double)_count / image.Total.ToInt64() > percent)
{
return _index;
}
}
return Math.Pow(2, 16);
}
将16位有符号二值圖像轉換為8位無符二值圖像函數:
/// <summary>
/// 将16有符号位二值圖轉化為8位
/// </summary>
/// <param name="image">圖檔</param>
/// <returns>傳回轉換好的圖檔</returns>
public static Mat Cv16sTo8u(Mat image)
{
Mat _m_ = new Mat(image.Size, Emgu.CV.CvEnum.DepthType.Cv8U, 1);
unsafe
{
Int16* dataImage = (Int16*)image.DataPointer.ToPointer();
byte* data_m_ = (byte*)_m_.DataPointer.ToPointer();
for (int row = 0; row < image.Height; row++)
{
//data = data + row * image.Cols;
for (int col = 0; col < image.Width; col++)
{
*data_m_ = Convert.ToByte( Math.Abs(*dataImage));
dataImage++;
data_m_++;
}
}
}
return _m_;
}
尋找相乘後的圖像灰階大于0的直方圖的自适應門檻值函數:
/// <summary>
/// 尋找圖檔大于d值像素集的最适應門檻值
/// </summary>
/// <param name="image">輸入的圖檔</param>
/// <param name="d"></param>
/// <returns>傳回最合适門檻值</returns>
public static int OtsuThreshold(Mat image, int d)
{
long[] his = Histogram(image,8,d);
float _PK;
float _MK;//第k級累加灰階均值;
float _MG = 0;//整個圖檔的灰階均值
long _MN = 0;//圖檔的像素數目
float[] _Ks = new float[256];//存儲類值最大方差
float _Max;//類間最大方差
List<int> _MaxKs = new List<int>();//存儲使類間方差最大的多個K值;
for (int _index = 0; _index < his.Length; _index++)//計算直方圖中像素的總數
{
_MN += his[_index];
}
for (int i = 0; i < 256; i++)//計算圖檔平均灰階值
{
_MG += (float)(i * (double)his[i] / _MN);
}
for (int k = 0; k < 256; k++)//計算 圖檔在不同K的類值最大方差
{
long _count = 0;//表示在k中存在的像素
for (int _index = 0; _index <= k; _index++)//若所有像素都在k内,就将其方差置為0
{
_count += his[_index];
}
if (_count == _MN)
{
_Ks[k] = 0;
continue;
}
else if (_count == 0)
{
_Ks[k] = 0;
continue;
}
_PK = (float)((double)_count / _MN);
_MK = 0;
for (int i = d; i <= k; i++)
{
float p = (float)((double)his[i] / _MN);
//_PK += p;
_MK += i * p;
}
_Ks[k] = (float)Math.Pow(_MG * _PK - _MK, 2) / (_PK * (1 - _PK));
}
_Ks[0] = 0;
_Max = _Ks.Max();
for (int i = 0; i < 256; i++)
{
if (_Ks[i] == _Max)
_MaxKs.Add(i);
}
int _K = (int)_MaxKs.Average();
return _K;
}
計算直方圖函數:
/// <summary>
/// 圖檔灰階直方圖計算
/// </summary>
/// <param name="image">圖檔</param>
/// <param name="depth">深度</param>
/// <param name="d">要跳過的灰階</param>
/// <returns></returns>
public static long[] Histogram(Mat image, int depth= 8,int d = 0)
{
if (image.NumberOfChannels != 1)
{
throw new Exception("通道必須為1");
}
//提取直方圖------------------------------------
long[] _his = new long[(int) Math.Pow(2,depth)];
for (int i = d; i < (int)Math.Pow(2, depth); i++)
{
_his[i] = 0;
}
unsafe
{
byte* data = (byte*)image.DataPointer.ToPointer();
for (int row = 0; row < image.Height; row++)
{
//data = data + row * image.Cols;
for (int col = 0; col < image.Width; col++)
{
if (*data >= d)
{
_his[*data]++;
}
data++;
}
}
}
return _his;
}
這是基于emgucv基本的實作函數.
下面是其處理效果:
原始圖檔:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiM1ADM1gDMzAjMykDM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
先用均值濾波器濾波,再用普通的otsu 得到的是: 邊界修飾後的:
原始圖檔:
普通Otsu處理結果: 邊界修飾後: