2024年6月

概念类比

范畴 CPU GPU
二进制文件 .exe .cso / .ps
二进制指令 机器码 CSO(shader指令)
助记符 汇编 SL
高级语言 C# HLSL
高级语言文件 .cs .hlsl / .fx
高级语言编译器 csc.exe fxc.exe
API .NET API DirectX API
运行时环境 CLR DirectX
调试工具 Visual Studio Debugger RenderDoc
  • 着色器类型
着色器简称 着色器名 解释
cs_4_0 Compute Shader model 4.0 计算着色器,用于处理非图形计算任务
ds_5_0 Domain Shader model 5.0 域着色器,用于曲面细分技术中,生成顶点后处理顶点数据
fx_2_0 Effect model 2.0 效果文件,用于组合多个渲染状态和着色器程序,方便管理和使用
gs_4_0 Geometry Shader model 4.0 几何着色器,能接收一些图形形状作为输入,并输出其他形状,用于生成新顶点和图形
hs_5_0 Hull Shader model 5.0 曲面控制着色器,用于图形的曲面细分
ps_2_0 Pixel Shader model 2.0 像素着色器,用于计算像素颜色
tx_1_0 Texture Shader model 1.0 (software) 纹理着色器,主要用于处理纹理映射
vs_1_1 Vertex Shader model 1.1 顶点着色器,用于处理每个顶点数据

3DS Max HLSL编写与预览

  • 首先,为了避免折腾和跟上b站的视频教程,下载
    3DS Max
    ,接着添加一个茶壶
    只是教程用的是
    Direct9
    ,我们现在用的是
    Direct11
    ,语法有点差异
    image
    打开
    3DS Max
    ,按下快捷键
    M
    ,或者点击材质编辑器
    image
    然后切换模式,换成精简材质编辑器
    image
    点击
    物理材质
    切换自己写的shader
    image
    选择
    DirectX Shader
    材质
    image
    点击确定
    image
    点击路径,可选择自定义材质
    image

可以事先在桌面上新建一个txt文件,然后把扩展名改为.fx,可以使用vscode或者visualStudio下载HLSL扩展进行编辑
这列我提供一个Direct11的最简单的纯色着色器效果文件
solidColor.fx

// solidColor.fx


//世界投影矩阵
//用来将顶点从模型空间转换到最终的裁剪空间
float4x4 WorldViewProjection : WorldViewProjection < string UIWidget="None"; >;

//UI面板项目
float4 SolidColor
<
    string UIWidget = "Color";
    string UIName="Solid Color";
> = float4(1.0f, 1.0f, 1.0f, 1.0f);

struct VertexShaderInput
{
    //顶点着色器输入用这个语义
    //表示顶点的位置信息
    //模型空间(或世界空间)中定义的
    float4 Position : POSITION;
};

struct VertexShaderOutput
{
    //顶点着色器输出用这个语义
    //表示顶点在裁剪空间(Clip Space)中的位置
    //用来决定顶点在屏幕上位置的空间
    float4 Position : SV_Position;
};

struct PixelShaderOutput
{
    float4 Color : SV_TARGET;
};

//================== 简单的顶点着色器函数
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;

    // 计算最终的顶点位置
    output.Position = mul(input.Position, WorldViewProjection);

    return output;
}


//=============== 基本像素着色器函数
PixelShaderOutput PixelShaderFunction()
{
    PixelShaderOutput output;

    // 设置像素颜色为 SolidColor 定义的颜色
    output.Color = SolidColor;

    return output;
}

// 定义渲染效果
//Direct9写technique
//Direct10写technique10
//Direct11写technique11
technique11 SolidColorTechnique
{
    pass P0
    {
        // 基本顶点着色器
        VertexShader = compile vs_5_0 VertexShaderFunction();

        // 基本像素着色器
        PixelShader = compile ps_5_0 PixelShaderFunction();
    }
}

然后把这个材质拖到模型上
image
参数
Solid Color
是我们在代码中定义的组件,用来选材质颜色

float4 SolidColor
<
    string UIWidget = "Color";
    string UIName="Solid Color";
> = float4(1.0f, 1.0f, 1.0f, 1.0f);

image

WPF着色器编写与使用

