halcon wpf编程_采用HALCON机器视觉软件及C#语言检测工件位置的方法 (之四)

四.工件位置检测算法

我们希望得到模块右下角第一个插座中的最右侧的插针位置P(m,n)及该插座相对于夹具的角度a。如图31所示。图31 插针坐标与工件的夹角

因为插针有一定的长度,且镜头有变形,直接检测插针前端面的位置偏差较大;且插针前端面的面积小、形状一致性不好,不容易检测。

经分析,插座底部塑料基座的形状特征明显,采用形状匹配函数检测插座底部图像,间接检测插针位置检测,试验证明成功率高。

为提高检测精度和可靠性,采用形状匹配函数检测三个特征图像的位置,并用其中2个位置坐标计算插针坐标P和工件夹角a。这样比仅检测一个特征图像的位置和转角的方法可靠。

1. 偏移量(offset)的设置与计算图32 插针位置的偏移量

如图32所示,点A、C为最右边的2个检测结果的中心坐标。P点是最右侧的插针位置。

1)设置偏移量

已知:a, b, c, d, m, n

求:q, L

由:k1 = (n - b) / (m - a)

k2 = (b - d) / (a - c)

得:q = arctan( (k1 – k2) / (1 + k1 X k2) ) (1)

及:

q、L是相对于插座的尺寸,与工件夹角a无关。

因此,选取好模板、进行一次匹配后,只需手动设置一次插针位置P(m,n);之后,进行插针位置检测时,用A点、C点的坐标及q、L,即可求出P和a。

2)计算偏移量

已知:a, b, c, d, q, L

求:a, m, n

a = arctan((b - d) / (a - c)) (3)

b = q + a

m = a + L X cos(b) (4)

n = b + L X sin(b)

1. C#程序设计

1)将HDevelop的程序My_shape_match导出C#代码如下:

//

// File generated by HDevelop for HALCON/DOTNET (C#) Version 10.0

//

// This file is intended to be used with the HDevelopTemplate or

// HDevelopTemplateWPF projects located under %HALCONEXAMPLES%\c#

using System;

using HalconDotNet;

public partial class HDevelopExport

