C# Winform+Halcon结合标准视觉工具
介绍+
winform与halcon结合标准化工具实例
软件架构
软件架构说明 基于NET6 WINFORM+HALCON
实现标准化视觉检测工具
集成相机通讯
集成PLC通讯
TCP等常见通讯
支持常见halcon算子
- 图形采集
- blob分析
- 高精度匹配
- 颜色提取
- 找几何体
- 二维码提取
- OCR识别
- 等等
。。。
安装教程
https://dotnet.microsoft.com/zh-cn/download/dotnet/6.0
使用说明
安装 NET6 SDK,编译即可运行
对入门的同学应该有较好的学习意义
本项目涵盖了标准化视觉检测工具的大部分功能,有兴趣的小伙伴可以请我吃一顿肯德基,获取源码进行学习。
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using HalconDotNet;
using MT_OCR5._0;
using PaddleOCRSharp;
namespace Vision.Winform.Algorithm
{ //识别各种二维码,一维码, 字符识别
namespace MT_code
{
public class QR
{
public ToolPar toolPar = new ToolPar();
public bool status = true;
internal HObject image = null;
//二维码识别句柄
internal HTuple modelID = null;
public string CodeType = "QR Code";
public string ParamName = "default_parameters";
public string ParamValue = "standard_recognition";
private void creathandle()
{
try
{
switch (toolPar.RunPar.QrMode)
{
case QrMode.QR:
CodeType = "QR Code";
break;
case QrMode.DM:
CodeType = "Data Matrix ECC 200";
break;
}
switch (toolPar.RunPar.QrRecognition)
{
case QRRecognition.standard_recognition:
ParamValue = "standard_recognition";
break;
case QRRecognition.enhanced_recognition:
ParamValue = "enhanced_recognition";
break;
case QRRecognition.maximum_recognition:
ParamValue = "maximum_recognition";
break;
}
if(modelID == null)
{
HOperatorSet.CreateDataCode2dModel((HTuple)CodeType, (HTuple)ParamName, (HTuple)ParamValue, out modelID);
}
}
catch (Exception)
{
status = false;
}
//return 0;
}
private void clearhandle()
{
HOperatorSet.ClearDataCode2dModel(modelID);
}
public void run()
{
creathandle();
try
{
toolPar.ResultPar.CodeResult = new List<BarcodeResult>();
if (modelID == null)
{
status = false;
return;
}
if (toolPar.InputPar.图像 != null)
{
if (toolPar.InputPar.ROI != null)
{
HOperatorSet.ReduceDomain(toolPar.InputPar.图像, toolPar.InputPar.ROI, out image);
}
else
{
image = toolPar.InputPar.图像;
}
//设置极性
string codePolarity= "dark_on_light";
switch (toolPar.RunPar.CodePolarity)
{
case CodePolarity.any: codePolarity = "any"; break;
case CodePolarity.positive: codePolarity = "dark_on_light"; break;
case CodePolarity.negative: codePolarity = "light_on_dark"; break;
}
HOperatorSet.SetDataCode2dParam(modelID, "polarity", codePolarity);
HOperatorSet.SetDataCode2dParam(modelID, "timeout", toolPar.RunPar.TimeOut);
HObject xlds;
HTuple strtmp, resultHandles;
HOperatorSet.FindDataCode2d(image,
out xlds,
modelID,
"stop_after_result_num", toolPar.RunPar.CodeNum,
out resultHandles,
out strtmp);
//把结果塞进去
for (int i = 0; i < strtmp.Length; i++)
{
BarcodeResult tmp;
HObject region;
HOperatorSet.GenRegionContourXld(xlds, out region, "filled");
tmp.region = region;
tmp.code = strtmp[i].S;
toolPar.ResultPar.CodeResult.Add(tmp);
}
toolPar.ResultPar.CodeNum = strtmp.Length;
//HOperatorSet.clear
}
else
{
status = false;
return;
}
}
catch (Exception)
{
clearhandle();
status = false;
}
clearhandle();
}
[Serializable]
public class ToolPar : ToolParBase
{
private InputPar _inputPar = new InputPar();
public InputPar InputPar
{
get { return _inputPar; }
set { _inputPar = value; }
}
private RunPar _runPar = new RunPar();
public RunPar RunPar
{
get { return _runPar; }
set { _runPar = value; }
}
private ResultPar _resultPar = new ResultPar();
public ResultPar ResultPar
{
get { return _resultPar; }
set { _resultPar = value; }
}
}
[Serializable]
public class InputPar
{
private HObject _图像;
public HObject 图像
{
get { return _图像; }
set { _图像 = value; }
}
private HObject _ROI;
public HObject ROI
{
get { return _ROI; }
set { _ROI = value; }
}
private HObject _屏蔽区域;
public HObject 屏蔽区域
{
get { return _屏蔽区域; }
set { _屏蔽区域 = value; }
}
public int FindCodeNum { set; get; } = 1;
}
[Serializable]
public class RunPar
{
public QRRecognition QrRecognition { get; set; }
public QrMode QrMode { get; set; }
public CodePolarity CodePolarity { get; set; }
public int TimeOut { get; set; }
public int CodeNum { get; set; }
}
[Serializable]
public class ResultPar
{
private List<BarcodeResult> _CodeResult;
public List<BarcodeResult> CodeResult
{
get { return _CodeResult; }
set { _CodeResult = value; }
}
private int _CodeNum;
public int CodeNum
{
get { return _CodeNum; }
set { _CodeNum = value; }
}
}
}
public class One_dimension
{
public ToolPar toolPar = new ToolPar();
public bool status = true;
internal HObject image = null;
//条形码识别句柄
internal HTuple modelID = null;
internal HTuple codetype = "auto";
private void creathandle()
{
try
{
HOperatorSet.CreateBarCodeModel(new HTuple(), new HTuple(), out modelID);
}
catch (Exception)
{
status = false;
}
//return 0;
}
private void clearhandle()
{
HOperatorSet.ClearBarCodeModel(modelID);
}
public void run()
{
creathandle();
toolPar.ResultPar.CodeResult = new List<BarcodeResult>();
try
{
if (modelID == null)
{
status = false;
return;
}
if (toolPar.InputPar.图像 != null)
{
if (toolPar.InputPar.ROI != null)
{
HOperatorSet.ReduceDomain(toolPar.InputPar.图像, toolPar.InputPar.ROI, out image);
}
else
{
image = toolPar.InputPar.图像;
}
HObject region;
HTuple strtmp;
HOperatorSet.SetBarCodeParam(modelID, "timeout", toolPar.RunPar.TimeOut);
HOperatorSet.SetBarCodeParam(modelID, "stop_after_result_num", toolPar.RunPar.CodeNum);
设置极性
//string codePolarity = "dark_on_light";
//switch (toolPar.RunPar.CodePolarity)
//{
// case CodePolarity.any: codePolarity = "any"; break;
// case CodePolarity.positive: codePolarity = "dark_on_light"; break;
// case CodePolarity.negative: codePolarity = "light_on_dark"; break;
//}
//HOperatorSet.SetBarCodeParam(modelID, "polarity", codePolarity);
HOperatorSet.FindBarCode(image, out region, modelID, codetype, out strtmp);
//把结果塞进去
for (int i = 0; i < strtmp.Length; i++)
{
BarcodeResult tmp;
tmp.region = region;
tmp.code = strtmp[i].S;
toolPar.ResultPar.CodeResult.Add(tmp);
}
toolPar.ResultPar.CodeNum = strtmp.Length;
//HOperatorSet.clear
}
else
{
status = false;
return;
}
}
catch (Exception)
{
clearhandle();
status = false;
}
clearhandle();
}
[Serializable]
public class ToolPar : ToolParBase
{
private InputPar _inputPar = new InputPar();
public InputPar InputPar
{
get { return _inputPar; }
set { _inputPar = value; }
}
private RunPar _runPar = new RunPar();
public RunPar RunPar
{
get { return _runPar; }
set { _runPar = value; }
}
private ResultPar _resultPar = new ResultPar();
public ResultPar ResultPar
{
get { return _resultPar; }
set { _resultPar = value; }
}
}
[Serializable]
public class InputPar
{
private HObject _图像;
public HObject 图像
{
get { return _图像; }
set { _图像 = value; }
}
private HObject _ROI;
public HObject ROI
{
get { return _ROI; }
set { _ROI = value; }
}
private HObject _屏蔽区域;
public HObject 屏蔽区域
{
get { return _屏蔽区域; }
set { _屏蔽区域 = value; }
}
}
[Serializable]
public class RunPar
{
//public BarRecognition BarRecognition { get; set; }
public BarMode BarMode { get; set; }
public CodePolarity CodePolarity { get; set; }
public int TimeOut { get; set; }
public int CodeNum { get; set; }
}
[Serializable]
public class ResultPar
{
private List<BarcodeResult> _CodeResult;
public List<BarcodeResult> CodeResult
{
get { return _CodeResult; }
set { _CodeResult = value; }
}
private int _CodeNum;
public int CodeNum
{
get { return _CodeNum; }
set { _CodeNum = value; }
}
}
}
public class OCR
{
public ToolPar toolPar = new ToolPar();
public bool status = true;
internal HObject image = null;
RunPar runPar=new RunPar ();
public void run()
{
try
{
if (toolPar.InputPar.图像 != null)
{
if (toolPar.InputPar.ROI != null && toolPar.InputPar.屏蔽区域 != null)
{
HObject reduce_region = new HObject();
reduce_region.Dispose();
HOperatorSet.Difference(toolPar.InputPar.ROI, toolPar.InputPar.屏蔽区域, out reduce_region);
//image.Dispose();
HOperatorSet.ReduceDomain(toolPar.InputPar.图像, reduce_region, out image);
HOperatorSet.CropDomain(image, out image);
reduce_region.Dispose();
}
else if (toolPar.InputPar.ROI != null && toolPar.InputPar.屏蔽区域 == null)
{
HOperatorSet.GenEmptyObj(out image);
HOperatorSet.ReduceDomain(toolPar.InputPar.图像, toolPar.InputPar.ROI, out image);
HOperatorSet.CropDomain(image, out image);
}
else if (toolPar.InputPar.ROI == null && toolPar.InputPar.屏蔽区域 != null)
{
HObject reduce_region = new HObject();
reduce_region.Dispose();
HOperatorSet.Difference(toolPar.InputPar.图像, toolPar.InputPar.屏蔽区域, out reduce_region);
//image.Dispose();
HOperatorSet.ReduceDomain(toolPar.InputPar.图像, reduce_region, out image);
HOperatorSet.CropDomain(image, out image);
reduce_region.Dispose();
}
else
{
if (image != null)
image.Dispose();
image = toolPar.InputPar.图像;
}
}
else
{
status = false;
return;
}
//初始化
//ocr_engine.OCR_INIT();
//HObject img;
//HOperatorSet.GenEmptyObj(out img);
//img = image;
检测
//string ocr_result = ocr_engine.OCR_Dect(img);
//ocr_engine.engine.Dispose();
//toolPar.ResultPar.OCRResult = ocr_result;
//ocr_result = String.Empty;
HOperatorSet.CountChannels(image, out var channels);
Bitmap img;
HObject multiChannelImage;
HOperatorSet.GenEmptyObj(out multiChannelImage);
if (channels == 3)
{
img=runPar.Honject2Bitmap24(image);
}
else
{
HOperatorSet.Compose3(image, image, image, out multiChannelImage);
img = runPar.Honject2Bitmap24(multiChannelImage);
}
runPar.ocrResult= runPar.engine.DetectText(img);
toolPar.ResultPar.OCRResult = runPar.ocrResult.Text;
}
catch (Exception)
{
status = false;
}
}
[Serializable]
public class ToolPar : ToolParBase
{
private InputPar _inputPar = new InputPar();
public InputPar InputPar
{
get { return _inputPar; }
set { _inputPar = value; }
}
private RunPar _runPar = new RunPar();
public RunPar RunPar
{
get { return _runPar; }
set { _runPar = value; }
}
private ResultPar _resultPar = new ResultPar();
public ResultPar ResultPar
{
get { return _resultPar; }
set { _resultPar = value; }
}
}
[Serializable]
public class InputPar
{
private HObject _图像;
public HObject 图像
{
get { return _图像; }
set { _图像 = value; }
}
private HObject _ROI;
public HObject ROI
{
get { return _ROI; }
set { _ROI = value; }
}
private HObject _屏蔽区域;
public HObject 屏蔽区域
{
get { return _屏蔽区域; }
set { _屏蔽区域 = value; }
}
}
[Serializable]
public class RunPar
{
public OCRModelConfig config = null;
public OCRParameter oCRParameter = new OCRParameter();
public OCRResult ocrResult = new OCRResult();
public PaddleOCREngine engine;
public RunPar()
{
engine = new PaddleOCREngine(config, oCRParameter);
}
[DllImport("kernel32.dll")]
public static extern void CopyMemory(int Destination, int add, int Length);
public Bitmap Honject2Bitmap24(HObject hObject)
{
HTuple width = new HTuple();
HTuple height = new HTuple();
HTuple pointer = new HTuple();
HTuple type = new HTuple();
HTuple width2 = new HTuple();
HTuple height2 = new HTuple();
HObject interleavedImage = new HObject();
HOperatorSet.GetImageSize(hObject, out width, out height);
HOperatorSet.InterleaveChannels(hObject, out interleavedImage, "rgb", 4 * width, 0);
HOperatorSet.GetImagePointer1(interleavedImage, out pointer, out type, out width2, out height2);
IntPtr scan = pointer;
return new Bitmap(width2 / 4, height2, width2, PixelFormat.Format24bppRgb, scan);
}
public void HObject2Bitmap8(HObject image, out Bitmap res)
{
HOperatorSet.GetImagePointer1(image, out var pointer, out var _, out var width, out var height);
res = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
ColorPalette palette = res.Palette;
for (int i = 0; i <= 255; i++)
{
palette.Entries[i] = Color.FromArgb(255, i, i, i);
}
res.Palette = palette;
System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, width, height);
BitmapData bitmapData = res.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
int num = Image.GetPixelFormatSize(bitmapData.PixelFormat) / 8;
IntPtr scan = bitmapData.Scan0;
IntPtr source = pointer;
int num2 = width * height;
byte[] array = new byte[num2];
Marshal.Copy(source, array, 0, num2);
Marshal.Copy(array, 0, scan, num2);
res.UnlockBits(bitmapData);
}
}
[Serializable]
public class ResultPar
{
private string _OCRResult { get; set; }
public string OCRResult { get { return _OCRResult; } set { _OCRResult = value; } }
}
}
}
}
相关文章:

