輪廓的查找、表達、繪制、特性及匹配(How to Use Contour? Find, Component, Construct, Features Match)

前言
??? 輪廓是構成任何一個形狀的邊界或外形線。前面講了如何根據色彩及色彩的分布(直方圖對比和模板匹配)來進行匹配,現在我們來看看如何利用物體的輪廓。包括以下內容:輪廓的查找、表達方式、組織方式、繪制、特性、匹配。

?查找輪廓
??? 首先我們面對的問題是如何在圖像中找到輪廓,OpenCv(EmguCv)為我們做了很多工作,我們的任務只是調用現成的函數而已。Image<TColor,TDepth>類的FindContours方法可以很方便的查找輪廓,不過在查找之前,我們需要將彩色圖像轉換成灰度圖像,然后再將灰度圖像轉換成二值圖像。代碼如下所示:

Image<Bgr, Byte> imageSource = new Image<Bgr, byte>(sourceImageFileName); //獲取源圖像
Image<Gray, Byte> imageGray = imageSource.Convert<Gray, Byte>(); //將源圖像轉換成灰度圖像
int thresholdValue = tbThreshold.Value; //用于二值化的閥值
Image<Gray, Byte> imageThreshold = imageGray.ThresholdBinary(new Gray(thresholdValue), new Gray(255d)); //對灰度圖像二值化
Contour<Point> contour=imageThreshold.FindContours();

?

輪廓的表達方式
??? 使用上面的代碼可以得到圖像的默認輪廓,但是輪廓在電腦中是如何表達的呢?在OpenCv(EmguCv)中提供了兩類表達輪廓的方式:頂點的序列、Freeman鏈碼。

1.頂點的序列
??? 用多個頂點(或各點間的線段)來表達輪廓。假設要表達一個從(0,0)到(2,2)的矩形,
(1)如果用點來表示,那么依次存儲的可能是:(0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,1);
(2)如果用點間的線段來表達輪廓,那么依次存儲的可能是:(0,0),(2,0),(2,2),(0,2)。
以下代碼可以用來獲取輪廓上的點:

for (int i = 0; i < contour.Total; i++)
sbContour.AppendFormat(
"{0},", contour[i]);

?

?2.Freeman鏈碼
??? Freeman鏈碼需要一個起點,以及從起點出發的一系列位移。每個位移有8個方向,從0~7分別指向從正北開始的8個方向。假設要用Freeman鏈碼表達從(0,0)到(2,2)的矩形,可能的表示方法是:起點(0,0),方向鏈2,2,4,4,6,6,0,0。
??? EmguCv對Freeman鏈碼的支持很少,我們需要做一系列的工作才能在.net中使用Freeman鏈碼:
(1)獲取Freeman鏈碼

復制代碼
//查找用Freeman鏈碼表示的輪廓
Image<Gray,Byte> imageTemp=imageThreshold.Copy();
IntPtr storage
= CvInvoke.cvCreateMemStorage(0);
IntPtr ptrFirstChain
= IntPtr.Zero;
int total = CvInvoke.cvFindContours(imageTemp.Ptr, storage, ref ptrFirstChain, sizeof(MCvChain), mode, CHAIN_APPROX_METHOD.CV_CHAIN_CODE, new Point(0, 0));
復制代碼


(2)遍歷Freeman鏈碼上的點

復制代碼
//初始化Freeman鏈碼讀取
[DllImport("cv200.dll")]
public static extern void cvStartReadChainPoints(IntPtr ptrChain,IntPtr ptrReader);
//讀取Freeman鏈碼的點
[DllImport("cv200.dll")]
public static extern Point cvReadChainPoint(IntPtr ptrReader);
[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet
= System.Runtime.InteropServices.CharSet.Ansi)]
//定義鏈碼讀取結構
public struct MCvChainPtReader
{
//seqReader
public MCvSeqReader seqReader;
/// char
public byte code;
/// POINT->tagPOINT
public Point pt;
/// char[16]
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 16)]
public string deltas;
}

//將鏈碼指針轉換成結構
MCvChain chain=(MCvChain)Marshal.PtrToStructure(ptrChain,typeof(MCvChain));
//定義存放鏈碼上點的列表
List<Point> pointList = new List<Point>(chain.total);
//鏈碼讀取結構
MCvChainPtReader chainReader = new MCvChainPtReader();
IntPtr ptrReader
= Marshal.AllocHGlobal(sizeof(MCvSeqReader) + sizeof(byte) + sizeof(Point) + 16 * sizeof(byte));
Marshal.StructureToPtr(chainReader, ptrReader,
false);
//開始讀取鏈碼
cvStartReadChainPoints(ptrChain, ptrReader);
int i = 0;
while (ptrReader != IntPtr.Zero && i < chain.total)
{
//依次讀取鏈碼上的每個點
Point p = cvReadChainPoint(ptrReader);
if (ptrReader == IntPtr.Zero)
break;
else
{
pointList.Add(p);
sbChain.AppendFormat(
"{0},", p);
i
++;
}
}
imageResult.DrawPolyline(pointList.ToArray(),
true, new Bgr(lblExternalColor.BackColor), 2);
復制代碼

??

??? 需要注意的是:cvReadChainPoint函數似乎永遠不會滿足循環終止的條件,即ptrReader永遠不會被置為null,這跟《學習OpenCv》和參考上不一致;我們需要用chain.total來輔助終止循環,讀取了所有的點之后就可以罷手了。

輪廓之間的組織方式
??? 在查找到輪廓之后,不同輪廓是怎么組織的呢?根據不同的選擇,它們可能是:(1)列表;(2)雙層結構;(3)樹型結構。
??? 從縱向上來看,列表只有一層,雙層結構有一或者兩層,樹型結構可能有一層或者多層。
??? 如果要遍歷所有的輪廓,可以使用遞歸的方式,代碼如下:

復制代碼
//遍歷輪廓,并生成遍歷結果
private void TravelContour(Contour<Point> contour,ref int total,ref StringBuilder sbContour)
{
if (contour != null)
{
sbContour.Append(
"------------------------\r\n");
sbContour.AppendFormat(
"輪廓{0},右節點:{1},下級節點:{2},外接矩形:({3})\r\n", total, contour.HNext != null, contour.VNext != null, contour.BoundingRectangle);
sbContour.AppendFormat(
"包含{0}個點(面積:{1},周長:{2}):\r\n", contour.Total, contour.Area, contour.Perimeter);
for (int i = 0; i < contour.Total; i++)
sbContour.AppendFormat(
"{0},", contour[i]);
sbContour.Append(
"\r\n");
total
++;
if (contour.HNext != null)
TravelContour(contour.HNext,
ref total, ref sbContour);
if (contour.VNext != null)
TravelContour(contour.VNext,
ref total, ref sbContour);
}
}
復制代碼