{

public HTuple hv_ExpDefaultWinHandle;

// Procedures

// External procedures

// Chapter: Matching / Shape-Based

// Short Description: Display the results of Shape-Based Matching.

public void dev_display_shape_matching_results (HTuple hv_ModelID, HTuple hv_Color, HTuple hv_Row, HTuple hv_Column, HTuple hv_Angle, HTuple hv_ScaleR, HTuple hv_ScaleC, HTuple hv_Model)

{

// 该方法的功能是:显示形状匹配的结果。

// 对应于HDevelop中的函数dev_display_shape_matching_results()

// 不使用该方法,程序略。

}

// Chapter: Graphics / Text

// Short Description: This procedure writes a

text message.

public void disp_message (HTuple hv_WindowHandle, HTuple hv_String, HTuple hv_CoordSystem, HTuple hv_Row, HTuple hv_Column, HTuple

hv_Color, HTuple hv_Box)

{

// 该方法的功能是:在窗体中显示文本信息。

// 对应于HDevelop中的函数disp_message()

// 不使用该方法,程序略。

}

// Main procedure

private void action()

{

HSystem sys = new HSystem();

// Local iconic variables

HObject ho_Image=null, ho_Rectangle=null,

ho_ImageReduced=null;

HObject ho_Mask, ho_Cross;

// Local control variables

HTuple hv_Error = null;

HTuple hv_AcqHandle, hv_WindowID=new HTuple();

HTuple hv_Button, hv_R=new HTuple(), hv_C=new HTuple();

HTuple hv_Row1=new HTuple(), hv_Column1=new HTuple(), hv_Row2=new HTuple();

HTuple hv_Column2=new HTuple(), hv_ModelID,

hv_S1, hv_Row;

HTuple hv_Column, hv_Angle, hv_Score, hv_S2, hv_Runtime;

HTuple hv_y0, hv_y1, hv_x0, hv_x1;

// Initialize local and output iconic variables

HOperatorSet.GenEmptyObj(out ho_Image);

HOperatorSet.GenEmptyObj(out ho_Rectangle);

HOperatorSet.GenEmptyObj(out ho_ImageReduced);

HOperatorSet.GenEmptyObj(out ho_Mask);

HOperatorSet.GenEmptyObj(out ho_Cross);

try

{

HOperatorSet.CloseAllFramegrabbers();

//open camera with default settings:

HOperatorSet.OpenFramegrabber("DahengCAM", 1, 1, 0, 0, 0, 0,

"default", -1, "default", -1, "default", "default", "default", -1, -1, out

hv_AcqHandle);

//open a window

//dev_close_window(...);

//dev_open_window(...);

//Define the region fill mode as margin

HOperatorSet.SetDraw(hv_ExpDefaultWinHandle, "margin");

hv_Button = 0;

while ((int)(new HTuple(hv_Button.TupleEqual(0))) != 0)

{

// Grabbing images from a Daheng USB 2.0 camera

ho_Image.Dispose();

HOperatorSet.GrabImage(out ho_Image, hv_AcqHandle);

HOperatorSet.DispObj(ho_Image, hv_ExpDefaultWinHandle);

disp_message(hv_ExpDefaultWinHandle, "Load a old template press LEFT key, Set a new template press middle key", "window", 12, 12, "black", "true");

//draw a rectangle to select region of testing.

HOperatorSet.DispRectangle1(hv_ExpDefaultWinHandle, 320, 250, 630, 750);

// 扫描鼠标按键

{

// 不使用该方法,程序略。

}

}

// 鼠标中键按下,设置新的形状模板

if ((int)(new HTuple(hv_Button.TupleEqual(2))) != 0)

{

disp_message(hv_ExpDefaultWinHandle, "draw rectangle for shape model. ",

"window", 12, 12, "black", "true");

HOperatorSet.DrawRectangle1(hv_ExpDefaultWinHandle, out hv_Row1, out

hv_Column1, out hv_Row2, out hv_Column2);

ho_Rectangle.Dispose();

HOperatorSet.GenRectangle1(out ho_Rectangle, hv_Row1, hv_Column1, hv_Row2,

hv_Column2);

ho_ImageReduced.Dispose();

HOperatorSet.ReduceDomain(ho_Image, ho_Rectangle, out ho_ImageReduced);

HOperatorSet.WriteImage(ho_ImageReduced, "png", 0, "D:/Vision/MySample/MyShapeMatch/Image006.png");

}

// 鼠标左键按下,读取硬盘中已有的形状模板

if ((int)(new HTuple(hv_Button.TupleEqual(1))) != 0)

{

disp_message(hv_ExpDefaultWinHandle, "Load a old template ",

"window", 12, 12, "black", "true");

ho_ImageReduced.Dispose();

HOperatorSet.ReadImage(out ho_ImageReduced, "Image006.png");

}

// 生成一个待检测的小区域,被检测区域的图像名为Mask

ho_Rectangle.Dispose();

HOperatorSet.GenRectangle1(out ho_Rectangle, 320, 250, 630, 750);

ho_Mask.Dispose();

HOperatorSet.ReduceDomain(ho_Image, ho_Rectangle, out ho_Mask);

// 生成形状模板、形状匹配

HOperatorSet.CreateShapeModel(ho_ImageReduced, "auto", (new

HTuple(-45)).TupleRad() , (new HTuple(90)).TupleRad(), "auto", "auto", "use_polarity", "auto", "auto", out hv_ModelID);

HOperatorSet.CountSeconds(out hv_S1);

HOperatorSet.FindShapeModel(ho_Mask, hv_ModelID, (new HTuple(-45)).TupleRad()

, (new HTuple(90)).TupleRad(), 0.5, 3, 0.0, "least_squares", 0, 0.5, out hv_Row,

out hv_Column, out hv_Angle, out hv_Score);

HOperatorSet.CountSeconds(out hv_S2);

hv_Runtime = (hv_S2-hv_S1)*1000;

// 显示匹配结果

HOperatorSet.DispRectangle1(hv_ExpDefaultWinHandle, 320, 250, 630, 750);

dev_display_shape_matching_results(hv_ModelID, "green", hv_Row, hv_Column,

hv_Angle, 1, 1, 0);

ho_Cross.Dispose();

HOperatorSet.GenCrossContourXld(out ho_Cross, hv_Row, hv_Column, 26, (new HTuple(45)).TupleRad() );

HOperatorSet.SetColor(hv_ExpDefaultWinHandle, "red");

HOperatorSet.DispObj(ho_Cross, hv_ExpDefaultWinHandle);

hv_y0 = hv_Row[0];

hv_y1 = hv_Row[1];

hv_x0 = hv_Column[0];

hv_x1 = hv_Column[1];

HOperatorSet.DispLine(hv_ExpDefaultWinHandle, hv_y0, hv_x0, hv_y1,

hv_x1);

disp_message(hv_ExpDefaultWinHandle, ((new HTuple(hv_Score.TupleLength())+" shapes located in ")+(hv_Runtime.TupleString( ".1f")))+" ms ",

"window", 12, 12, "black", "true");

HOperatorSet.ClearShapeModel(hv_ModelID);

HOperatorSet.CloseFramegrabber(hv_AcqHandle);

}

catch (HalconException

HDevExpDefaultException)

{

ho_Image.Dispose();

ho_Rectangle.Dispose();

ho_ImageReduced.Dispose();

ho_Mask.Dispose();

ho_Cross.Dispose();

throw HDevExpDefaultException;

}

ho_Image.Dispose();

ho_Rectangle.Dispose();

ho_ImageReduced.Dispose();

ho_Mask.Dispose();

ho_Cross.Dispose();

}

public void InitHalcon()

{

// Default settings used in HDevelop

HOperatorSet.SetSystem("do_low_error", "false");

}

public void RunHalcon(HTuple Window)

{

hv_ExpDefaultWinHandle = Window;

action();

}

}