C# Winform+Halcon结合标准视觉工具
介绍 winform与halcon结合标准化工具实例 软件架构 软件架构说明 基于NET6 WINFORMHALCON 实现标准化视觉检测工具 集成相机通讯 集成PLC通讯 TCP等常见通讯 支持常见halcon算子 图形采集blob分析高精度匹配颜色提取找几何体二维码提取OCR识别等等 。。。 安装教程 …...

英语单词量测试
网址:https://preply.com/en/learn/english/test-your-vocab 测试结果: 细节:英语母语者有20000-35000个单词的词汇量,8岁孩子的词汇量在8000个左右。而不是我们教育系统里说的,6000个单词足够用了。足够用࿰…...

三、安装node_exporter
目录 一、简介 二、下载安装 一、简介 Exporter是Prometheus的指标数据收集组件。它负责从目标Jobs收集数据,并把收集到的数据转换为Prometheus支持的时序数据格式。 和传统的指标数据收集组件不同的是,他只负责收集,并不向Server端发送数据…...
kafka基础知识
kafka架构 producer -> kafka cluster(broker>topic>partition) -> consumer -> zookeeper kafka压测 kafka-producer-perf-test.sh kafka-consumer-perf-test.sh kafka日志保存位置及消息保存时间 /tpdata/client/Kafka/kafka/config/server.properties log.…...