看了下,似乎wpf只支持像素着色器,不支持顶点着色器。那代码就简化许多了。
第二个问题是wpf中没有通过HLSL生成UI控件,怎么调整SolidColor?
我看了下HLSL变量声明语法,原来
<DataType名称 = 值;... ;>
是批注语法效果框架能识别,但会被hlsl忽略
https://learn.microsoft.com/zh-cn/windows/win32/direct3dhlsl/dx-graphics-hlsl-variable-syntax

[Storage_Class] [Type_Modifier] Type Name[Index] [: Semantic] [: Packoffset] [: Register]; [Annotations] [= Initial_Value]

wpf中使用的则是
Register
可选部分,从寄存器读取输入

wpfSolidColor.fx

struct PixelShaderOutput
{
    float4 Color : SV_TARGET;
};
float4 SolidColor : register(c0) = float4(1.0f, 1.0f, 1.0f, 1.0f);

//=============== 基本像素着色器函数
PixelShaderOutput PixelShaderFunction()
{
    PixelShaderOutput output;

    // 设置像素颜色为 SolidColor 定义的颜色
    output.Color = SolidColor;

    return output;
}
namespace 你的命名空间
{
    public class SolidShader:ShaderEffect
    {
        public static readonly DependencyProperty SolidColorProperty = DependencyProperty.Register("SolidColor", typeof(Color), typeof(SolidShader), new UIPropertyMetadata(Color.FromArgb(255, 0, 0, 0), PixelShaderConstantCallback(1)));
        public SolidShader()
        {
            PixelShader pixelShader = new PixelShader();
            pixelShader.UriSource = new Uri("pack://application:,,,/程序集命名空间;component/路径/TextEffect2.ps", UriKind.Absolute);
            this.PixelShader = pixelShader;

            this.UpdateShaderValue(SolidColorProperty);
        }
        public Color SolidColor
        {
            get
            {
                return ((Color)(this.GetValue(SolidColorProperty)));
            }
            set
            {
                this.SetValue(SolidColorProperty, value);
            }
        }
    }
}

最后看到像素着色器正常运行
image

总结

  • 自定义
    着色器类型很多,3ds max中能自定义完整的渲染管线,包括顶点着色器和像素着色器。但是wpf只支持像素着色器的自定义。
  • 着色器编译入口
    使用
    fxc.exe
    我们可以自及指定入口函数,但是使用
    Shazzam Shader Editor
    看起来已经在代码中固定了入口函数。
    Shazzam Shader Editor
    使用
    Shazzam Shader Editor
    的好处是编译和预览方便
  • 版本
    Direct已经更新到11了,但wpf只支持Direct9

我们文件上传接口只需要在方法参数上写
MultipartFile
类,mvc就可以帮我们把上传的文件封装为这个类的对

象供我们非常方便的操作,那它是怎么做的呢?我们一起来看看

我们发的请求默认都是由
DispatcherServlet
类的
doDispatch()
来处理,这个方法的逻辑处理的第一步就是处理文件上传的请求,我们一起来看看是怎么处理的吧。

本文分析的问题
:文件上传请求的执行原理、文件上传自动配置原理

执行流程原理

checkMultipart()

processedRequest = checkMultipart(request)
:处理文件上传请求。所以我们把这个方法看明白就知道了

@Nullable
// 文件上传解析器,只能有一个
private MultipartResolver multipartResolver;

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    // 1.利用文件上传解析器来判断是否是文件上传请求
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        // 如果之前被MultipartFilter包装过了,就不做处理
        if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
            if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {
                logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
            }
        }
        // 是否有异常
        else if (hasMultipartException(request)) {
            logger.debug("Multipart resolution previously failed for current request - " +
                         "skipping re-resolution for undisturbed error rendering");
        }
        else {
            try {
                // 2、利用文件上传解析器来解析文件上传的请求
                return this.multipartResolver.resolveMultipart(request);
            }
            catch (MultipartException ex) {
                if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
                    logger.debug("Multipart resolution failed for error dispatch", ex);
                    // Keep processing error dispatch with regular request handle below
                }
                else {
                    throw ex;
                }
            }
        }
    }
    // If not returned before: return original request.
    return request;
}

流程:

  1. 利用
    MultipartResolver
    (文件上传解析器)来判断是否是文件上传请求:
    isMultipart
    1. 默认使用
      StandardServletMultipartResolver
  2. 利用
    MultipartResolver
    (文件上传解析器)来解析文件上传的请求:
    resolveMultipart()
    1. 会把请求包装为
      StandardMultipartHttpServletRequest

