QT联合halcon实现实时形状模板匹配

 try.6pro

QT       += core gui
 
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
 
CONFIG += c++11
 
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
 
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
 
INCLUDEPATH += "C:\Users\jgy\AppData\Local\Programs\MVTec\HALCON-22.05-Progress\include//"
INCLUDEPATH += "C:\Users\jgy\AppData\Local\Programs\MVTec\HALCON-22.05-Progress\include\halconcpp//"
 
LIBS += "C:\Users\jgy\AppData\Local\Programs\MVTec\HALCON-22.05-Progress\lib\x64-win64/halcon.lib"
LIBS += "C:\Users\jgy\AppData\Local\Programs\MVTec\HALCON-22.05-Progress\lib\x64-win64/halconcpp.lib"
 
 
SOURCES += \
    main.cpp \
    widget.cpp
 
HEADERS += \
    widget.h
 
FORMS += \
    widget.ui
 
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

widget.h

#ifndef WIDGET_H
#define WIDGET_H
 
#include "HalconCpp.h"
#include <QWidget>
#include <QDebug>
#include <QTimer> // 添加定时器头文件
#include <QGroupBox>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
 
using namespace HalconCpp;
void clean_up_output (HTuple hv_OutputDir, HTuple hv_RemoveResults);
void set_display_font (HTuple hv_WindowHandle, HTuple hv_Size, HTuple hv_Font, HTuple hv_Bold,
    HTuple hv_Slant);
void remove_dir_recursively (HTuple hv_DirName);
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);
void get_hom_mat2d_from_matching_result (HTuple hv_Row, HTuple hv_Column, HTuple hv_Angle,
    HTuple hv_ScaleR, HTuple hv_ScaleC, HTuple *hv_HomMat2D);
class Widget : public QWidget
{
    Q_OBJECT
 
 
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    HObject ho_Image;
    HTuple hv_AcqHandle,hv_WindowHandle;
    HTuple qh_window_handle; 
    Hlong windID;
    void abb();
 
private slots:
    void camera();//放置需要定时器循环的代码
    void on_pushButton_3_clicked();
    void on_pushButton_clicked();
 
private:
    Ui::Widget *ui;
    QTimer *timer;
 
};
 
#endif // WIDGET_H
main.cpp
#include "widget.h"
 
#include <QApplication>
 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}
widget.cpp

#include "widget.h"
#include "ui_widget.h"
 
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    abb();
 
    // 初始化定时器
    timer=new QTimer(this);
    connect(timer,SIGNAL(timeout()),this,SLOT(camera()));
    connect(ui->pushButton,SIGNAL(clicked(bool)),qApp,SLOT(quit()));
//    timer->start(1000);
}
 
Widget::~Widget()
{
    delete ui;
}
 
void Widget::abb()
{
    HTuple qt_window_ID, width_qhw, height_qhw;
    HTuple Image_width, Image_height;
    Image_width = 1280;
    Image_height = 720;
    qt_window_ID = (Hlong)ui->groupBox->winId();
    width_qhw = ui->groupBox->width();
    height_qhw = ui->groupBox->height();
 
    OpenWindow(0, 0, width_qhw, height_qhw, qt_window_ID, "visible", "", &qh_window_handle);
    SetPart(qh_window_handle, 0, 0, Image_height, Image_width);
}
 