华为昇腾310B1平台视频解码失败[ERROR] Send frame to vdec failed, errorno:507018
目录 1 [ERROR] Send frame to vdec failed, errorno:507018 2 bug解决尝试1 3 bug解决尝试2 4 最终解决方法 参考文献: 1 [ERROR] Send frame to vdec failed, errorno:507018 某项目中的代码运行报错 [ERROR] Send frame to vdec failed, errorno:507018 Ac…...
Flutter 中的 SwitchListTile 小部件:全面指南
Flutter 中的 SwitchListTile 小部件:全面指南 在Flutter的Material组件库中,SwitchListTile是一个包含开关(Switch)的列表项,非常适合用来创建带有标题、副标题以及开关的列表项,常用于设置界面ÿ…...
详细分析Vue3中的defineExpose(附Demo)
目录 前言1. 基本知识2. Demo3. 实战 前言 其基本知识可参考官网:Vue3中的defineExpose 1. 基本知识 defineExpose 是 Vue 3 的 Composition API 中一个新的实用函数,用于在 <script setup> 语法下显式暴露组件的公共属性和方法 这在处理子组件…...

合合信息:TextIn文档解析技术与高精度文本向量化模型再加速
文章目录 前言现有大模型文档解析问题表格无法解析无法按照阅读顺序解析文档编码错误 诉求文档解析技术技术难点技术架构关键技术回根溯源 文本向量化模型结语 前言 随着人工智能技术的持续演进,大语言模型在我们日常生活中正逐渐占据举足轻重的地位。大模型语言通…...

