(1)图像锐化处理
//此部分代码功能是锐化图像,本质上是 扫描图像并访问相邻像素
//sharpened_pixel=5*current-left-right-up-down
byte saturateCast(double inValue)
{
if (inValue < 0)
return 0;
else if (inValue > 255)
return 255;
else
return (byte)inValue;
}
bool sharpen(Mat inImg,Mat outImg)
{
int nchannel = inImg.NumberOfChannels;
int nrows = inImg.Rows;
int ncols = inImg.Cols;
if(nchannel==1)
{
var tempImg=inImg.ToImage<Gray,byte>();
var tempImg2 = tempImg.Clone();
for(int i=1;i<nrows-1;++i)
{
for(int j=1;j<ncols-1;++j)
{
tempImg2.Data[i, j, 0] = saturateCast(5 * tempImg.Data[i, j, 0] - tempImg.Data[i - 1, j, 0] - tempImg.Data[i, j - 1, 0] -
tempImg2.Data[i + 1, j, 0] - tempImg.Data[i, j + 1, 0]);
}
}
for(int i=0;i<nrows;++i)
{
tempImg2.Data[i, 0, 0] = 0;
tempImg2.Data[i, ncols - 1, 0] = 0;
}
for(int j=0;j<ncols;++j)
{
tempImg2.Data[0, j, 0] = 0;
tempImg2.Data[nrows - 1, j, 0] = 0;
}
tempImg2.Mat.CopyTo(outImg);
}
else if(nchannel==3)
{
var tempImg = inImg.ToImage<Bgr, byte>();
var tempImg2 = tempImg.Clone();
for(int i=1;i<nrows-1;++i)
{
for(int j=1;j<ncols-1;++j)
{
for(int channel=0;channel<3;channel++)
{
tempImg2.Data[i, j, channel] = saturateCast(5 * tempImg.Data[i, j, channel] - tempImg.Data[i - 1, j, channel] - tempImg.Data[i, j - 1, channel] -
tempImg.Data[i + 1, j, channel] - tempImg.Data[i, j + 1, channel]);
}
}
}
for(int i=0;i<nrows;++i)
{
for(int channel=0;channel<3;channel++)
{
tempImg2.Data[i, 0, channel] = 0;
tempImg2.Data[i, ncols - 1, channel] = 0;
}
}
for(int j=0;j<ncols;++j)
{
for(int channel=0;channel<3;channel++)
{
tempImg2.Data[0, j, channel] = 0;
tempImg2.Data[nrows - 1, j, channel] = 0;
}
}
tempImg2.Mat.CopyTo(outImg);
}else
{
return false;
}
return true;
}
private void button27_Click(object sender, EventArgs e)
{
Mat inImage = new Mat();
Mat outImage = new Mat();
if (chapter1OFD.ShowDialog() == DialogResult.OK)
inImage = CvInvoke.Imread(chapter1OFD.FileName, LoadImageType.AnyColor | LoadImageType.AnyDepth);
if (inImage.IsEmpty)
return;
if (!sharpen(inImage, outImage))
return;
if (outImage.IsEmpty)
return;
imageBox7.Image = outImage;
}
private void button28_Click(object sender, EventArgs e)
{
if (chapter2Img.IsEmpty)
return;
Image<Gray, float> kernelI = new Image<Gray, float>(3, 3, new Gray(0));
kernelI.Data[1, 1, 0] = (float)5.0;
kernelI.Data[0, 1, 0] = (float)-1.0;
kernelI.Data[1, 0, 0] = (float)-1.0;
kernelI.Data[2, 1, 0] = (float)-1.0;
kernelI.Data[1, 2, 0] = (float)-1.0;
if(chapter2Img.NumberOfChannels==3)
{
var tempImg = chapter2Img.ToImage<Bgr, byte>();
Image<Gray,byte>[] results = tempImg.Split();
CvInvoke.Filter2D(results[0].Mat, results[0].Mat, kernelI.Mat, new Point(-1, -1));
CvInvoke.Filter2D(results[1].Mat, results[1].Mat, kernelI.Mat, new Point(-1, -1));
CvInvoke.Filter2D(results[2].Mat, results[2].Mat, kernelI.Mat, new Point(-1, -1));
for(int i=0;i<tempImg.Rows;i++)
for(int j=0;j<tempImg.Cols;j++)
{
tempImg.Data[i, j, 0] = results[0].Data[i, j, 0];
tempImg.Data[i, j, 1] = results[1].Data[i, j, 0];
tempImg.Data[i, j, 2] = results[2].Data[i, j, 0];
}
imageBox7.Image = tempImg;
}
else if(chapter2Img.NumberOfChannels==1)
{
var tempImg = chapter2Img.ToImage<Gray, byte>();
CvInvoke.Filter2D(tempImg.Mat, tempImg.Mat, kernelI.Mat, new Point(-1, -1));
imageBox7.Image = tempImg;
}
}
以上两部分按钮,一个用的是自定义的函数进行锐化处理,一个用到了filter2d函数
(2)检测图片中的颜色(本例中为浅蓝色),并显示检测结果,监测结果为二值图像,白色表示所检测的颜色部分,黑色表示非所检测颜色部分
Mat chapter3Img = new Mat();
private void button29_Click(object sender, EventArgs e)
{
if(chapter1OFD.ShowDialog()==DialogResult.OK)
{
chapter3Img = CvInvoke.Imread(chapter1OFD.FileName, LoadImageType.AnyColor | LoadImageType.AnyDepth);
if (chapter3Img.IsEmpty)
return;
}
imageBox11.Image = chapter3Img;
}
private void button30_Click(object sender, EventArgs e)
{
if (chapter3Img.IsEmpty)
return;
ColorDetector cd = new ColorDetector();
cd.setTargetColor(230, 190, 130);
var result = cd.process(chapter3Img);
CvInvoke.NamedWindow("result", NamedWindowType.Normal);
CvInvoke.Imshow("result", result);
}
}
public class ColorDetector
{
private int maxDist;
private MCvScalar targetColor;
private Mat result=new Mat();
private void setTarget(int b,int g,int r)
{
targetColor.V0 = b;
targetColor.V1 = g;
targetColor.V2 = r;
}
public void setTargetColor(byte blue,byte green,byte red)
{
setTarget(blue, green, red);
}
public void setTargetColor(Color c)
{
setTarget(c.B, c.G, c.R);
}
int getColorDistance(Color c1,Color c2)
{
return (int)Math.Sqrt(Math.Pow((c1.B - c2.B), 2) + Math.Pow((c1.G - c2.G), 2) + Math.Pow((c1.R - c2.R), 2));
}
int getDistanceToTargetColor(Color c)
{
Color tempC;
tempC = Color.FromArgb((int)targetColor.V2, (int)targetColor.V1, (int)targetColor.V0);
var ds=getColorDistance(c, tempC);
以下是采用opencv的norm函数进行计算,仅仅为了说明一下,故将其注释掉
//Mat temp = new Mat(3, 1, DepthType.Cv64F, 1);
//var tempImg = temp.ToImage<Gray,float>();
//tempImg.Data[0, 0, 0] = c.R - tempC.R;
//tempImg.Data[1, 0, 0] = c.G - tempC.G;
//tempImg.Data[2, 0, 0] = c.B - tempC.B;
//var opencvDs = CvInvoke.Norm(tempImg);
over
return (int)ds;
}
public ColorDetector()
{
maxDist = 100;
setTarget(0, 0, 0);
}
void setColorDistanceThreshold(int distance)
{
if (distance < 0)
distance = 0;
maxDist = distance;
}
int getColorDistanceThreshold()
{
return maxDist;
}
public Mat process(Mat inImg)
{
result.Create(inImg.Rows, inImg.Cols, DepthType.Cv8U, 1);
var tempImg = result.ToImage<Gray, byte>();
var tempImg2 = inImg.ToImage<Bgr, byte>();
for(int i=0;i<tempImg2.Rows;i++)
for(int j=0;j<tempImg2.Cols;j++)
{
if(getDistanceToTargetColor(Color.FromArgb(tempImg2.Data[i,j,2], tempImg2.Data[i, j, 1], tempImg2.Data[i, j, 0]))<maxDist)
{
tempImg.Data[i, j, 0] = 255;
}
else
{
tempImg.Data[i, j, 0] = 0;
}
}
tempImg.Mat.CopyTo(result);
return result;
}
(3)HSV彩色空间,以及基于色调和饱和度的皮肤检测
private void button31_Click(object sender, EventArgs e)//HSV分离
{
if (chapter3Img.IsEmpty)
return;
var tempImg = new Mat();
CvInvoke.CvtColor(chapter3Img, tempImg, ColorConversion.Bgr2Hsv);
if (tempImg.IsEmpty)
return;
var imgs = tempImg.Split();
imageBox12.Image = imgs[2];
imageBox14.Image = imgs[1];
imageBox13.Image = imgs[0];
}
private void button32_Click(object sender, EventArgs e)
{
if (chapter3Img.IsEmpty)
return;
var tempImg = new Mat();
CvInvoke.CvtColor(chapter3Img, tempImg, ColorConversion.Bgr2Hsv);
if (tempImg.IsEmpty)
return;
//var imgs = tempImg.Split();
//imgs[2].SetTo(new MCvScalar(255));
var img = tempImg.ToImage<Hsv, byte>();
for(int i=0;i<img.Rows;i++)
for(int j=0;j<img.Cols;j++)
{
img.Data[i, j, 2] = 255;//统一亮度
}
CvInvoke.CvtColor(img.Mat, tempImg, ColorConversion.Hsv2Bgr);
imageBox15.Image = tempImg;
}
//基于HSV彩色空间的皮肤识别
void detectHScolor(Mat inImg,double minHue,double maxHue,double minSat,double maxSat,out Mat mask)
{
Mat HSVImg=new Mat();
CvInvoke.CvtColor(inImg, HSVImg, ColorConversion.Bgr2Hsv);
var hsvImgs = HSVImg.ToImage<Hsv, byte>().Split();
//色调掩码
Mat mask1 = new Mat();//小于maxHue
CvInvoke.Threshold(hsvImgs[0], mask1, maxHue, 255,ThresholdType.BinaryInv);
Mat mask2 = new Mat();//大于minHue
CvInvoke.Threshold(hsvImgs[0], mask2, minHue, 255, ThresholdType.Binary);
Mat hueMask = new Mat();//色调掩码
if (minHue < maxHue)
CvInvoke.BitwiseAnd(mask1, mask2, hueMask);
else
CvInvoke.BitwiseOr(mask1, mask2, hueMask);
//饱和度掩码
CvInvoke.Threshold(hsvImgs[1], mask1, maxSat, 255, ThresholdType.BinaryInv);
CvInvoke.Threshold(hsvImgs[1], mask2, minSat, 255, ThresholdType.Binary);
Mat satMask = new Mat();//饱和度掩码
CvInvoke.BitwiseAnd(mask1, mask2, satMask);
mask = new Mat();
CvInvoke.BitwiseAnd(hueMask, satMask, mask);
CvInvoke.NamedWindow("hue", NamedWindowType.Normal);
CvInvoke.NamedWindow("sat", NamedWindowType.Normal);
CvInvoke.Imshow("hue", hueMask);
CvInvoke.Imshow("sat", satMask);
}
private void button33_Click(object sender, EventArgs e)
{
if (chapter3Img.IsEmpty)
return;
Mat resultImg = new Mat();
detectHScolor(chapter3Img, 160, 10, 25, 166, out resultImg);
Mat detectedImg = chapter3Img.Clone();
detectedImg.SetTo(new MCvScalar(0, 0, 0));
chapter3Img.CopyTo(detectedImg, resultImg);
imageBox16.Image = detectedImg;
}
(4)直方图显示(灰度图像)
不得不吐槽下,我是准备用
public static void CalcHist(IInputArray images, int[] channels, IInputArray mask, IOutputArray hist, int[] histSize, float[] ranges, bool accumulate);
这个函数来进行计算的,但不知道为何,运行的时候总是会提示错误,捣鼓半天也查不到解决方案。然后,发现了Emgucv3的一个叫做DenseHistogram的类,该类主要是用来生成1D的直方图数据,颇为好用,故此部分用该类进行相关计算。在进行显示时,我用到了两种方法:使用HistogramViewer和使用HistogramBox控件。以下为代码:
Mat chapter4Img = new Mat();
private void button34_Click(object sender, EventArgs e)
{
if (chapter1OFD.ShowDialog() == DialogResult.OK)
chapter4Img = CvInvoke.Imread(chapter1OFD.FileName, LoadImageType.AnyColor | LoadImageType.AnyDepth);
if (chapter4Img.IsEmpty)
return;
imageBox17.Image = chapter4Img;
}
//以下是直方图计算类(灰度图像),内部使用DenseHistogram类
class HisttoGram1D
{
private int[] channels=new int[1];//使用的通道index
private int[] histSize= new int[1];//bin数量
private RangeF[] ranges=new RangeF[1];//像素值范围
private Mat mask=new Mat();//掩码
private Mat hist=new Mat();//得到的直方图数据
public HisttoGram1D()
{
channels[0] = 0;
histSize[0] = 256;
ranges[0] = new RangeF(0.0f, 255.1f);
}
public DenseHistogram getDenseHistogram(Mat inImg)
{
if (inImg.IsEmpty)
{
return null;
}
DenseHistogram dh = new DenseHistogram(histSize, ranges);
var imgs = inImg.ToImage<Gray, byte>().Split();
dh.Calculate(imgs, false, null);//计算直方图信息
return dh;
}
}
//直方图类定义over
private void button35_Click(object sender, EventArgs e)//单通道图像
{
Mat tempImg = new Mat();
HisttoGram1D ht1D = new HisttoGram1D();
if (chapter4Img.NumberOfChannels != 1)
{
CvInvoke.CvtColor(chapter4Img, tempImg, ColorConversion.Bgr2Gray);//彩色转灰度
}
else
{
tempImg = chapter4Img.Clone();
}
if (tempImg.IsEmpty)
return;
imageBox17.Image = tempImg;
//以下采用HistogramViewer类直接显示图像直方图
HistogramViewer hv = new HistogramViewer();
hv.Text = "单通道直方图";
hv.ShowInTaskbar = true;
hv.HistogramCtrl.GenerateHistograms(tempImg, 256);
hv.WindowState = FormWindowState.Normal;
hv.Show();
hv.Refresh();
//还有一种方式,直接使用HistogramViewer的静态函数,HistogramViewer.show(tempImg,256);也可以
//以下使用HistogramBox控件进行直方图显示,需要配合DenseHistogram类
var dhData = ht1D.getDenseHistogram(tempImg);//获取DenseHistogram对象
//值得注意的是,此时dhData,也就是DenseHistogram类,虽然是继承的Mat类,但里面存储的已经是直方图信息
histogramBox2.ClearHistogram();
histogramBox2.AddHistogram("HistogramBox控件显示单通道直方图", Color.FromArgb(255, 0, 0), dhData, 256, new float[] { 0.0f, 255.0f });
histogramBox2.Refresh();
int[] testt = new int[256];
var ttt = tempImg.ToImage<Gray, byte>();
for(int i=0;i<tempImg.Rows;i++)
for(int j=0;j<tempImg.Cols;j++)
{
testt[ttt.Data[i, j, 0]]++;
}
int ii = 0;
for(int i=0;i<testt.Length;i++)
{
ii += testt[i];
}
double iii = 0.0;
for(int i=0;i<dhData.GetBinValues().Length;i++)
{
iii += dhData.GetBinValues()[i];
}
int iiii = 0;
}
注意,以上有一句:
ranges[0] = new RangeF(0.0f, 255.1f);
如果将RangeF的范围设为0-255,最后得到的直方图会缺少像素值为255点的统计信息。原因我猜是rangeF的范围取值是[ min,max )方式吧。
这个问题把我困了n久。。。囧
接下来的小节我将重写一个直方图类,借助DenseHistogram,实现探测图像的通道(暂定为单通道和RGB彩色图像),并显示直方图。
(5)这一部分,可以读取无论是RGB三通道图像还是Gray单通道图像,并生成直方图的Mat数据并显示。
另外,还完成了点击“计算RGB直方图”这一按钮功能。
//定义函数,输入Mat图像,输出DenseHistogram对象
DenseHistogram[] getDenseHistogramDataOfImage(Mat inImg)//这里的inImg深度暂定为byte
{
DenseHistogram[] dh = new DenseHistogram[3] { new DenseHistogram(256, new RangeF(0f, 255.1f)), new DenseHistogram(256, new RangeF(0f, 255.1f)), new DenseHistogram(256, new RangeF(0f, 255.1f)) };
if (inImg.NumberOfChannels==3)
{
dh[0].Calculate(new Image<Gray, byte>[] { inImg.ToImage<Bgr, byte>()[0] }, false, null);
dh[1].Calculate(new Image<Gray, byte>[] { inImg.ToImage<Bgr, byte>()[1] }, false, null);
dh[2].Calculate(new Image<Gray, byte>[] { inImg.ToImage<Bgr, byte>()[2] }, false, null);
}
else
{
dh[0].Calculate(new Image<Gray, byte>[] { inImg.ToImage<Gray, byte>()[0] }, false, null);
dh[1] = null;
dh[2] = null;
}
return dh;
}
private void button36_Click(object sender, EventArgs e)
{
if (chapter4Img.IsEmpty)
return;
var dhData = getDenseHistogramDataOfImage(chapter4Img);
histogramBox2.ClearHistogram();
if(chapter4Img.NumberOfChannels==3)
{
histogramBox2.AddHistogram("Blue直方图", Color.FromArgb(0, 0, 255), dhData[0], 256, new float[] { 0.0f, 255.0f });
histogramBox2.AddHistogram("Green直方图", Color.FromArgb(0, 255, 0), dhData[1], 256, new float[] { 0.0f, 255.0f });
histogramBox2.AddHistogram("Red直方图", Color.FromArgb(255, 0, 0), dhData[2], 256, new float[] { 0.0f, 255.0f });
}
else
{
histogramBox2.AddHistogram("直方图", Color.FromArgb(0, 0, 0), dhData[0], 256, new float[] { 0.0f, 255.0f });
}
histogramBox2.Refresh();
}
private Mat AddMultiHistograms(DenseHistogram[] dh)//此函数功能实现由输入的直方图数据生成Mat图像
{
double maxValue = 0;
double minValue = 0;
if (dh[1] == null)//说明用于计算直方图的图像为单通道
{
var arrayData = dh[0].GetBinValues();
maxValue = arrayData.Max();
minValue = arrayData.Min();
for (int i = 0; i < arrayData.Length; i++)
{
arrayData[i] = (int)((arrayData[i] - minValue) / (maxValue - minValue) * 400.0);
}
var hist = new Image<Gray, byte>(512, 512, new Gray(255));
Point p1 = new Point();
Point p2 = new Point();
p1.X = 0;
p1.Y = 512-(int)arrayData[0];
for (int i = 1; i < arrayData.Length; i++)
{
p2.X = i*2;
p2.Y = 512-(int)arrayData[i];
CvInvoke.Line(hist, p1, p2, new MCvScalar(0),3);
p1.X = p2.X;
p1.Y = p2.Y;
}
return hist.Mat;
}
else
{
List<float[]> arrayData = new List<float[]> { dh[0].GetBinValues(), dh[1].GetBinValues(), dh[2].GetBinValues() };//颜色顺序BGR
maxValue = Math.Max(Math.Max(dh[0].GetBinValues().Max(), dh[1].GetBinValues().Max()), dh[2].GetBinValues().Max());
minValue=Math.Min(Math.Min(dh[0].GetBinValues().Min(), dh[1].GetBinValues().Min()), dh[2].GetBinValues().Min());
for(int index=0;index<arrayData.Count;index++)
{
for(int i=0;i<arrayData[index].Length;i++)
{
arrayData[index][i] = (float)((arrayData[index][i] - minValue) / (maxValue - minValue)*400.0);
}
}
var hist = new Image<Bgr, byte>(512, 512, new Bgr(255, 255, 255));//初始化图像
Point p1 = new Point();
Point p2 = new Point();
var colors = new MCvScalar[3] { new MCvScalar(255, 0, 0), new MCvScalar(0, 255, 0), new MCvScalar(0, 0, 255) };
for(int index=0;index<arrayData.Count;index++)
{
p1.X = 0;
p1.Y = 512 - (int)arrayData[index][0];
for(int i=1;i<arrayData[index].Length;i++)
{
p2.X = i*2;
p2.Y = 512 - (int)arrayData[index][i];
CvInvoke.Line(hist, p1, p2, colors[index], 3);
p1.X = p2.X;
p1.Y = p2.Y;
}
}
return hist.Mat;
}
}
private void button37_Click(object sender, EventArgs e)
{
if (chapter4Img.IsEmpty)
return;
var dhData = getDenseHistogramDataOfImage(chapter4Img);
var histMat = AddMultiHistograms(dhData);
imageBox18.Image = histMat;
}
(6)基于查找表的颜色反转
无论是彩色还是单通道都可以哈
//利用查找表反转灰度图像
private void button38_Click(object sender, EventArgs e)
{
if (chapter4Img.IsEmpty)
return;
Mat lut = new Mat(1, 256, DepthType.Cv8U, 1);
byte[] data = new byte[256];
for(int i=0;i<data.Length;i++)
{
data[i] = (byte)(256 - i);
}
lut.SetTo(data);
Mat resultImg = new Mat();
CvInvoke.LUT(chapter4Img, lut, resultImg);
imageBox17.Image = resultImg;
}