?

輪廓的繪制
??? 輪廓的繪制比較簡單,用上面提到的方法取得輪廓的所有點,然后把這些點連接成一個多邊形即可。
??? 當然,對于用頂點序列表示的輪廓,用Image<TColor,TDepth>.Draw方法或者cvDrawContours函數可以很方便的繪制出輪廓。我發現,如果將參數max_level設置成2,可以繪制出所有的輪廓。
??? 繪制輪廓的代碼如下:

Image<Bgr, Byte> imageResult = imageThreshold.Convert<Bgr, Byte>(); //結果圖像
int maxLevel = 0; //繪制的輪廓深度
int.TryParse(txtMaxLevel.Text, out maxLevel);
imageResult.Draw(contour,
new Bgr(lblExternalColor.BackColor), new Bgr(lblHoleColor.BackColor), maxLevel, 2);


輪廓的特性
??? 輪廓的特性有很多,下面一一介紹。

1.輪廓的多邊形逼近
??? 輪廓的多邊形逼近指的是:使用多邊形來近似表示一個輪廓。
??? 多邊形逼近的目的是為了減少輪廓的頂點數目。
??? 多邊形逼近的結果依然是一個輪廓,只是這個輪廓相對要粗曠一些。
??? 可以使用Contour<Point>.ApproxPoly方法或者cvApproxyPoly函數來對輪廓進行多邊形逼近,示例代碼如下:

contour = firstContour.ApproxPoly(double.Parse(txtApproxParameter.Text), 2, new MemStorage());

??

2.輪廓的關鍵點
??? 輪廓的關鍵點是:輪廓上包含曲線信息比較多的點。關鍵點是輪廓頂點的子集。
??? 可以使用cvFindDominantPoints函數來獲取輪廓上的關鍵點,該函數返回的結果一個包含 關鍵點在輪廓頂點中索引 的序列。再次強調:是索引,不是具體的點。如果要得到關鍵點的具體坐標,可以用索引到輪廓上去找。
??? 以下代碼演示了如何獲取輪廓上的關鍵點:

復制代碼
//得到關鍵點信息
private void GetDominantPointsInfo(Contour<Point> contour, ref StringBuilder sbContour, ref Image<Bgr, Byte> imageResult, double parameter1, double parameter2, double parameter3, double parameter4, Bgr dominantPointColor)
{
if (contour.Total > 2)
{
MemStorage storage
= new MemStorage();
try
{
IntPtr ptrSeq
= cvFindDominantPoints(contour.Ptr, storage.Ptr, (int)CV_DOMINANT.CV_DOMINANT_IPAN, parameter1, parameter2, parameter3, parameter4);
Seq
<int> seq = new Seq<int>(ptrSeq, storage);
sbContour.AppendFormat(
"{0}個關鍵點:\r\n", seq.Total);
for (int i = 0; i < seq.Total; i++)
{
int idx = seq[i]; //關鍵點序列中存儲的數據 是 關鍵點在輪廓中所處位置的索引
Point p = contour[idx]; //得到關鍵點的坐標
sbContour.AppendFormat("{0}({1},{2}),", idx, p.X, p.Y);
imageResult.Draw(
new CircleF(new PointF(p.X, p.Y), 3), dominantPointColor, -1);
}
sbContour.Append(
"\r\n");
}
catch (CvException ex)
{
sbContour.AppendFormat(
"在獲取關鍵點時發生異常,錯誤描述:{0},錯誤源:{1},錯誤堆棧:{2}\r\n錯誤文件:{3},函數名:{4},行:{5},錯誤內部描述:{6}\r\n", ex.Message, ex.Source, ex.StackTrace, ex.FileName, ex.FunctionName, ex.Line, ex.ErrorStr);
}
catch (Exception e)
{
sbContour.AppendFormat(
"在獲取關鍵點時發生異常,錯誤描述:{0},錯誤源:{1},錯誤堆棧:{2}\r\n", e.Message, e.Source, e.StackTrace);
}
finally
{
storage.Dispose();
}
}
}
復制代碼


3.輪廓的周長和面積
??? 輪廓的周長可以用Contour<Point>.Perimeter屬性或者cvArcLength函數來獲取。
??? 輪廓的面積可以用Contour<Point>.Area屬性或者cvContourArea函數來獲取。

4.輪廓的邊界框
??? 有三種常見的邊界框:矩形、圓形、橢圓。
??? (1)矩形:在圖像處理系統中提供了一種叫Rectangle的矩形,不過它只能表達邊垂直或水平的特例;OpenCv中還有一種叫Box的矩形,它跟數學上的矩形一致,只要4個角是直角即可。
??? 如果要獲取輪廓的Rectangle,可以使用Contour<Point>.BoundingRectangle屬性或者cvBoundingRect函數。
??? 如果要獲取輪廓的Box,可以使用Contour<Point>.GetMinAreaRect方法或者cvMinAreaRect2函數。
??? (2)圓形
??? 如果要獲取輪廓的圓形邊界框,可以使用cvMinEnclosingCircle函數。
??? (3)橢圓
??? 如果要獲取輪廓的橢圓邊界框,可以使用cvFitEllipse2函數。
??? 下列代碼演示了如何獲取輪廓的各種邊界框:

復制代碼
//得到邊界框信息
private void GetEdgeInfo(Contour<Point> contour, string edge, ref StringBuilder sbContour, ref Image<Bgr, Byte> imageResult, Bgr edgeColor)
{
if (edge == "Rect")
//矩形
imageResult.Draw(contour.BoundingRectangle, edgeColor, 2);
else if (edge == "MinAreaRect")
{
//最小矩形
MCvBox2D box = CvInvoke.cvMinAreaRect2(contour.Ptr, IntPtr.Zero);
PointF[] points
= box.GetVertices();
Point[] ps
= new Point[points.Length];
for (int i = 0; i < points.Length; i++)
ps[i]
= new Point((int)points[i].X, (int)points[i].Y);
imageResult.DrawPolyline(ps,
true, edgeColor, 2);
}
else if (edge == "Circle")
{
//圓形
PointF center;
float radius;
CvInvoke.cvMinEnclosingCircle(contour.Ptr,
out center, out radius);
imageResult.Draw(
new CircleF(center, radius), edgeColor, 2);
}
else
{
//橢圓
if (contour.Total >= 6)
{
MCvBox2D box
= CvInvoke.cvFitEllipse2(contour.Ptr);
imageResult.Draw(
new Ellipse(box), edgeColor, 2);
}
else
sbContour.Append(
"輪廓點數小于6,不能創建外圍橢圓。\r\n");
}
}
復制代碼