Git与Gitlab
第1章Git概述 Git是一个免费的、开源的分布式版本控制系统,可以快速高效地处理从小型到大型的各种项目。 代码托管中心,记录每个版本的代码,从项目创建到现在使用的代码,中间所有的修改都有记录。 1. 何为版本控制 版本控制是…...

MySQL数据库从入门到精通(下)
对表做了修改之后,记得点击对应图标按钮重新执行一下。 1.创建角色表 数据库一开始就要设计好,轻易不要改动。一个账号下可能有多个角色,所以我们单独再创建另一个表role用来存储所有的角色信息。其中idrole表示角色id,name表示名…...

从融媒到智媒,小程序框架可助力传媒企业在AI实践下的服务变现
过去5年,媒体行业一直都在进行着信息化建设向融媒体平台建设的转变。一些融媒体的建设演变总结如下: 新闻终端的端侧内容矩阵建设,如App新闻端,社交平台上的官方媒体等 新闻本地生活双旗舰客户端,兼顾主流媒体核心宣传…...

MES系统在电线电缆行业生产上的应用
MES系统在线缆行业的应用可以带来多重价值,包括提高生产效率、降低生产成本、提高产品质量、优化库存管理、改善生产环境和提高企业竞争力等方面。因此,在电线电缆行业中广泛应用MES系统可以提高企业的经济效益和社会效益,推动企业发展和行业…...
怎么把图片上的字去掉
将图片上的字去掉通常需要使用图像编辑软件或在线工具。以下是一些常用的方法和步骤: 使用Adobe Photoshop: 打开Photoshop,导入需要编辑的图片。 选择“橡皮擦工具”或“克隆图章工具”。 如果使用“橡皮擦工具”,调整橡皮擦的…...