void Widget::camera()
{
    HObject  ho_ModelContours,ho_GrayImage;
   HTuple hv_ModelID1, hv_Row, hv_Column;
        HTuple  hv_Angle, hv_Scale1, hv_Score;
    GrabImage(&ho_Image, hv_AcqHandle);
 
    DispObj(ho_Image, qh_window_handle);
    ReadShapeModel("model.shm", &hv_ModelID1);
    GetShapeModelContours(&ho_ModelContours, hv_ModelID1, 1);
    GrabImageAsync(&ho_Image, hv_AcqHandle, -1);
    HDevWindowStack::Push(qh_window_handle);
    FindScaledShapeModel(ho_Image, hv_ModelID1, -0.39, 0.78, 0.9, 1.1, 0.5, 1, 0.5,
                                 "least_squares", 0, 0.9, &hv_Row, &hv_Column, &hv_Angle, &hv_Scale1, &hv_Score);
    dev_display_shape_matching_results(hv_ModelID1, "red", hv_Row, hv_Column, hv_Angle, 1, 1, 0);
 
    // 刷新界面
    ui->groupBox->update();
}
void Widget::on_pushButton_3_clicked()
{
 
 
    //Image Acquisition 01: Code generated by Image Acquisition 01
 
    OpenFramegrabber("DirectShow", 1, 1, 0, 0, 0, 0, "default", 8, "rgb", -1, "false",
        "default", "[0] Integrated Camera", 0, -1, &hv_AcqHandle);
    int k=20;
     timer->start(k);
 
     qDebug() << "Hello, World!";
    qDebug() << "Hello, World2!";
 
// windID = (Hlong)this->ui->groupBox->winId();
//    while (true)
//  {
//      GrabImageAsync(&ho_Image, hv_AcqHandle, -1);
//      OpenWindow(0,0,ui->groupBox->width(),ui->groupBox->height(),windID,"visible","",&hv_WindowHandle);
// qDebug()<<"hello3 "<<str<<"!"<<endl;
//      ReadShapeModel("model.shm", &hv_ModelID1);
//      GetShapeModelContours(&ho_ModelContours, hv_ModelID1, 1);
//      FindScaledShapeModel(ho_Image, hv_ModelID1, -0.39, 0.78, 0.9, 1.1, 0.5, 1, 0.5,
//          "least_squares", 0, 0.9, &hv_Row, &hv_Column, &hv_Angle, &hv_Scale1, &hv_Score);
//      dev_display_shape_matching_results(hv_ModelID1, "red", hv_Row, hv_Column, hv_Angle,
//          1, 1, 0);
//    }
 
}
 
void Widget::on_pushButton_clicked()
{
    // 停止定时器
    timer->stop();
    CloseFramegrabber(hv_AcqHandle);
}
 