??

5.輪廓的矩
??? 我們可以使用Contour<Point>.GetMoments方法或者cvMoments函數方便的得到輪廓的矩集,然后再相應的方法或函數獲取各種矩。
??? 特定的矩:MCvMoments.GetSpatialMoment方法、cvGetSpatialMoment函數
??? 中心矩:MCvMoments.GetCentralMoment方法、cvGetCentralMoment函數
??? 歸一化中心矩:MCvMoments.GetNormalizedCentralMoment方法、cvGetNormalizedCentralMoment函數
??? Hu矩:MCvMoments.GetHuMoment方法、McvHuMoments.hu1~hu7字段、cvGetHuMoments函數
??? 以下代碼演示了如何獲取輪廓的矩:

復制代碼
//得到各種矩的信息
private void GetMomentsInfo(Contour<Point> contour, ref StringBuilder sbContour)
{
//
MCvMoments moments = contour.GetMoments();
//遍歷各種情況下的矩、中心矩及歸一化矩,必須滿足條件:xOrder>=0; yOrder>=0; xOrder+yOrder<=3;
for (int xOrder = 0; xOrder <= 3; xOrder++)
{
for (int yOrder = 0; yOrder <= 3; yOrder++)
{
if (xOrder + yOrder <= 3)
{
double spatialMoment = moments.GetSpatialMoment(xOrder, yOrder);
double centralMoment = moments.GetCentralMoment(xOrder, yOrder);
double normalizedCentralMoment = moments.GetNormalizedCentralMoment(xOrder, yOrder);
sbContour.AppendFormat(
"矩(xOrder:{0},yOrder:{1}),矩:{2:F09},中心矩:{3:F09},歸一化矩:{4:F09}\r\n", xOrder, yOrder, spatialMoment, centralMoment, normalizedCentralMoment);
}
}
}
//Hu矩
MCvHuMoments huMonents = moments.GetHuMoment();
sbContour.AppendFormat(
"Hu矩 h1:{0:F09},h2:{1:F09},h3:{2:F09},h4:{3:F09},h5:{4:F09},h6:{5:F09},h7:{6:F09}\r\n", huMonents.hu1, huMonents.hu2, huMonents.hu3, huMonents.hu4, huMonents.hu5, huMonents.hu6, huMonents.hu7);
}
復制代碼


6.輪廓的輪廓樹
??? 輪廓樹用來描述某個特定輪廓的內部特征。注意:輪廓樹跟輪廓是一一對應的關系;輪廓樹不用于描述多個輪廓之間的層次關系。
??? 可以用函數cvCreateContourTree來構造輪廓樹。

IntPtr ptrTree1 = CvInvoke.cvCreateContourTree(contour1.Ptr, new MemStorage().Ptr, thresholdOfCreate);

?

?7.輪廓的凸包和凸缺陷
??? 輪廓的凸包和凸缺陷用于描述物體的外形。凸包和凸缺陷很容易獲得,不過我目前不知道它們到底怎么使用。
??? 如果要判斷輪廓是否是凸的,可以用Contour<Point>.Convex屬性和cvCheckContourConvexity函數。
??? 如果要獲取輪廓的凸包,可以用Contour<Point>.GetConvexHull方法或者cvConvexHull2函數,返回的是包含頂點的序列。
??? 如果要獲取輪廓的凸缺陷,可以用Contour<Point>.GetConvexityDefacts方法或者cvConvexityDefects函數。
??? 注意:EmguCv將缺陷的單詞拼寫錯了,defect才是缺陷。
??? 以下代碼演示了如何獲取輪廓的凸包及凸缺陷:

復制代碼
//得到凸包及缺陷信息
private void GetConvexInfo(Contour<Point> contour,ref StringBuilder sbContour,ref Image<Bgr,Byte> imageResult)
{
if (!contour.Convex) //判斷輪廓是否為凸
{
//凸包
Seq<Point> convexHull = contour.GetConvexHull(ORIENTATION.CV_CLOCKWISE);
//缺陷
Seq<MCvConvexityDefect> defects = contour.GetConvexityDefacts(new MemStorage(), ORIENTATION.CV_CLOCKWISE);
//顯示信息
sbContour.AppendFormat("輪廓的凸包有{0}個點,依次為:", convexHull.Total);
Point[] points
= new Point[convexHull.Total];
for (int i = 0; i < convexHull.Total; i++)
{
Point p
= convexHull[i];
points[i]
= p;
sbContour.AppendFormat(
"{0},", p);
}
sbContour.Append(
"\r\n");
imageResult.DrawPolyline(points,
true, new Bgr(lblConvexColor.BackColor), 2);
MCvConvexityDefect defect;
sbContour.AppendFormat(
"輪廓有{0}個缺陷,依次為:\r\n", defects.Total);
for (int i = 0; i < defects.Total; i++)
{
defect
= defects[i];
sbContour.AppendFormat(
"缺陷:{0},起點:{1},終點:{2},最深的點:{3},深度:{4}\r\n", i, defect.StartPoint, defect.EndPoint, defect.DepthPoint, defect.Depth);
}
}
else
sbContour.Append(
"輪廓是凸的,凸包和輪廓一樣。\r\n");
}
復制代碼

?

?8.輪廓的成對幾何直方圖
??? 成對幾何直方圖的資料比較少,我是這么理解的。
??? (1)輪廓保存的是一系列的頂點,輪廓是由一系列線段組成的多邊形。對于看起來光滑的輪廓(例如圓),只是線段條數比較多,線段長度比較短而已。實際上,電腦中顯示的任何曲線都由線段組成。
??? (2)每兩條線段之間都有一定的關系,包括它們(或者它們的延長線)之間的夾角,兩條線段的夾角范圍是:(0,180)。
??? (3)每兩條線段上的點之間還有距離關系,包括最短(小)距離、最遠(大)距離,以及平均距離。最大距離我用了一個偷懶的計算方法,我把輪廓外界矩形的對角線長度看作了最大距離。
??? (4)成對幾何直方圖所用的統計數據包括了夾角和距離。
??? 可以用函數cvCalcPGH來計算輪廓的成對幾何直方圖,示例代碼如下:

復制代碼
//生成成對幾何直方圖
Rectangle rect1 = contour1.BoundingRectangle;
float maxDist1 = (float)Math.Sqrt(rect1.Width * rect1.Width + rect1.Height * rect1.Height); //輪廓的最大距離:這里使用輪廓矩形邊界框的對角線長度
int[] bins1 = new int[] { 60, 20 };
RangeF[] ranges1
= new RangeF[] { new RangeF(0f, 180f), new RangeF(0f, maxDist1) }; //直方圖第0維為角度,范圍在(0,180),第2維為輪廓兩條邊緣線段的距離
DenseHistogram hist1 = new DenseHistogram(bins1, ranges1);
CvInvoke.cvCalcPGH(contour1.Ptr, hist1.Ptr);
復制代碼

?

?

輪廓的匹配
??? 如果要比較兩個物體,可供選擇的特征很多。如果要判斷某個人的性別,可以根據他(她)頭發的長短來判斷,這很直觀,在長發男稀有的年代準確率也很高。也可以根據這個人尿尿的射程來判斷,如果射程大于0.50米,則是男性。總之,方法很多,不一而足。
??? 我們在上文中得到了輪廓的這么多特征,它們也可以用于進行匹配。典型的輪廓匹配方法有:Hu矩匹配、輪廓樹匹配、成對幾何直方圖匹配。
1.Hu矩匹配
??? 輪廓的Hu矩對包括縮放、旋轉和鏡像映射在內的變化具有不變性。Contour<Point>.MatchShapes方法和cvMatchShapes函數可以很方便的實現對2個輪廓間的匹配。
2.輪廓樹匹配
??? 用樹的形式比較兩個輪廓。cvMatchContourTrees函數實現了輪廓樹的對比。
3.成對幾何直方圖匹配
??? 在得到輪廓的成對幾何直方圖之后,可以使用直方圖對比的方法來進行匹配。如果您和我一樣忘記了直方圖的對比方式,可以看看我寫的另一篇文章《顏色直方圖的計算、顯示、處理、對比及反向投影(How to Use Histogram? Calculate, Show, Process, Compare and BackProject)》。

??? 各種輪廓匹配的示例代碼如下:

復制代碼
//開始匹配
private void btnStartMatch_Click(object sender, EventArgs e)
{
//準備輪廓(這里只比較最外圍的輪廓)
Image<Bgr, Byte> image1 = new Image<Bgr, byte>((Bitmap)pbImage1.Image);
Image
<Bgr, Byte> image2 = new Image<Bgr, byte>((Bitmap)pbImage2.Image);
Image
<Gray, Byte> imageGray1 = image1.Convert<Gray, Byte>();
Image
<Gray, Byte> imageGray2 = image2.Convert<Gray, Byte>();
Image
<Gray, Byte> imageThreshold1 = imageGray1.ThresholdBinaryInv(new Gray(128d), new Gray(255d));
Image
<Gray, Byte> imageThreshold2 = imageGray2.ThresholdBinaryInv(new Gray(128d), new Gray(255d));
Contour
<Point> contour1 = imageThreshold1.FindContours(CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE, RETR_TYPE.CV_RETR_EXTERNAL);
Contour
<Point> contour2 = imageThreshold2.FindContours(CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE, RETR_TYPE.CV_RETR_EXTERNAL);
/*if (contour1.Perimeter / 50 > 2 && contour2.Perimeter / 50 > 2)
{
contour1 = contour1.ApproxPoly(contour1.Perimeter / 50, 2, new MemStorage()); //對輪廓進行多邊形逼近(參數設為輪廓周長的1/50)
contour2 = contour2.ApproxPoly(contour2.Perimeter / 50, 2, new MemStorage());
}
*/
//進行匹配
string result = "";
if (rbHuMoments.Checked)
result
= MatchShapes(contour1, contour2); //Hu矩匹配
else if (rbContourTree.Checked)
result
= MatchContourTrees(contour1, contour2); //輪廓樹匹配
else if (rbPGH.Checked)
result
= MatchPghHist(contour1, contour2); //成對幾何直方圖匹配
txtResult.Text += result;
}

//Hu矩匹配
private string MatchShapes(Contour<Point> contour1, Contour<Point> contour2)
{
//匹配方法
CONTOURS_MATCH_TYPE matchType = rbHuI1.Checked ? CONTOURS_MATCH_TYPE.CV_CONTOUR_MATCH_I1 : (rbHuI2.Checked ? CONTOURS_MATCH_TYPE.CV_CONTOURS_MATCH_I2 : CONTOURS_MATCH_TYPE.CV_CONTOURS_MATCH_I3);
Stopwatch sw
= new Stopwatch();
sw.Start();
//匹配
double matchValue = contour1.MatchShapes(contour2, matchType);
sw.Stop();
double time = sw.Elapsed.TotalMilliseconds;
return string.Format("Hu矩匹配({0:G}),結果:{1:F05},用時:{2:F05}毫秒\r\n", matchType, matchValue, time);
}

//輪廓樹匹配
private string MatchContourTrees(Contour<Point> contour1, Contour<Point> contour2)
{
//生成輪廓樹
double thresholdOfCreate = double.Parse(txtThresholdOfCreateContourTrees.Text); //生成輪廓樹的閥值
IntPtr ptrTree1 = CvInvoke.cvCreateContourTree(contour1.Ptr, new MemStorage().Ptr, thresholdOfCreate);
IntPtr ptrTree2
= CvInvoke.cvCreateContourTree(contour2.Ptr, new MemStorage().Ptr, thresholdOfCreate);
//匹配
double thresholdOfMatch = double.Parse(txtThresholdOfMatchContourTrees.Text); //比較輪廓樹的閥值
Stopwatch sw = new Stopwatch();
sw.Start();
double matchValue = CvInvoke.cvMatchContourTrees(ptrTree1, ptrTree2, MATCH_CONTOUR_TREE_METHOD.CONTOUR_TREES_MATCH_I1, thresholdOfMatch);
sw.Stop();
double time = sw.Elapsed.TotalMilliseconds;
return string.Format("輪廓樹匹配(生成輪廓樹的閥值:{0},比較輪廓樹的閥值:{1}),結果:{2:F05},用時:{3:F05}毫秒\r\n", thresholdOfCreate, thresholdOfMatch, matchValue, time);
}