1)将导出的C#代码整理编写几个功能独立的方法

如本文第二节“HALCON与C#混合编程的方法”所述,在Program.cs程序中编写类HDevelopExport;然后在其中根据导出的C#代码编写以下几个方法:halcon初始化InitHalcon();摄像头初始化InitCamera(HTuple Window);采集图像、显示图像GrabAndDisplay();设置模板SetTemplate();从文件中读取模板ReadTemplate();模板匹配MatchTemplate();显示偏移点位置DisplayOffset();显示计算角度,划线DisplayLine();关闭相机CloseCamera()。

形状匹配函数的参数设置与本文三.5.节的相同。

Program.cs程序如下所示:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Windows.Forms;

using HalconDotNet;

namespace MatchMyTemplate

{

public partial class HDevelopExport

{

// 定义变量

public HTuple hv_ExpDefaultWinHandle;

HObject ho_Image, ho_Rectangle, ho_ImageReduced;

HObject ho_ModelContours;

HObject ho_Mask;

HTuple hv_AcqHandle;

HTuple hv_S1, hv_S2;

HTuple hv_Width, hv_Height;

HTuple hv_ModelID, hv_Row, hv_Column;

HTuple hv_Row1, hv_Column1, hv_Row2, hv_Column2;

HTuple hv_Angle, hv_Score, hv_Runtime;

HTuple hv_R;

public double[] Px;

public double[] Py;

public int ResultN;

public double usedTime;

public double Cx,Cy;

public double Fx,Fy;

public double Cr = 9;

public void InitHalcon() // 初始化halcon

{

HOperatorSet.SetSystem("do_low_error", "false");

}

public void InitCamera(HTuple Window) // 摄像头初始化

{

hv_ExpDefaultWinHandle = Window;

HOperatorSet.GenEmptyObj(out ho_Image); // 生ho_Image数据区

// Grabbing images from a Daheng USB 2.0 camera

HOperatorSet.CloseAllFramegrabbers();

HOperatorSet.OpenFramegrabber("DahengCAM", 1, 1, 0, 0, 0, 0, "default", -1, "default",-1, "default", "default", "default", -1, -1, out hv_AcqHandle);

//open camera with default settings

}

public void GrabAndDisplay() // 采集图像、显示图像

{

ho_Image.Dispose(); // 清除ho_Image中的数据

HOperatorSet.GrabImage(out ho_Image, hv_AcqHandle); // 采集图像

HOperatorSet.GetImageSize(ho_Image, out hv_Width, out hv_Height); // 获取图片的尺寸

HOperatorSet.SetPart(hv_ExpDefaultWinHandle, 0, 0, hv_Height - 1, hv_Width - 1);

HOperatorSet.DispObj(ho_Image, hv_ExpDefaultWinHandle);

// 显示ho_Image中的图片

HOperatorSet.SetDraw(hv_ExpDefaultWinHandle,"margin"); // 填充模式为只画框

HOperatorSet.SetColor(hv_ExpDefaultWinHandle,"red"); // 画线颜色红

HOperatorSet.DispRectangle1(hv_ExpDefaultWinHandle,320, 250, 630, 750);

}

public void SetTemplate() // 设置模板

{

MessageBox.Show("在红框中按下鼠标左键画方框选模板,按右键结束");

HOperatorSet.DrawRectangle1(hv_ExpDefaultWinHandle,

out hv_Row1, out hv_Column1, out hv_Row2, out hv_Column2);

HOperatorSet.GenRectangle1(out ho_Rectangle, hv_Row1, hv_Column1, hv_Row2,

hv_Column2);

HOperatorSet.ReduceDomain(ho_Image, ho_Rectangle, out ho_ImageReduced);

HOperatorSet.WriteImage(ho_ImageReduced, "png", 0, "D:/Vision/MySample/ShapeMatch/Image005.png");

MessageBox.Show("模板已保存");

}

public void CloseCamera() // 关闭相机

{

HOperatorSet.CloseFramegrabber(hv_AcqHandle);

}

public void ReadTemplate() // 从文件中读取模板

{

HOperatorSet.GenEmptyObj(out ho_ImageReduced);

ho_ImageReduced.Dispose();

HOperatorSet.ReadImage(out ho_ImageReduced, "D:/Vision/MySample/ShapeMatch/Image005.png");

}

public void MatchTemplate() // 模板匹配

{

int i,j;

double temp;

HOperatorSet.GenRectangle1(out ho_Rectangle, 320, 250, 630, 750);

HOperatorSet.ReduceDomain(ho_Image, ho_Rectangle, out ho_Mask);

//Reduce image range

HOperatorSet.CreateShapeModel(ho_ImageReduced, "auto", (new HTuple(-45)).TupleRad() ,(new HTuple(90)).TupleRad(),

"auto", "auto","use_polarity", "auto", "auto",out hv_ModelID);

HOperatorSet.CountSeconds(out hv_S1); // Match start

HOperatorSet.FindShapeModel(ho_Mask,hv_ModelID, (new HTuple(-45)).TupleRad()

,(new HTuple(90)).TupleRad(),0.5, 3, 0.0, " least_squares ", 0,0.5, out hv_Row,

out hv_Column, outhv_Angle, out hv_Score);

HOperatorSet.CountSeconds(out hv_S2); // Match stop

hv_Runtime = (hv_S2 - hv_S1) * 1000;

usedTime = hv_Runtime;

ResultN = new HTuple(hv_Row.TupleLength());

// 获取匹配结果个数

if (ResultN >1)

{

Px = new double[ResultN];

Py = new double[ResultN];

hv_R = new HTuple(); //HTuple变量初始化

for (i = 0; i < ResultN; i++)

{

Py[i] = hv_Row[i]; // 将搜索结果的中心坐标读出

Px[i] = hv_Column[i];

Aa[i] = hv_Angle[i];

hv_R[i] = 8; //设置圆半径

}

for (i = 0; i < ResultN; i++) // 从大到小排序

{

for (j = i + 1; j < ResultN; j++)

{

if (Px[i] < Px[j])

{

temp = Px[i];

Px[i] = Px[j];

Px[j] = temp;

temp = Py[i];

Py[i] = Py[j];

Py[j] = temp;

temp = Aa[i];

Aa[i] = Aa[j];

Aa[j] = temp;

}

}

}

HOperatorSet.SetColor(hv_ExpDefaultWinHandle,"red"); // 显示匹配结果位置

HOperatorSet.DispCircle(hv_ExpDefaultWinHandle,hv_Row, hv_Column, hv_R);

HOperatorSet.ClearShapeModel(hv_ModelID);

Cy = Py[0]+ 10; // offset的初始值

Cx = Px[0]+ 60;

}

else

{

MessageBox.Show("匹配失败!?");

}

}

public void DisplayOffset() // 显示偏移点位置

{

HOperatorSet.SetColor(hv_ExpDefaultWinHandle, "red");

HOperatorSet.DispObj(ho_Image,hv_ExpDefaultWinHandle);

HOperatorSet.DispCircle(hv_ExpDefaultWinHandle,hv_Row, hv_Column, hv_R);

HOperatorSet.DispLine(hv_ExpDefaultWinHandle,hv_Row[0], hv_Column[0], hv_Row[1], hv_Column[1]);

HOperatorSet.SetColor(hv_ExpDefaultWinHandle,"yellow");

HOperatorSet.DispCircle(hv_ExpDefaultWinHandle,Cy, Cx, Cr);

}

public void DisplayLine() //显示计算角度,划线

{

HOperatorSet.SetColor(hv_ExpDefaultWinHandle, "yellow");

HOperatorSet.DispLine(hv_ExpDefaultWinHandle, Cy, Cx, Fy, Fx);

}

}

static class Program

{

static void Main()

{

Application.EnableVisualStyles();

Application.SetCompatibleTextRenderingDefault(false);

Application.Run(new Form1());

}

}

}