void get_hom_mat2d_from_matching_result (HTuple hv_Row, HTuple hv_Column, HTuple hv_Angle,
    HTuple hv_ScaleR, HTuple hv_ScaleC, HTuple *hv_HomMat2D)
{
 
  // Local control variables
  HTuple  hv_HomMat2DIdentity, hv_HomMat2DScale;
  HTuple  hv_HomMat2DRotate;
 
  //This procedure calculates the transformation matrix for the model contours
  //from the results of Shape-Based Matching.
  //
  HomMat2dIdentity(&hv_HomMat2DIdentity);
  HomMat2dScale(hv_HomMat2DIdentity, hv_ScaleR, hv_ScaleC, 0, 0, &hv_HomMat2DScale);
  HomMat2dRotate(hv_HomMat2DScale, hv_Angle, 0, 0, &hv_HomMat2DRotate);
  HomMat2dTranslate(hv_HomMat2DRotate, hv_Row, hv_Column, &(*hv_HomMat2D));
  return;
 
 
}
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)
{
 
  // Local iconic variables
  HObject  ho_ClutterRegion, ho_ModelContours, ho_ContoursAffinTrans;
  HObject  ho_RegionAffineTrans;
 
  // Local control variables
  HTuple  hv_WindowHandle, hv_UseClutter, hv_UseClutter0;
  HTuple  hv_HomMat2D, hv_ClutterContrast, hv_Index, hv_Exception;
  HTuple  hv_NumMatches, hv_GenParamValue, hv_HomMat2DInvert;
  HTuple  hv_Match, hv_HomMat2DTranslate, hv_HomMat2DCompose;
 
  //This procedure displays the results of Shape-Based Matching.
  //
  //Ensure that the different models have the same use_clutter value.
  //
  //This procedure displays the results on the active graphics window.
  if (HDevWindowStack::IsOpen())
    hv_WindowHandle = HDevWindowStack::GetActive();
  //If no graphics window is currently open, nothing can be displayed.
  if (0 != (int(hv_WindowHandle==-1)))
  {
    return;
  }
  //
  hv_UseClutter = "false";
  try
  {
    GetShapeModelClutter(&ho_ClutterRegion, HTuple(hv_ModelID[0]), "use_clutter",
        &hv_UseClutter0, &hv_HomMat2D, &hv_ClutterContrast);
    {
    HTuple end_val14 = (hv_ModelID.TupleLength())-1;
    HTuple step_val14 = 1;
    for (hv_Index=0; hv_Index.Continue(end_val14, step_val14); hv_Index += step_val14)
    {
      GetShapeModelClutter(&ho_ClutterRegion, HTuple(hv_ModelID[hv_Index]), "use_clutter",
          &hv_UseClutter, &hv_HomMat2D, &hv_ClutterContrast);
      if (0 != (int(hv_UseClutter!=hv_UseClutter0)))
      {
        throw HException("Shape models are not of the same clutter type");
      }
    }
    }
  }
  // catch (Exception)
  catch (HException &HDevExpDefaultException)
  {
    HDevExpDefaultException.ToHTuple(&hv_Exception);
  }
  if (0 != (int(hv_UseClutter==HTuple("true"))))
  {
    if (HDevWindowStack::IsOpen())
      SetDraw(HDevWindowStack::GetActive(),"margin");
    //For clutter-enabled models, the Color tuple should have either
    //exactly 2 entries, or 2* the number of models. The first color
    //is used for the match and the second for the clutter region,
    //respectively.
    if (0 != (HTuple(int((hv_Color.TupleLength())!=(2*(hv_ModelID.TupleLength())))).TupleAnd(int((hv_Color.TupleLength())!=2))))
    {
      throw HException("Length of Color does not correspond to models with enabled clutter parameters");
    }
  }
 
  hv_NumMatches = hv_Row.TupleLength();
  if (0 != (int(hv_NumMatches>0)))
  {
    if (0 != (int((hv_ScaleR.TupleLength())==1)))
    {
      TupleGenConst(hv_NumMatches, hv_ScaleR, &hv_ScaleR);
    }
    if (0 != (int((hv_ScaleC.TupleLength())==1)))
    {
      TupleGenConst(hv_NumMatches, hv_ScaleC, &hv_ScaleC);
    }
    if (0 != (int((hv_Model.TupleLength())==0)))
    {
      TupleGenConst(hv_NumMatches, 0, &hv_Model);
    }
    else if (0 != (int((hv_Model.TupleLength())==1)))
    {
      TupleGenConst(hv_NumMatches, hv_Model, &hv_Model);
    }
    //Redirect all display calls to a buffer window and update the
    //graphics window only at the end, to speed up the visualization.
    SetWindowParam(hv_WindowHandle, "flush", "false");
    {
    HTuple end_val49 = (hv_ModelID.TupleLength())-1;
    HTuple step_val49 = 1;
    for (hv_Index=0; hv_Index.Continue(end_val49, step_val49); hv_Index += step_val49)
    {
      GetShapeModelContours(&ho_ModelContours, HTuple(hv_ModelID[hv_Index]), 1);
      if (0 != (int(hv_UseClutter==HTuple("true"))))
      {
        GetShapeModelClutter(&ho_ClutterRegion, HTuple(hv_ModelID[hv_Index]), HTuple(),
            &hv_GenParamValue, &hv_HomMat2D, &hv_ClutterContrast);
        HomMat2dInvert(hv_HomMat2D, &hv_HomMat2DInvert);
      }
      if (HDevWindowStack::IsOpen())
        SetColor(HDevWindowStack::GetActive(),HTuple(hv_Color[hv_Index%(hv_Color.TupleLength())]));
      {
      HTuple end_val56 = hv_NumMatches-1;
      HTuple step_val56 = 1;
      for (hv_Match=0; hv_Match.Continue(end_val56, step_val56); hv_Match += step_val56)
      {
        if (0 != (int(hv_Index==HTuple(hv_Model[hv_Match]))))
        {
          get_hom_mat2d_from_matching_result(HTuple(hv_Row[hv_Match]), HTuple(hv_Column[hv_Match]),
              HTuple(hv_Angle[hv_Match]), HTuple(hv_ScaleR[hv_Match]), HTuple(hv_ScaleC[hv_Match]),
              &hv_HomMat2DTranslate);
          AffineTransContourXld(ho_ModelContours, &ho_ContoursAffinTrans, hv_HomMat2DTranslate);
          if (0 != (int(hv_UseClutter==HTuple("true"))))
          {
            HomMat2dCompose(hv_HomMat2DTranslate, hv_HomMat2DInvert, &hv_HomMat2DCompose);
            AffineTransRegion(ho_ClutterRegion, &ho_RegionAffineTrans, hv_HomMat2DCompose,
                "constant");
            if (0 != (int((hv_Color.TupleLength())==2)))
            {
              if (HDevWindowStack::IsOpen())
                SetColor(HDevWindowStack::GetActive(),HTuple(hv_Color[1]));
              if (HDevWindowStack::IsOpen())
                DispObj(ho_RegionAffineTrans, HDevWindowStack::GetActive());
              if (HDevWindowStack::IsOpen())
                SetColor(HDevWindowStack::GetActive(),HTuple(hv_Color[0]));
            }
            else
            {
              if (HDevWindowStack::IsOpen())
                SetColor(HDevWindowStack::GetActive(),HTuple(hv_Color[(hv_Index*2)+1]));
              if (HDevWindowStack::IsOpen())
                DispObj(ho_RegionAffineTrans, HDevWindowStack::GetActive());
              if (HDevWindowStack::IsOpen())
                SetColor(HDevWindowStack::GetActive(),HTuple(hv_Color[hv_Index*2]));
            }
          }
          if (HDevWindowStack::IsOpen())
            DispObj(ho_ContoursAffinTrans, HDevWindowStack::GetActive());
        }
      }
      }
    }
    }
    //Copy the content of the buffer window to the graphics window.
    SetWindowParam(hv_WindowHandle, "flush", "true");
    FlushBuffer(hv_WindowHandle);
  }
  return;
}
void clean_up_output (HTuple hv_OutputDir, HTuple hv_RemoveResults)
{
 
  // Local iconic variables
 
  // Local control variables
  HTuple  hv_WindowHandle, hv_WarningCleanup;
 
  //This local example procedure cleans up the output of the example.
  //
  if (0 != (hv_RemoveResults.TupleNot()))
  {
    return;
  }
  //Display a warning.
  SetWindowAttr("background_color","black");
  OpenWindow(0,0,600,300,0,"visible","",&hv_WindowHandle);
  HDevWindowStack::Push(hv_WindowHandle);
  set_display_font(hv_WindowHandle, 16, "mono", "true", "false");
  hv_WarningCleanup.Clear();
  hv_WarningCleanup[0] = "Congratulations, you have finished the example.";
  hv_WarningCleanup[1] = "";
  hv_WarningCleanup[2] = "Unless you would like to use the output data / model,";
  hv_WarningCleanup[3] = "press F5 to clean up.";
  if (HDevWindowStack::IsOpen())
    DispText(HDevWindowStack::GetActive(),hv_WarningCleanup, "window", "center",
        "center", ((((HTuple("black").Append("black")).Append("coral")).Append("coral")).Append("coral")),
        HTuple(), HTuple());
  //
  // stop(...); only in hdevelop
  if (HDevWindowStack::IsOpen())
    CloseWindow(HDevWindowStack::Pop());
  //
  //Delete all outputs of the example.
  remove_dir_recursively(hv_OutputDir);
  DeleteFile("model_best.hdl");
  DeleteFile("model_best_info.hdict");
  return;
}
void remove_dir_recursively (HTuple hv_DirName)
{
 
  // Local control variables
  HTuple  hv_Dirs, hv_I, hv_Files;
 
  //Recursively delete all subdirectories.
  ListFiles(hv_DirName, "directories", &hv_Dirs);
  {
  HTuple end_val2 = (hv_Dirs.TupleLength())-1;
  HTuple step_val2 = 1;
  for (hv_I=0; hv_I.Continue(end_val2, step_val2); hv_I += step_val2)
  {
    remove_dir_recursively(HTuple(hv_Dirs[hv_I]));
  }
  }
  //Delete all files.
  ListFiles(hv_DirName, "files", &hv_Files);
  {
  HTuple end_val7 = (hv_Files.TupleLength())-1;
  HTuple step_val7 = 1;
  for (hv_I=0; hv_I.Continue(end_val7, step_val7); hv_I += step_val7)
  {
    DeleteFile(HTuple(hv_Files[hv_I]));
  }
  }
  //Remove empty directory.
  RemoveDir(hv_DirName);
  return;
}
void set_display_font (HTuple hv_WindowHandle, HTuple hv_Size, HTuple hv_Font, HTuple hv_Bold,
    HTuple hv_Slant)
{
 
  // Local iconic variables
 
  // Local control variables
  HTuple  hv_OS, hv_Fonts, hv_Style, hv_Exception;
  HTuple  hv_AvailableFonts, hv_Fdx, hv_Indices;
 
  //This procedure sets the text font of the current window with
  //the specified attributes.
  //
  //Input parameters:
  //WindowHandle: The graphics window for which the font will be set
  //Size: The font size. If Size=-1, the default of 16 is used.
  //Bold: If set to 'true', a bold font is used
  //Slant: If set to 'true', a slanted font is used
  //
  GetSystem("operating_system", &hv_OS);
  if (0 != (HTuple(int(hv_Size==HTuple())).TupleOr(int(hv_Size==-1))))
  {
    hv_Size = 16;
  }
  if (0 != (int((hv_OS.TupleSubstr(0,2))==HTuple("Win"))))
  {
    //Restore previous behaviour
    hv_Size = (1.13677*hv_Size).TupleInt();
  }
  else
  {
    hv_Size = hv_Size.TupleInt();
  }
  if (0 != (int(hv_Font==HTuple("Courier"))))
  {
    hv_Fonts.Clear();
    hv_Fonts[0] = "Courier";
    hv_Fonts[1] = "Courier 10 Pitch";
    hv_Fonts[2] = "Courier New";
    hv_Fonts[3] = "CourierNew";
    hv_Fonts[4] = "Liberation Mono";
  }
  else if (0 != (int(hv_Font==HTuple("mono"))))
  {
    hv_Fonts.Clear();
    hv_Fonts[0] = "Consolas";
    hv_Fonts[1] = "Menlo";
    hv_Fonts[2] = "Courier";
    hv_Fonts[3] = "Courier 10 Pitch";
    hv_Fonts[4] = "FreeMono";
    hv_Fonts[5] = "Liberation Mono";
  }
  else if (0 != (int(hv_Font==HTuple("sans"))))
  {
    hv_Fonts.Clear();
    hv_Fonts[0] = "Luxi Sans";
    hv_Fonts[1] = "DejaVu Sans";
    hv_Fonts[2] = "FreeSans";
    hv_Fonts[3] = "Arial";
    hv_Fonts[4] = "Liberation Sans";
  }
  else if (0 != (int(hv_Font==HTuple("serif"))))
  {
    hv_Fonts.Clear();
    hv_Fonts[0] = "Times New Roman";
    hv_Fonts[1] = "Luxi Serif";
    hv_Fonts[2] = "DejaVu Serif";
    hv_Fonts[3] = "FreeSerif";
    hv_Fonts[4] = "Utopia";
    hv_Fonts[5] = "Liberation Serif";
  }
  else
  {
    hv_Fonts = hv_Font;
  }
  hv_Style = "";
  if (0 != (int(hv_Bold==HTuple("true"))))
  {
    hv_Style += HTuple("Bold");
  }
  else if (0 != (int(hv_Bold!=HTuple("false"))))
  {
    hv_Exception = "Wrong value of control parameter Bold";
    throw HException(hv_Exception);
  }
  if (0 != (int(hv_Slant==HTuple("true"))))
  {
    hv_Style += HTuple("Italic");
  }
  else if (0 != (int(hv_Slant!=HTuple("false"))))
  {
    hv_Exception = "Wrong value of control parameter Slant";
    throw HException(hv_Exception);
  }
  if (0 != (int(hv_Style==HTuple(""))))
  {
    hv_Style = "Normal";
  }
  QueryFont(hv_WindowHandle, &hv_AvailableFonts);
  hv_Font = "";
  {
  HTuple end_val48 = (hv_Fonts.TupleLength())-1;
  HTuple step_val48 = 1;
  for (hv_Fdx=0; hv_Fdx.Continue(end_val48, step_val48); hv_Fdx += step_val48)
  {
    hv_Indices = hv_AvailableFonts.TupleFind(HTuple(hv_Fonts[hv_Fdx]));
    if (0 != (int((hv_Indices.TupleLength())>0)))
    {
      if (0 != (int(HTuple(hv_Indices[0])>=0)))
      {
        hv_Font = HTuple(hv_Fonts[hv_Fdx]);
        break;
      }
    }
  }
  }
  if (0 != (int(hv_Font==HTuple(""))))
  {
    throw HException("Wrong value of control parameter Font");
  }
  hv_Font = (((hv_Font+"-")+hv_Style)+"-")+hv_Size;
  SetFont(hv_WindowHandle, hv_Font);
  return;
}
 
 
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
基于形状模板匹配是一种常用且前沿的模板匹配算法,也称为基于边缘方向梯度的匹配。该算法以物体边缘的梯度相关性作为匹配标准。 在使用QtHalcon进行模板匹配时,可以通过调用Halcon的dll来实现二次开发。具体实现过程主要包括以下几个步骤: 1. 设置编程环境:使用Qt 5.3和Halcon 12.0,并在Visual Studio 2010中设置IDE环境。 2. 导入Halcon的dll:在Qt项目中,通过引入Halcon的dll文件,实现Halcon的交互。 3. 创建模板:使用Halcon提供的函数,对待匹配的物体进行模板创建,获取物体的特征信息。 4. 匹配过程:使用模板匹配算法,将待匹配的图像与模板进行匹配,计算边缘方向梯度的相关性,并得到匹配结果。 5. 定位和识别:根据匹配结果,可以实现物体的定位和识别功能。 通过上述步骤,基于QtHalcon模板匹配算法可以成功实现视觉定位识别的功能。这种实现方式可以供不同开发者学习和使用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Qthalcon联合开发实现基于形状模板匹配](https://blog.csdn.net/weixin_42937740/article/details/121832988)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [基于qt+halcon实现视觉定位模板匹配【附部分源码】](https://blog.csdn.net/ctu_sue/article/details/127053060)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值