这些工作都是利用文件上传解析器来做的,所以我们把文件上传解析器搞明白也就知道了

MultipartResolver(文件上传解析器)

主要作用就是把真实文件包装为
MultipartFile
对象,并缓存起来以便后面封装参数时使用

public interface MultipartResolver {
    // 判断请求是否是文件上传的请求
	boolean isMultipart(HttpServletRequest request);

    // 将请求包装为 StandardMultipartHttpServletRequest
	MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;

    // 清除资源
	void cleanupMultipart(MultipartHttpServletRequest request);
}

1、
isMultipart()
:判断请求是否是文件上传的请求。其实就是判断
Content-Type
的值是否是以

multipart/form-data

multipart/
开头

(这里也就解释了为啥我们发送文件上传的请求时
Content-Type
的值要为
multipart/form-data

2、
resolveMultipart()
:将请求包装为
MultipartHttpServletRequest

MultipartResolver
的默认实现是
StandardServletMultipartResolver
,它会把请求封装为
StandardMultipartHttpServletRequest
,把文件封装为
StandardMultipartFile

// 是否延迟解析
private boolean resolveLazily = false;

// 判断
public boolean isMultipart(HttpServletRequest request) {
    return StringUtils.startsWithIgnoreCase(request.getContentType(),
                                            (this.strictServletCompliance ? MediaType.MULTIPART_FORM_DATA_VALUE : "multipart/"));
}

// 包装请求
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
    return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}

MultipartHttpServletRequest(文件上传请求)

默认实现
StandardMultipartHttpServletRequest
,会把文件封装为
StandardMultipartFile

// 创建文件上传请求
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
    throws MultipartException {

    super(request);
    // 是否延迟解析,默认false
    if (!lazyParsing) {
        // 解析请求
        parseRequest(request);
    }
}


private void parseRequest(HttpServletRequest request) {
    try {
        // 从 HttpServletRequest 中获取上传的文件
        Collection<Part> parts = request.getParts();
        this.multipartParameterNames = new LinkedHashSet<>(parts.size());
        // 存储封装好的文件对象
        MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
        // 遍历所有的文件
        for (Part part : parts) {
            String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
            ContentDisposition disposition = ContentDisposition.parse(headerValue);
            // 获取文件名字
            String filename = disposition.getFilename();
            if (filename != null) {
                // 添加到集合中
                files.add(part.getName(), new StandardMultipartFile(part, filename));
            }
            else {
                // 没有文件名,就是普通参数了
                this.multipartParameterNames.add(part.getName());
            }
        }
        // 将上面所有生成的 StandardMultipartFile 文件对象设置到父类的 multipartFiles属性中
        // 以便后面封装参数时使用
        setMultipartFiles(files);
    }
    catch (Throwable ex) {
        handleParseFailure(ex);
    }
}

protected final void setMultipartFiles(MultiValueMap<String, MultipartFile> multipartFiles) {
    this.multipartFiles =
        new LinkedMultiValueMap<>(Collections.unmodifiableMap(multipartFiles));
}

  1. HttpServletRequest
    中获取上传的文件
    Part

  2. 遍历所有的文件



    1. Part
      中获取请求头
      Content-Disposition
      的值,解析生成
      ContentDisposition
      对象,然后获取文件名

    2. 情况1
      :文件名不为空,说明是文件,把文件封装为
      StandardMultipartFile
      对象

    3. 情况2
      :文件名为空,说明是普通参数,则保存参数名称

  3. 将上面所有生成的
    StandardMultipartFile
    文件对象设置到父类的
    multipartFiles
    属性中,以便后面封装参数时使用

整个执行的原理到这里也就完毕了。

自动配置原理

文件上传的自动配置类是
MultipartAutoConfiguration

@AutoConfiguration
@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(MultipartProperties.class)
public class MultipartAutoConfiguration {

	private final MultipartProperties multipartProperties;