2)设计窗体及代码

设计窗体如图33所示。Form1.cs程序代码如下:

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;

namespace MatchMyTemplate

{

public partial class Form1 : Form

{

HDevelopExport hd = new HDevelopExport();

public double Lam,

theta;

public Form1()

{

InitializeComponent();

}

private void button1_Click(object sender, EventArgs e) // 打开相机

{

hd.InitCamera(hWindowControl1.HalconWindow); // 摄像头初始化

timer1.Enabled = true; // 开始定时拍照显示

textBox1.Text = "";

textBox1.Refresh();

}

private void timer1_Tick(object sender, EventArgs e) // 定时拍照、显示

{

hd.GrabAndDisplay();

}

private void button3_Click(object sender, EventArgs e) // 关闭相机

{

imer1.Enabled = false;

}

private void Form1_FormClosed(object sender, FormClosedEventArgs e) // 关闭窗体

{

timer1.Enabled = false; // 停止拍照

hd.CloseCamera();

}

private void button2_Click(object sender, EventArgs e) // 图像匹配、计算2点距离

{

int j, m;

double d;

double[] x;

double[] y;

timer1.Enabled = false; // 停止拍照

hd.MatchTemplate(); // 图像匹配

button2.Visible = false;

m = hd.ResultN;

x = new double[m];

y = new double[m];

for (j = 0; j < m; j++) // 获取匹配坐标

{

x[j] = hd.Px[j];

y[j] = hd.Py[j];

}

textBox1.Text = "匹配时间:" + hd.usedTime.ToString("#####.##")

+ " ms" + "\r\n" + "\r\n";

for (j = 0; j < m; j++)

{

if (j < m - 1) // 计算2点距离

{

d = (y[j + 1] - y[j]) *(y[j + 1] - y[j]) + (x[j + 1] - x[j]) * (x[j + 1] - x[j]);

d = Math.Sqrt(d);

d = 0.0485 * d;

textBox1.Text =textBox1.Text + "d(" + Convert.ToString(j + 1) + ")

= " + d.ToString("#####.##")+ "mm" + "\r\n";

}

textBox1.Text = textBox1.Text +"x("+ Convert.ToString(j + 1) + ") = " + Convert.ToString(x[j])+" y(" + Convert.ToString(j+ 1) + ") = " + Convert.ToString(y[j]) + "\r\n"+ "\r\n";

}

}

private void button5_Click(object sender, EventArgs e) // 读模板

{

hd.ReadTemplate();

button2.Visible = true;

}

private void button4_Click(object sender, EventArgs e) // 设置模板

{

timer1.Enabled = false; // 停止拍照

hd.SetTemplate();

}

private void Form1_Load(object sender, EventArgs e) // 载入窗体

{

button2.Visible = false;

}

private void button7_Click(object sender, EventArgs e) // 计算偏移点位置

{

double a, b, c, d, m, n, k1, k2;

a = hd.Px[0];

b = hd.Py[0];

c = hd.Px[1];

d = hd.Py[1];

m = hd.Cx;

n = hd.Cy;

k1 = (b - n) / (a - m);

k2 = (d - b) / (c - a);

Lam = Math.Sqrt((b - n) * (b - n) +(a - m) * (a - m));

theta = Math.Atan((k1 - k2) / (1 + k1* k2));

}

private void button8_Click(object sender, EventArgs e) // 偏移点上移

{

hd.Cy = hd.Cy - 1;

hd.DisplayOffset();

}

private void button9_Click(object sender, EventArgs e) // 偏移点下移

{

hd.Cy = hd.Cy + 1;

hd.DisplayOffset();

}

private void button10_Click(object sender, EventArgs e) // 偏移点左移

{

hd.Cx = hd.Cx - 1;

hd.DisplayOffset();

}

private void button11_Click(object sender, EventArgs e) // 偏移点右移

{

hd.Cx = hd.Cx + 1;

hd.DisplayOffset();

}

private void button6_Click(object sender, EventArgs e) // 计算、显示插针位置和夹角

{

double a, b, c, d, m, n;

double alpha, beta;

a = hd.Px[0];

b = hd.Py[0];

c = hd.Px[1];

d = hd.Py[1];

alpha = Math.Atan((d - b) / (c - a));

beta = theta + alpha ;

m = a + Lam * Math.Cos(beta);

n = b + Lam * Math.Sin(beta);

textBox1.Text = textBox1.Text + " m = " + Convert.ToString(m) + "\r\n" + "

n = " + Convert.ToString(n) + "\r\n" + "alpha = " + Convert.ToString(alpha)

+ "\r\n" + " theta = " + Convert.ToString(theta)+ "\r\n" + " beta = " + Convert.ToString(beta);

hd.Cx = m;

hd.Cy = n;

hd.Fx = m-600*Math.Cos(alpha); // 计算夹角斜线的终点坐标

hd.Fy = n-600*Math.Sin(alpha);

hd.DisplayOffset(); // 显示插针位置

hd.DisplayLine(); // 显示工件夹角

}

}

}