BFS和DFS优先搜索算法
1. BFS与DFS 1.1 BFS DFS即Depth First Search,深度优先搜索。它是一种图遍历算法,它从一个起始点开始,逐层扩展搜索范围,直到找到目标节点为止。 这种算法通常用于解决“最短路径”问题,比如在迷宫中找到从起点到终…...

python将两张图片对齐
目录 需要对齐的照片如下: 源码: 结果: 需要对齐的照片如下: 源码: import cv2 import numpy as np from matplotlib import pyplot as plt# 读取两张图片 imgA cv2.imread(./out/out/3.png) imgB cv2.imread(./…...

Linux修炼之路之初识操作系统+基础指令(1)
目录 引言 一:对操作系统(OS)的简单了解 1.操作系统(OS) 是什么 2.操作系统好坏的衡量标准 3.操作系统存在的重要性 4.理解所有在计算机上的操作 二:Linux与windows操作的特点区别 三:基础指令 1.ls 指令 1.使用 2.常用选项 2.…...
Flink中基于Chandy-Lamport算法的分布式快照实现详解
Apache Flink利用了一种基于Chandy-Lamport分布式快照算法的变体——异步屏障快照(Asynchronous Barrier Snapshotting, ABS)来实现其强大的容错机制。Chandy-Lamport算法最初由K.M. Chandy和Leslie Lamport于1985年提出,是一种用于分布式系统…...

软件3班20240513
java.util.PropertyResourceBundle4554617c package com.yanyu;import java.sql.*; import java.util.ResourceBundle;public class JDBCTest01 {public static void main(String[] args) throws SQLException { // 获取属性配置文件ResourceBundle bundle Res…...
【小程序】怎么优化小程序的性能
优化小程序的性能是提高用户体验和确保应用顺畅运行的关键。以下是一些优化小程序性能的方法: 1. 代码优化2. 图片优化3. 网络请求优化4. 页面渲染优化5. 分包加载6. 使用性能分析工具7. 后端优化8. 用户体验优化 1. 代码优化 精简代码:删除不必要的代码…...
告别信用卡绑定烦恼:探索这个全功能的Azure语音替代品,包含AI视频制作!(微软Azure语音替代方案)
文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 语音合成的替代方案📝 功能特色📝 使用步骤示例⚓️ 相关链接 ⚓️📖 介绍 📖 虽然微软Azure语音服务为个人用户提供了充足的免费语音合成额度,但其注册过程中的信用卡绑定要求、繁琐的API配置步骤却…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...

【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...