	public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
		this.multipartProperties = multipartProperties;
	}

	@Bean
	@ConditionalOnMissingBean(MultipartConfigElement.class)
	public MultipartConfigElement multipartConfigElement() {
		return this.multipartProperties.createMultipartConfig();
	}

	@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
	@ConditionalOnMissingBean(MultipartResolver.class)
	public StandardServletMultipartResolver multipartResolver() {
		StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
		multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
		return multipartResolver;
	}

}
  1. 启用文件上传的配置类
    MultipartProperties
    ,配置前缀:
    spring.servlet.multipart


    1. 也就是说我们可以通过这个类,然后在
      application.yml
      配置文件中来配置默认底层的规则
  2. 给容器中导入了
    MultipartConfigElement
    类:文件上传的配置


    1. 我们可以在容器中自己注册这个类
  3. 给容器中导入了文件上传解析器
    StandardServletMultipartResolver
    ,标准的Servlet文件上传解析器,用来处理文件上传


    1. 设置了
      resolveLazily
      属性:解析文件是否延迟解析,默认不是延迟解析

我们也可以往容器中注册我们
自定义
的文件上传解析器,SpringBoot就会使用我们的。因为有条件注解来动态判断

总结

执行流程
:利用
StandardServletMultipartResolver
(文件上传解析器)来包装请求、把每一个真实文件封装为

MultipartFile
类,以便我们能简单的操作。还会把所有的
MultipartFile
对象设置到父类的
multipartFiles

属性中,
以便后面封装参数时使用

自动配置
:利用SpringBoot的自动配置往容器中注册一个默认的
文件上传解析器

注意:
文件上传解析器默认全局只能有一个,不能像
HandlerMapping

HandlerAdapter
有多个

DispatcherServlet
中就是这么写的

	@Nullable
	private MultipartResolver multipartResolver;

	private List<HandlerMapping> handlerMappings;

	/** List of HandlerAdapters used by this servlet. */
	@Nullable
	private List<HandlerAdapter> handlerAdapters;

K最临近(K-Nearest Neighbors,KNN)
方法是一种简单且直观的分类和回归算法,主要用于分类任务。其基本原理是用到表决的方法,找到距离其最近的K个样本,然后通过K个样本的标签进行表决,预测结果给出的标签是表决多的一方。
在使用K最临近方法的时候,有
两个方面可调:
一是K值的大小
,K一般选用单数,这样不会导致在进行表决时出现概率相等的情况。
二是样本之间的距离
,由于样本特征的分布不同,因此在描述两样本之间的距离时有多种方式可以描述,例如:欧氏距离(Euclidean Distance)、曼哈顿距离(Manhattan Distance)和闵可夫斯基距离(Minkowski Distance)等。而且往往由于选择的距离不同,对应的K值也不一样,大家可以根据自己的数据特点尝试用不用的距离构建分类模型。本文提供了这些方法供大家选择。

在matlab中实现K最临近方法构建分类模型的代码如下:

labels = res(:, 1);  % 第一列是标签
features = res(:, 2:end);  % 后面的列是特征
features = zscore(features);   %归一化处理



% %% 欧式距离
%
% 
% % 设置 K 值
% K = 7;
% 
% % 初始化分类准确度
% accuracy = 0;
% 
% % 留一交叉验证
% for i = 1:size(features, 1)
%     % 从样本中选择一个作为验证样本,其余作为训练样本
%     validation_sample = features(i, :);
%     validation_label = labels(i);
%     
%     train_samples = features([1:i-1, i+1:end], :);
%     train_labels = labels([1:i-1, i+1:end]);
%     
%     % 计算验证样本与训练样本的距离
%     distances = sqrt(sum((train_samples - validation_sample).^2, 2));
%     
%     % 寻找最近的 K 个邻居
%     [~, idx] = mink(distances, K);
%     
%     % 投票确定验证样本的类别
%     predicted_label = mode(train_labels(idx));
%     
%     % 检查预测结果是否正确
%     if predicted_label == validation_label
%         accuracy = accuracy + 1;
%     end
% end
% 
% % 计算分类准确度
% accuracy = accuracy / size(features, 1);
% disp(['分类准确度:', num2str(accuracy)]);

% 
% 
% % 曼哈顿距离
% 
% 
% % 设置 K 值
% K = 9;
% 
% % 初始化分类准确度
% accuracy = 0;
% 
% % 留一交叉验证
% for i = 1:size(features, 1)
%     % 从样本中选择一个作为验证样本,其余作为训练样本
%     validation_sample = features(i, :);
%     validation_label = labels(i);
%     
%     train_samples = features([1:i-1, i+1:end], :);
%     train_labels = labels([1:i-1, i+1:end]);
%     
%     % 计算曼哈顿距离
%     distances = sum(abs(train_samples - validation_sample), 2);
%     
%     % 寻找最近的 K 个邻居
%     [~, idx] = mink(distances, K);
%     
%     % 投票确定验证样本的类别
%     predicted_label = mode(train_labels(idx));
%     
%     % 检查预测结果是否正确
%     if predicted_label == validation_label
%         accuracy = accuracy + 1;
%     end
% end
% 
% % 计算分类准确度
% accuracy = accuracy / size(features, 1);
% disp(['分类准确度:', num2str(accuracy)]);