//成對幾何直方圖匹配
private string MatchPghHist(Contour<Point> contour1, Contour<Point> contour2)
{
//生成成對幾何直方圖
Rectangle rect1 = contour1.BoundingRectangle;
float maxDist1 = (float)Math.Sqrt(rect1.Width * rect1.Width + rect1.Height * rect1.Height); //輪廓的最大距離:這里使用輪廓矩形邊界框的對角線長度
int[] bins1 = new int[] { 60, 20 };
RangeF[] ranges1
= new RangeF[] { new RangeF(0f, 180f), new RangeF(0f, maxDist1) }; //直方圖第0維為角度,范圍在(0,180),第2維為輪廓兩條邊緣線段的距離
DenseHistogram hist1 = new DenseHistogram(bins1, ranges1);
CvInvoke.cvCalcPGH(contour1.Ptr, hist1.Ptr);
Rectangle rect2
= contour2.BoundingRectangle;
float maxDist2 = (float)Math.Sqrt(rect2.Width * rect2.Width + rect2.Height * rect2.Height);
int[] bins2 = new int[] { 60, 20 };
RangeF[] ranges2
= new RangeF[] { new RangeF(0f, 180f), new RangeF(0f, maxDist2) };
DenseHistogram hist2
= new DenseHistogram(bins2, ranges2);
CvInvoke.cvCalcPGH(contour2.Ptr, hist2.Ptr);
//匹配
Stopwatch sw = new Stopwatch();
sw.Start();
double compareResult;
HISTOGRAM_COMP_METHOD compareMethod
= rbHistCorrel.Checked ? HISTOGRAM_COMP_METHOD.CV_COMP_CORREL : (rbHistChisqr.Checked ? HISTOGRAM_COMP_METHOD.CV_COMP_CHISQR : (rbHistIntersect.Checked ? HISTOGRAM_COMP_METHOD.CV_COMP_INTERSECT : HISTOGRAM_COMP_METHOD.CV_COMP_BHATTACHARYYA));
if (rbHistEmd.Checked)
{
//EMD
//將直方圖轉換成矩陣
Matrix<Single> matrix1 = FormProcessHist.ConvertDenseHistogramToMatrix(hist1);
Matrix
<Single> matrix2 = FormProcessHist.ConvertDenseHistogramToMatrix(hist2);
compareResult
= CvInvoke.cvCalcEMD2(matrix1.Ptr, matrix2.Ptr, DIST_TYPE.CV_DIST_L2, null, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
matrix1.Dispose();
matrix2.Dispose();
}
else
{
//直方圖對比方式
hist1.Normalize(1d);
hist2.Normalize(1d);
compareResult
= CvInvoke.cvCompareHist(hist1.Ptr, hist2.Ptr, compareMethod);
}
sw.Stop();
double time = sw.Elapsed.TotalMilliseconds;
return string.Format("成對幾何直方圖匹配(匹配方式:{0}),結果:{1:F05},用時:{2:F05}毫秒\r\n", rbHistEmd.Checked ? "EMD" : compareMethod.ToString("G"), compareResult, time);
}

復制代碼

?

??

??? 通過以上代碼,可以計算出兩個輪廓對比的值,但是這些值具體代表什么意義呢?實際上,我目前還不清楚,需要進行大量的試驗才行。

?

---------------------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using Emgu.CV;
using Emgu.Util;
using Emgu.CV.Structure;
using Emgu.CV.CvEnum;
using System.Diagnostics;

?

namespace Chuanlin2012._01
{
??? public partial class MainFrm : Form
??? {
??????? public Image<Bgr, byte> src;
??????? public Image<Bgr, byte> subsrc;?????

??????? public MainFrm()
??????? {
??????????? InitializeComponent();
??????? }

?

??????? private void 打開ToolStripMenuItem_Click(object sender, EventArgs e)
??????? {
??????????? OpenFileDialog opflg = new OpenFileDialog();
??????????? opflg.Filter = "所有文件|*.*";
??????????? if (opflg.ShowDialog() == DialogResult.OK)
??????????? {
??????????????? Image<Bgr, byte> rgbimg1 = new Image<Bgr, byte>(opflg.FileName);
??????????????? imageBox1.Image = rgbimg1;
??????????????? src = rgbimg1;
??????????????
??????????? }

??????? }

?

??????? private void 保存ToolStripMenuItem_Click(object sender, EventArgs e)
??????? {
??????????? if (imageBox1.Image != null)
??????????? {
??????????????? SaveFileDialog sfdlg = new SaveFileDialog();
??????????????? sfdlg.Filter = "bmp文件|*.bmp";
??????????????? sfdlg.FilterIndex = 2;
??????????????? sfdlg.RestoreDirectory = true;

??????????????? if (sfdlg.ShowDialog() == DialogResult.OK)
??????????????? {
??????????????????? string fname = sfdlg.FileName;
??????????????????? Bitmap bmp = imageBox1.Image.Bitmap;
??????????????????? bmp.Save(fname, System.Drawing.Imaging.ImageFormat.Bmp);
??????????????? }
??????????? }
??????????? else
??????????? {
??????????????? System.Windows.Forms.MessageBox.Show("請先打開圖像!");
??????????????? return;
??????????? }

?


??????? }

?

???private void 退出ToolStripMenuItem_Click_1(object sender, EventArgs e)
??????? {
??????????? this.Close();

??????? }

?

??????? private void sobel算子ToolStripMenuItem_Click(object sender, EventArgs e)
??????? {
??????????? if (imageBox1.Image != null)
??????????? {
??????????????? Image<Bgr, float> imgsoble = src.Sobel(0, 1, 3);
??????????????? imageBox1.Image = imgsoble;
??????????? }
??????????? else
??????????? {
??????????????? System.Windows.Forms.MessageBox.Show("請先打開圖像!");
??????????????? return;
??????????? }
????????????????
??????? }

?

??????????????? private void laplace算子ToolStripMenuItem_Click(object sender, EventArgs e)
??????? {
??????????? if (imageBox1.Image != null)
??????????? {
??????????????? Image<Bgr, float> imglaplace = src.Laplace(3);
??????????????? imageBox1.Image = imglaplace;
??????????? }
??????????? else?
??????????? {
??????????????? System.Windows.Forms.MessageBox.Show("請先打開圖像!");
??????????????? return;
??????????? }

??????? }


???????????? private void canny算子ToolStripMenuItem_Click(object sender, EventArgs e)
??????? {
??????????? if (imageBox1.Image != null)
??????????? {

??????????????? //CannyFrm cfrm = new CannyFrm();
??????????????? //cfrm.Show();
??????????????? double m = 90;
??????????????? double n = 255;
??????????????? Gray threshold1 = new Gray(m);
??????????????? Gray threshold2 = new Gray(n);
??????????????? Image<Gray, byte> imggray = src.Convert<Gray, byte>();
??????????????? Image<Gray, byte> imgcanny = imggray.Canny(threshold1, threshold2);
????????????????
??????????????? imageBox1.Image = imgcanny;
???????????????

??????????? }
??????????? else
??????????????? System.Windows.Forms.MessageBox.Show("請先輸入圖像!");
??????? }

?

??????? private void 二值化后繪制圖像輪廓ToolStripMenuItem_Click(object sender, EventArgs e)
??????? {
??????????? if (imageBox1.Image != null)
??????????? {
??????????????? Image<Gray, byte> imggray = src.Convert<Gray, byte>();
??????????????? Image<Gray, byte> img2b = imggray.ThresholdBinary(new Gray(60), new Gray(255));
??????????????? Contour<Point> contour = img2b.FindContours();??????? //找輪廓
??????????????? Image<Bgr, byte> imgbgr = img2b.Convert<Bgr, byte>();
??????????????? int maxLevel = 0;?????????????????????????????????????????????????????????? //繪制的輪廓深度
??????????????? imgbgr.Draw(contour, new Bgr(0, 0, 255), new Bgr(0, 0, 255), maxLevel, 1);//繪制圖像輪廓
??????????????? imageBox1.Image = imgbgr;????????????????????????????
??????????? }
??????????? else
??????????????? System.Windows.Forms.MessageBox.Show("請先輸入圖像!");
??????? }

?

?

??????? private void SmoothBlur算子ToolStripMenuItem_Click(object sender, EventArgs e)
??????? {
??????????? if (imageBox1.Image != null)
??????????? {
??????????????? Image<Bgr, byte> imgsmoot = src.SmoothBlur(9, 9);
??????????????? imageBox1.Image = imgsmoot;
?
??????????? }
??????? }

?

??????? private void button1_Click(object sender, EventArgs e)
??????? {
??????????? OpenFileDialog opdlg = new OpenFileDialog();
??????????? opdlg.Filter = "所有文件|*.*";
??????????? if (opdlg.ShowDialog() == DialogResult.OK)
??????????? {
??????????????? Image<Bgr, byte> imgrgb = new Image<Bgr, byte>(opdlg.FileName);
??????????????? imageBox2.Image = imgrgb;
??????????????? subsrc = imgrgb;
??????????? }
??????? }

?

//六種模板匹配方法

??????? private void radioButton1_CheckedChanged(object sender, EventArgs e)
??????? {
??????????? if (imageBox1.Image != null && imageBox2.Image != null)
??????????? {
??????????????? double time;
??????????????? double totalTime = 0;
??????????????? Stopwatch sw = new Stopwatch();
??????????????? sw.Start();
?
??????????????? //輸入圖像????????????
??????????????? Image<Bgr, Byte> imageInput =src ;
??????????????? //模板圖像????????????
??????????????? Image<Bgr, Byte> imageTemplate = subsrc;

??????????????
??????????????? //匹配方式數組
??????????????? TM_TYPE tmType = TM_TYPE.CV_TM_CCOEFF;

??????????????? //輸出圖像(匹配結果)
??????????????? Image<Gray, Single> imageResult = imageInput.MatchTemplate(imageTemplate,tmType);?????????????

??????????????? //歸一化結果???????????????
??????????????? //CvInvoke.cvNormalize(imageResult.Ptr, imageResult.Ptr, 1d, 0d, NORM_TYPE.CV_MINMAX, IntPtr.Zero);
??????????????? //imageBox1.Image = imageResult;
????????????????
??????????????? //找到最匹配的點,以及該點的值????????????????
??????????????? double bestValue;????????????????
??????????????? Point bestPoint;????????????????
??????????????? FindBestMatchPointAndValue(imageResult, tmType, out bestValue, out bestPoint);
??????????????? //在最匹配的點附近畫一個跟模板一樣大的矩形????????????????
??????????????? Rectangle rect = new Rectangle(new Point(bestPoint.X - imageTemplate.Size.Width / 2, bestPoint.Y - imageTemplate.Size.Height / 2), imageTemplate.Size);???????????????
??????????????? imageResult.Draw(rect, new Gray(bestValue), 2);
??????????????? imageBox1.Image = imageResult;???????????????
??????????????? time = sw.Elapsed.TotalMilliseconds;?
??????????????? totalTime += time;
??????????????? sw.Stop();
??????????????? textBox1.Text = totalTime.ToString();
??????????????? sw.Reset();
?????????
??????????? }
??????????? else
??????????????? System.Windows.Forms.MessageBox.Show("請先輸入圖像!");

??????? }
??????
???????? private void radioButton2_CheckedChanged(object sender, EventArgs e)
???????? {
???????????? if (imageBox1.Image != null && imageBox2.Image != null)
???????????? {
???????????????? //輸入圖像????????????
???????????????? Image<Bgr, Byte> imageInput = src;
???????????????? //模板圖像????????????
???????????????? Image<Bgr, Byte> imageTemplate = subsrc;

???????????????? TM_TYPE tmtype = TM_TYPE.CV_TM_CCORR;

???????????????? Image<Gray, Single> imgResult = imageInput.MatchTemplate(imageTemplate, tmtype);

???????????????? //找到最好的點和最好點的值
???????????????? double bestValue;
???????????????? Point bestPoint;
???????????????? FindBestMatchPointAndValue(imgResult,tmtype,out? bestValue,out? bestPoint);

???????????????? Rectangle rect = new Rectangle(new Point(bestPoint.X - imageTemplate.Width / 2, bestPoint.Y - imageTemplate.Height / 2), imageTemplate.Size);
???????????????? imgResult.Draw(rect, new Gray(bestValue), 2);
???????????????? imageBox1.Image = imgResult;

???????????? }
???????????? else
???????????? {
???????????????? System.Windows.Forms.MessageBox.Show("請先輸入圖像!");
???????????? }
???????? }

?

???????? private void radioButton3_CheckedChanged(object sender, EventArgs e)
???????? {
?????????????? if(imageBox1.Image!=null && imageBox2.Image!=null)
?????????????? {
?????????????????? //輸入圖像????????????
???????????????? Image<Bgr, Byte> imageInput = src;
???????????????? //模板圖像????????????
???????????????? Image<Bgr, Byte> imageTemplate = subsrc;

???????????????? TM_TYPE tmtype = TM_TYPE.CV_TM_SQDIFF;

???????????????? Image<Gray, Single> imgResult = imageInput.MatchTemplate(imageTemplate, tmtype);

???????????????? //找到最好的點和最好點的值
???????????????? double bestValue;
???????????????? Point bestPoint;
???????????????? FindBestMatchPointAndValue(imgResult,tmtype,out? bestValue,out? bestPoint);

???????????????? Rectangle rect = new Rectangle(new Point(bestPoint.X - imageTemplate.Width / 2, bestPoint.Y - imageTemplate.Height / 2), imageTemplate.Size);
???????????????? imgResult.Draw(rect, new Gray(bestValue), 2);
???????????????? imageBox1.Image = imgResult;
????????????

???????????? }
???????????? else
???????????? {
???????????????? System.Windows.Forms.MessageBox.Show("請先輸入圖像!");
???????????? }
???????? }

?

???????? private void radioButton4_CheckedChanged(object sender, EventArgs e)
???????? {
???????????? if (imageBox1.Image != null && imageBox2.Image != null)
???????????? {
???????????????? //輸入圖像????????????
???????????????? Image<Bgr, Byte> imageInput = src;
???????????????? //模板圖像????????????
???????????????? Image<Bgr, Byte> imageTemplate = subsrc;

???????????????? TM_TYPE tmtype = TM_TYPE.CV_TM_CCOEFF_NORMED;

???????????????? Image<Gray, Single> imgResult = imageInput.MatchTemplate(imageTemplate, tmtype);

???????????????? //找到最好的點和最好點的值
???????????????? double bestValue;
???????????????? Point bestPoint;
???????????????? FindBestMatchPointAndValue(imgResult, tmtype, out? bestValue, out? bestPoint);

???????????????? Rectangle rect = new Rectangle(new Point(bestPoint.X - imageTemplate.Width / 2, bestPoint.Y - imageTemplate.Height / 2), imageTemplate.Size);
???????????????? imgResult.Draw(rect, new Gray(bestValue), 2);
???????????????? imageBox1.Image = imgResult;
????????????????
???????????? }
???????????? else
???????????? {
???????????????? System.Windows.Forms.MessageBox.Show("請先輸入圖像!");
???????????? }
???????? }

?

???????? private void radioButton5_CheckedChanged(object sender, EventArgs e)
???????? {
???????????? if (imageBox1.Image != null && imageBox2.Image != null)
???????????? {
???????????????? //輸入圖像????????????
???????????????? Image<Bgr, Byte> imageInput = src;
???????????????? //模板圖像????????????
???????????????? Image<Bgr, Byte> imageTemplate = subsrc;

???????????????? TM_TYPE tmtype = TM_TYPE.CV_TM_CCORR_NORMED;

???????????????? Image<Gray, Single> imgResult = imageInput.MatchTemplate(imageTemplate, tmtype);

???????????????? //找到最好的點和最好點的值
???????????????? double bestValue;
???????????????? Point bestPoint;
???????????????? FindBestMatchPointAndValue(imgResult, tmtype, out? bestValue, out? bestPoint);

???????????????? Rectangle rect = new Rectangle(new Point(bestPoint.X - imageTemplate.Width / 2, bestPoint.Y - imageTemplate.Height / 2), imageTemplate.Size);
???????????????? imgResult.Draw(rect, new Gray(bestValue), 2);
???????????????? imageBox1.Image = imgResult;

???????????? }
???????????? else
???????????? {
???????????????? System.Windows.Forms.MessageBox.Show("請先輸入圖像!");
???????????? }
???????? }

???????? private void radioButton6_CheckedChanged(object sender, EventArgs e)
???????? {
???????????? if (imageBox1.Image != null && imageBox2.Image != null)
???????????? {
???????????????? //輸入圖像????????????
???????????????? Image<Bgr, Byte> imageInput = src;
???????????????? //模板圖像????????????
???????????????? Image<Bgr, Byte> imageTemplate = subsrc;

???????????????? TM_TYPE tmtype = TM_TYPE.CV_TM_SQDIFF_NORMED;

???????????????? Image<Gray, Single> imgResult = imageInput.MatchTemplate(imageTemplate, tmtype);

???????????????? //找到最好的點和最好點的值
???????????????? double bestValue;
???????????????? Point bestPoint;
???????????????? FindBestMatchPointAndValue(imgResult, tmtype, out? bestValue, out? bestPoint);

???????????????? Rectangle rect = new Rectangle(new Point(bestPoint.X - imageTemplate.Width / 2, bestPoint.Y - imageTemplate.Height / 2), imageTemplate.Size);
???????????????? imgResult.Draw(rect, new Gray(bestValue), 2);
????????????????
???????????? }
???????????? else
???????????? {
???????????????? System.Windows.Forms.MessageBox.Show("請先輸入圖像!");
???????????? }
???????? }

???????? private void FindBestMatchPointAndValue(Image<Gray, Single> image, TM_TYPE tmType, out double bestValue, out Point bestPoint)
???????? {
???????????? bestValue = 0d;
???????????? bestPoint = new Point(0, 0);
???????????? double[] minValues, maxValues;
???????????? Point[] minLocations, maxLocations;
???????????? image.MinMax(out minValues, out maxValues, out minLocations, out maxLocations);
???????????? //對于平方差匹配和歸一化平方差匹配,最小值表示最好的匹配;其他情況下,最大值表示最好的匹配????????????
???????????? if (tmType == TM_TYPE.CV_TM_SQDIFF || tmType == TM_TYPE.CV_TM_SQDIFF_NORMED)
???????????? {
???????????????? bestValue = minValues[0];
???????????????? bestPoint = minLocations[0];
???????????? }
???????????? else
???????????? {
???????????????? bestValue = maxValues[0];
???????????????? bestPoint = maxLocations[0];
???????????? }
???????? }

???????? private void 圖像對比ToolStripMenuItem_Click(object sender, EventArgs e)
???????? {
???????????? CompareFrm cpfrm = new CompareFrm();
???????????? cpfrm.Show();
???????? }


?????
??? }
}


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/387653.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/387653.shtml
英文地址,請注明出處:http://en.pswp.cn/news/387653.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android:IntentService的學習

在Android的四大組件中&#xff0c;Service排行老二&#xff0c;在Android中的主要作用是后臺服務&#xff0c;進行與界面無關的操作。由于Service運行在主線程&#xff0c;所以進行異步操作需要在子線進行。為此Android為我們提供了IntentService。 IntentService是一個抽象類…

智能商業大會構造信息化交流平臺

在快速發展的當今社會&#xff0c;所有事物都在日新月異地變化著&#xff0c;相較于過去的傳統商業的變化速度&#xff0c;現今基于數據的互聯網商業變化速度高出了一個量級&#xff0c;同時市場對于企業的應對速度也有了更高的要求&#xff0c;然而面對大體量的數據&#xff0…

itcast-ssh-crm實踐

分析 BaseDao 文件上傳 轉載于:https://www.cnblogs.com/hellowq/p/10209761.html

分類器大牛們

David Lowe&#xff1a;Sift算法的發明者&#xff0c;天才。 Rob Hess&#xff1a;sift的源碼OpenSift的作者&#xff0c;個人主頁上有openSift的下載鏈接&#xff0c;Opencv中sift的實現&#xff0c;也是參考這個。 Koen van de Sande&#xff1a;作者給出了sift,densesift,co…

go 成長路上的坑(1)

一、先來看一段代碼 package mainimport "fmt"type X struct{}func (x *X) test(){println("h1",x) } func main(){a : X{} a.test()(&X{}).test()(X{}).test() } 猜猜他的結果 二、揭曉答案 package mainimport "fmt"type X struct{}func (…

利用python腳本程序監控文件被修改

需求&#xff1a;利用python編寫監控程序&#xff0c;監控一個文件目錄&#xff0c;當目錄下的文件發生改變時&#xff0c;實現有修改就發報警郵件 郵件使用QQ郵箱&#xff0c;需要開啟smtp&#xff0c;使用手機發生短信&#xff0c;騰訊會給你發郵箱密碼。如下所示&#xff1a…

Oracle RAC

環境如下&#xff1a; Linux操作系統&#xff1a;Centos 6.5 64bit &#xff08;這個版本的redhat 6內核等OS在安裝grid最后執行root.sh時會出現crs-4124&#xff0c;是oracle11.2.0.1的bug&#xff09; VMware version&#xff1a;Workstation 8.0.3 build-703057 Oracle…

好程序員web前端分享MVVM框架Vue實現原理

好程序員web前端分享MVVM框架Vue實現原理&#xff0c;Vue.js是當下很火的一個JavaScript MVVM庫&#xff0c;它是以數據驅動和組件化的思想構建的。相比于Angular.js和react.js更加簡潔、更易于理解的API&#xff0c;使得我們能夠快速地上手并使用Vue.js。?1.什么是MVVM呢&…

HDU - 3516 Tree Construction

HDU - 3516 思路&#xff1a; 平行四邊形不等式優化dp &#xff1a;&#xff09; 代碼&#xff1a; #pragma GCC optimize(2) #pragma GCC optimize(3) #pragma GCC optimize(4) #include<bits/stdc.h> using namespace std; #define y1 y11 #define fi first #define se…

各類總線傳輸速率

1. USB總線 USB1.1&#xff1a; -------低速模式(low speed)&#xff1a;1.5Mbps -------全速模式(full speed)&#xff1a; 12Mbps USB2.0&#xff1a;向下兼容。增加了高速模式&#xff0c;最大速率480Mbps。 -------高速模式(high speed)&#xff1a; 25~480Mbps US…

Activiti多人會簽例子

Activiti中提供了多實例任務&#xff08;for-each&#xff09;將多實例應到到UserTask中可以實現會簽功能。 Multi-instance (for each) Description A multi-instance activity is a way of defining repetition for a certain step in a business process. In programming …

Django 【認證系統】auth

本篇內容 介紹Django框架提供的auth 認證系統 方法&#xff1a; 方法名 備注 create_user 創建用戶 authenticate 登錄驗證 login 記錄登錄狀態 logout 退出用戶登錄 is_authenticated 判斷用戶是否登錄 login_required裝飾器 進行登錄判斷 引入模塊 from django.…

兒科常見疾病的中成藥療法

孩子感冒&#xff0c;分清寒熱是關鍵——兒童風寒感冒和風熱感冒的中成藥內服外治法 兒童不養兒不知父母恩&#xff0c;每個人恐怕都只有自己做了父母&#xff0c;才能感受到父母的愛。嬰幼兒正處于最初的發育期&#xff0c;抵抗力弱&#xff0c;有個感冒發燒的也是常有的事兒。…

物化視圖

有個項目因為有比較多的查詢匯總&#xff0c;考慮到速度&#xff0c;所以使用了物化視圖。簡單的把用到的給整理了下。先看簡單創建語句&#xff1a;create materialized view mv_materialized_test refresh force on demand start with sysdate nextto_date(concat(to_char( s…

為什么直接ping知乎的ip不能訪問知乎的網站,而百度就可以?

結論&#xff1a; 簡單的說&#xff0c;就是baidu有錢。 正文&#xff1a; 大型網站依靠自身稀稀落落的服務器很難滿足網頁“秒開”的用戶需求&#xff0c;會加入CDN加速的隊伍。 當用戶訪問 http://www.zhihu.com 時&#xff0c;域名解析到距離用戶最近的CDN服務器的公網IP&am…

皮膚病

小偏方治百病/《國醫絕學健康館》編委會編.—重慶&#xff1a;重慶出版社&#xff0c;2010.3&#xff08;國醫絕學健康館&#xff09; 濕疹 苦參湯熏洗治陰囊濕疹方 苦參、蛇麻子中藥各50克&#xff0c;混合后&#xff0c;在晚上煎湯&#xff0c;可直接放在臉盆中煎。煎好后&am…

MySQL-ProxySQL中間件(一)| ProxySQL基本概念

目錄 MySQL-ProxySQL中間件&#xff08;一&#xff09;| ProxySQL基本概念&#xff1a; https://www.cnblogs.com/SQLServer2012/p/10972593.htmlMySQL-ProxySQL中間件&#xff08;二&#xff09;| Admin Schemas介紹&#xff1a;https://www.cnblogs.com/SQLServer2012/p/109…

01 ftp上傳簡單示例服務端

import json import socket import structserver socket.socket() server.bind((127.0.0.1,8001)) server.listen() conn,addr server.accept()#首先接收文件的描述信息的長度 struct_data_len conn.recv(4) data_len struct.unpack(i,struct_data_len)[0]# 通過文件信息的…

標簽td設置隱藏(hidden)

這樣設置這個td就不會被其他的td給擠掉了! 還有一種方法就是把tr標簽的solid設置為0px 這個方法把td標簽的left,right,bottom,top的邊框的solid全部設置為0px;轉載于:https://www.cnblogs.com/tranquilityMan/p/10972811.html

Windows Server 2008 NFS

打開Windows Server 2008的Dos運行窗口&#xff08;不是powershell&#xff09;&#xff0c;然后鍵入&#xff1a; servermanagercmd.exe -install FS-NFS-Services 安裝完畢之后&#xff0c;就要把NFS的存貯映射到Windows Server 2008上某個盤符以供使用&#xff0c;但為了…