1. 实验结果

如图33所示,首先点击“Camera On”键,打开相机,再点击“Set template”键,设置模板,模板图像如图34所示。图33 Form外观及模板设置 图34 模板图像

如果已经设置好模板,则点击“Load template”键,读取模板图像。然后,点击“Match”键开始匹配。

点击“adjust offset”区域中的四个按键,可以调整偏移点,如图35中的黄色圆点所示。将黄色圆点移动至最右边的插针位置上。点击“Set offset”计算偏移点的参数q、L。参见公式(1)、(2)。图35 插针位置检测结果

之后,开始检查插针位置及夹角。

关闭相机,然后再开启相机。将模块任意摆放,但右下角最右侧的4个插针必须在红色框内,如图33所示。

点击“Load template”键,再点击“Match”键,再点击“Display target”键,检测结果显示如图35所示。

图35中3个红色点是3个匹配结果的中心点。匹配时间为13毫秒左右。

经标定,1个像素为0.0485毫米。2个插针之间的公称距离为5毫米。检测数据表明:位置误差小于1个像素。最右侧插针位置由黄点标注,其坐标是由公式(4)计算而得。实验表明检测精度令人满意。

由于显示图像的窗体坐标y轴方向朝下,匹配结果的转角a方向定义与一般直角坐标相反。所以转角a为正时,计算得出的夹角alpha为负。夹角alpha是由公式(3)计算得出,和匹配函数给出的旋转角a(1)~a(3)有一定的偏差。但图35中的黄色线是根据alpha计算画出,显然,其精度更高。

五.小结

HALCON软件功能十分强大,使用比较简单;但没有中文说明书,读英语文档较费时间。由HALCON程序导出的C#代码,不能直接使用,只能参考。希望本文能对想学习、使用机器视觉位置检测的工程师有些帮助。

六.参考文献

[1] MVTec Software GmbH, HALCON Solution Guide II-B. München,Germany, 2010.

[2] 李卫平,左力. 运动控制系统原理与应用. 武汉:华中科技大学出版社,2013.

[3] 孙国栋,赵大兴.机器视觉检测理论与算法. 北京:科学出版社,2015.

[4] 孙正. 数字图像处理与识别. 北京:机械工业出版社,2016.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值