% %% 闵可夫斯基距离
% 
% % 设置 K 值
% K = 5;
% 
% % 初始化分类准确度
% accuracy = 0;
% 
% % 留一交叉验证
% for i = 1:size(features, 1)
%     % 从样本中选择一个作为验证样本,其余作为训练样本
%     validation_sample = features(i, :);
%     validation_label = labels(i);
%     
%     train_samples = features([1:i-1, i+1:end], :);
%     train_labels = labels([1:i-1, i+1:end]);
%     
%     % 计算闵可夫斯基距离
%     distances = pdist2(train_samples, validation_sample, 'minkowski', 1); % p=1, 曼哈顿距离
%     
%     % 寻找最近的 K 个邻居
%     [~, idx] = mink(distances, K);
%     
%     % 投票确定验证样本的类别
%     predicted_label = mode(train_labels(idx));
%     
%     % 检查预测结果是否正确
%     if predicted_label == validation_label
%         accuracy = accuracy + 1;
%     end
% end
% 
% % 计算分类准确度
% accuracy = accuracy / size(features, 1);
% disp(['分类准确度:', num2str(accuracy)]);
% 





 %% KD树搜索方法


% 设置 K 值
K = 5;

% 初始化分类准确度
accuracy = 0;
predictedScores=zeros(56,2);

% 留一交叉验证
for i = 1:size(features, 1)
    % 从样本中选择一个作为验证样本,其余作为训练样本
    validation_sample = features(i, :);
    validation_label = labels(i);
    
    train_samples = features([1:i-1, i+1:end], :);
    train_labels = labels([1:i-1, i+1:end]);
    
    % 创建KD树
    mdl = fitcknn(train_samples, train_labels, 'NumNeighbors', K, 'Distance', 'euclidean', 'NSMethod', 'kdtree');
    
    % 预测验证样本的类别
    %predicted_label = predict(mdl, validation_sample);

    [predicted_label,predictedScore] = predict(mdl, validation_sample);
    predictedScores(i,:)=predictedScore;
    
    % 检查预测结果是否正确
    if predicted_label == validation_label
        accuracy = accuracy + 1;
    end
end

% 计算分类准确度
accuracy = accuracy / size(features, 1);
disp(['分类准确度:', num2str(accuracy)]);


目录
1 案例说明 1
2 VFBOX网关工作原理 1
3 准备工作 2
4 使用PRONETA软件获取PROFINET IO从站的配置信息 2
5 设置网关采集PROFINETIO从站设备数据 5
6 启动ETHERCAT从站转发采集的数据 8
7 选择槽号和数据地址 9
8 选择子槽号 11
9 案例总结 12

1 案例说明

  1. 设置网关采集ProfinetIO从站设备数据
  2. 把采集的数据转成EtherCAT协议转发给其他系统。

2 VFBOX网关工作原理
VFBOX网关是协议转换网关,是把一种协议转换成另外一种协议。网关可以采集西门子,欧姆龙,三菱,AB PLC,DLT645,DLT698电表,modbus rtu tcp,环保的HJ212协议,opc ua和opc da,电力的IEC103 IEC104, IEC61850等,以及EthernetIP,Profinet IO,CCLink,EtherCAT现场总线协议,bacnet,MQTT,mysql,sqlserver数据库。不同型号的网关支持不同的协议,具体支持的协议参考网关侧面标签上的型号。依托500多种型号的网关产品,可以实现多种不同协议之间的互联互通。
网关连接在不同协议的设备之间,就像一个“翻译”,把一种设备支持的协议,转换成另一种设备支持的协议,从而实现两个不同协议的设备之间的数据交换。工作架构如下:
image

3 准备工作

  1. 仰科网关。支持采集Profinet IO从站设备数据,EtherCAT协议转发。
  2. 电脑。IP设置成192.168.1.198,和网关在同一个网段。
  3. 网线、12V电源。

4 使用proneta软件获取Profinet IO从站的配置信息

  1. 把Profinet IO从站连接到电脑上,电脑和Profinet IO从站在同一个网段,电脑能ping通Profinet IO从站设备IP地址。

  2. 使用打开proneta软件。

  3. 点击设置。
    image

  4. 点击“网络适配器”,选择电脑上和profinet IO设备连接的网卡。
    image

  5. 返回主页,点击网络分析。
    image

  6. 点击1处,软件会自动搜索从站信息,有结果会在2显示,3,4是设备信息。记录设备的:名称,IP地址,供应商名称,固件版本等信息,这些信息在配置网关时会使用。
    image

  7. 读取插槽信息。如下设备有3个插槽。插槽0是设备Type信息“BACICV1.4”,插槽1对应网关中的slot1,信息为DI 8 bytes,插槽2对应网关中的slot2,信息为DO 8 bytes。其他的gsd文件按照相应方式进行配置。
    image

  8. 完成以上信息后,把以上参数配置到网关中。

  9. 一些情况下,在proneta_3_2_0_0\GSD目录下会生成设备的GSD文件,可以把生成的GSD文件导入到网关中,如果没有,则导入用户提供的GSD文件。

5 设置网关采集ProfinetIO从站设备数据

  1. 安装VFBOX Studio软件。打开软件后,点击新建工程,设备类型选择实际网关的型号。参考网关背面标签。可选的型号有:VB301-1100,VB301-1200,VB301-1400,VB302-2400,VB303-2400.
    image

  2. 点击“新建设备”,驱动类型选择如下:
    image

  3. 导入设备的GSD文件,获取Profinet IO从站的信息。设备类型信息(BACICV1.4),设备名称(robot2),设备的IP地址。
    image

  4. 导入后,双击设备名称,弹出设备属性对话框。
    image

StartupMode Advanced:GSD文件V2.3(含2.3)之后版本
Legacy:GSD文件V2.3之前版本
Write Parameters 点slot的最右边,如果slot有弹出对话框,则设置为true,否则设置为false

网卡 网关连接profinet设备的网口号
设备名称 Profinet从站设备的名称
设备IP地址 Profinet从站设备的IP地址
更新时间 默认16ms。数据更新时间。
Slot1-。。。 采集数据的槽号,添加好槽号后,可以看到下面采集数据的地址。

  1. 添加要采集的数据。DI或者Input的读写控制是“读”, Do或者Output的读写控制是“写”,
    image

  2. 配置完成后点击菜单“下载”,把工程下载到网关里。

  3. 点击菜单“查看数据”,查看网关采集到的数据。可以双击“写”属性的标签,发送数据给设备。
    image

6 启动EtherCAT从站转发采集的数据

  1. 网关可以作为EtherCAT协议的从站,把采集到的数据发送给EtherCAT的主站设备。
  2. 到菜单“服务-EtherCAT”,启用设置成Yes。
    image

参数 说明
IN/Out网口 网关连接主站的网口
存储区大小 根据实际传输的数据个数选择对应的值
Inputs 网关输出给主站的数据
Outputs 主站输出给网关的数据

  1. 点击“Inpus”,添加网关输出给主站的数据。
    image

  2. 点击“Outputs”,添加主站发送给网关的数据
    image

  3. 完成以上设置后,点击下载,把工程下载的网关里。

  4. 点击“Generate ESI File”可以生成ESI文件,把生成的ESI文件下载到EtherCAT主站设备后,就可以和网关进行数据通信了。
    image

  5. 如果修改了某些参数,需要重新下载工程到网关中,并重新生产ESI文件,然后再导入到主站的设备中。

9 案例总结
使用协议转换网关可以很方便的实现不同协议的设备之间的数据转换。大量节省了项目实施过程的时间成本,人力成本。VFBOX网关产品都是工业级品质,符合工业应用的场景。只需要简单的参数配置,可以很快完成设备和系统之间的连接。方案优点:

  1. 不需要修改设备里的程序。
  2. 不影响设备原有的工作方式和功能。
  3. 只需要简单的配置就可以实现功能需求。
  4. 设备长期稳定工作。
  5. 网关运行架构如下
    Profinet IO从站数据 转EtherCAT项目案例

前言

噪声标签学习下的一个任务是:训练集上存在开集噪声和闭集噪声;然后在测试集上对闭集样本进行分类。

训练集中被加入的开集样本,会被均匀得打上闭集样本的标签充当开集噪声;而闭集噪声的设置与一般的噪声标签学习一致,分为对称噪声:随机将闭集样本的标签替换为其他类别;和非对称噪声:将闭集样本的标签替换为特定的类别。

论文实验中,常用cifar数据集模拟这类任务。目前已知有两类方法:

  • 第一类基于cifar100,将100个类的一部分,通常是20个类作为开集样本,将它们标签替换了前80个类作为开集噪声;然后对于后续80个类,选择部分样本设置为对称/非对称闭集噪声。CVPR2022的PNP: Robust Learning From Noisy Labels by Probabilistic Noise Prediction
    提供的代码中
    ,使用了这种方法。但是,如果要考虑非对称噪声,在cifar10上就很难实现,cifar10的类的顺序不像cifar100那样有规律,不好设置闭集噪声。

  • 第二类方法适用cifar10和cifar100,保持原始数据集的样本数不变,使用额外的数据集(通常是imagenet32、places365)代替部分样本作为开集噪声,对于剩下的非开集噪声样本再设置闭集噪声。ECCV2022的Embedding contrastive unsupervised features to cluster in-and out-of-distribution noise in corrupted image datasets
    提供的代码
    使用了这种方式。

places365可以使用
torchvision.datasets.Places365
下载,由于训练集较大,通常是用它的验证集作为辅助数据集。

imagenet32是imagnet的32x32版本,同样是1k类,但是类的具体含义的顺序与imagenet不同,imagenet32类的具体含义可见
这里
。image32下载地址在对应论文A downsampled variant of imagenet as an alternative to the cifar datasets
提供的链接

实验

使用第二种方法,辅助数据集使用imagenet32,基于cifar构造含开集闭集噪声的训练集。

设计imagenet32数据集

import os
import pickle
import numpy as np
from PIL import Image
from torch.utils.data import Dataset

_train_list = ['train_data_batch_1',
               'train_data_batch_2',
               'train_data_batch_3',
               'train_data_batch_4',
               'train_data_batch_5',
               'train_data_batch_6',
               'train_data_batch_7',
               'train_data_batch_8',
               'train_data_batch_9',
               'train_data_batch_10']
_val_list = ['val_data']


def get_dataset(transform_train, transform_test):
    # prepare datasets

    # Train set
    train = Imagenet32(train=True, transform=transform_train)  # Load all 1000 classes in memory

    # Test set
    test = Imagenet32(train=False, transform=transform_test)  # Load all 1000 test classes in memory

    return train, test


class Imagenet32(Dataset):
    def __init__(self, root='~/data/imagenet32', train=True, transform=None):
        if root[0] == '~':
            root = os.path.expanduser(root)
        self.transform = transform
        size = 32
        # Now load the picked numpy arrays

        if train:
            data, labels = [], []

            for f in _train_list:
                file = os.path.join(root, f)

                with open(file, 'rb') as fo:
                    entry = pickle.load(fo, encoding='latin1')
                    data.append(entry['data'])
                    labels += entry['labels']
            data = np.concatenate(data)

        else:
            f = _val_list[0]
            file = os.path.join(root, f)
            with open(file, 'rb') as fo:
                entry = pickle.load(fo, encoding='latin1')
                data = entry['data']
                labels = entry['labels']

        data = data.reshape((-1, 3, size, size))
        self.data = data.transpose((0, 2, 3, 1))  # Convert to HWC
        labels = np.array(labels) - 1
        self.labels = labels.tolist()

    def __getitem__(self, index):

        img, target = self.data[index], self.labels[index]
        img = Image.fromarray(img)

        if self.transform is not None:
            img = self.transform(img)

        return img, target, index

    def __len__(self):
        return len(self.data)

目录结构:

imagenet32
├─ train_data_batch_1
├─ train_data_batch_10
├─ train_data_batch_2
├─ train_data_batch_3
├─ train_data_batch_4
├─ train_data_batch_5
├─ train_data_batch_6
├─ train_data_batch_7
├─ train_data_batch_8
├─ train_data_batch_9
└─ val_data

设计cifar数据集

import torchvision
import numpy as np
from dataset.imagenet32 import Imagenet32


class CIFAR10(torchvision.datasets.CIFAR10):

    def __init__(self, root='~/data', train=True, transform=None,
                 r_ood=0.2, r_id=0.2, seed=0, corruption='imagenet', ):
        nb_classes = 10
        self.nb_classes = nb_classes
        super().__init__(root, train=train, transform=transform)
        if train is False:
            return
        np.random.seed(seed)
        if r_ood > 0.:
            ids_ood = [i for i in range(len(self.targets)) if np.random.random() < r_ood]
            if corruption == 'imagenet':
                imagenet32 = Imagenet32(root='~/data/imagenet32', train=True)
                img_ood = imagenet32.data[np.random.permutation(range(len(imagenet32)))[:len(ids_ood)]]
            else:
                raise ValueError(f'Unknown corruption: {corruption}')
            self.ids_ood = ids_ood
            self.data[ids_ood] = img_ood

        if r_id > 0.:
            ids_not_ood = [i for i in range(len(self.targets)) if i not in ids_ood]
            ids_id = [i for i in ids_not_ood if np.random.random() < (r_id / (1 - r_ood))]
            for i, t in enumerate(self.targets):
                if i in ids_id:
                    self.targets[i] = int(np.random.random() * nb_classes)
            self.ids_id = ids_id


class CIFAR100(torchvision.datasets.CIFAR100):

    def __init__(self, root='~/data', train=True, transform=None,
                 r_ood=0.2, r_id=0.2, seed=0, corruption='imagenet', ):
        nb_classes = 100
        self.nb_classes = nb_classes
        super().__init__(root, train=train, transform=transform)
        if train is False:
            return
        np.random.seed(seed)
        if r_ood > 0.:
            ids_ood = [i for i in range(len(self.targets)) if np.random.random() < r_ood]
            if corruption == 'imagenet':
                imagenet32 = Imagenet32(root='~/data/imagenet32', train=True)
                img_ood = imagenet32.data[np.random.permutation(range(len(imagenet32)))[:len(ids_ood)]]
            else:
                raise ValueError(f'Unknown corruption: {corruption}')
            self.ids_ood = ids_ood
            self.data[ids_ood] = img_ood

        if r_id > 0.:
            ids_not_ood = [i for i in range(len(self.targets)) if i not in ids_ood]
            ids_id = [i for i in ids_not_ood if np.random.random() < (r_id / (1 - r_ood))]
            for i, t in enumerate(self.targets):
                if i in ids_id:
                    self.targets[i] = int(np.random.random() * nb_classes)
            self.ids_id = ids_id

查看统计结果

import pandas as pd
import altair as alt
from dataset.cifar import CIFAR10, CIFAR100

# Initialize CIFAR10 dataset
cifar10 = CIFAR10()
cifar100 = CIFAR100()


def statistics_samples(dataset):
    ids_ood = dataset.ids_ood
    ids_id = dataset.ids_id

    # Collect statistics
    statistics = []
    for i in range(dataset.nb_classes):
        statistics.append({
            'class': i,
            'id': 0,
            'ood': 0,
            'clear': 0
        })

    for i, t in enumerate(dataset.targets):
        if i in ids_ood:
            statistics[t]['ood'] += 1
        elif i in ids_id:
            statistics[t]['id'] += 1
        else:
            statistics[t]['clear'] += 1

    df = pd.DataFrame(statistics)

    # Melt the DataFrame for Altair
    df_melt = df.melt(id_vars='class', var_name='type', value_name='count')

    # Create the bar chart
    chart = alt.Chart(df_melt).mark_bar().encode(
        x=alt.X('class:O', title='Classes'),
        y=alt.Y('count:Q', title='Sample Count'),
        color='type:N'
    )
    return chart


chart1 = statistics_samples(cifar10)
chart2 = statistics_samples(cifar100)
chart1 = chart1.properties(
    title='cifar10',
    width=100,  # Adjust width to fit both charts side by side
    height=400
)
chart2 = chart2.properties(
    title='cifar100',
    width=800,
    height=400
)
combined_chart = alt.hconcat(chart1, chart2).configure_axis(
    labelFontSize=12,
    titleFontSize=14
).configure_legend(
    titleFontSize=14,
    labelFontSize=12
)
combined_chart

运行环境

# Name                    Version                   Build  Channel
altair                    5.3.0                    pypi_0    pypi
pytorch                   2.3.1           py3.12_cuda12.1_cudnn8_0    pytorch
pandas                    2.2.2                    pypi_0    pypi