av_packet_rescale_ts详解

av_packet_rescale_ts是FFmpeg中用于将AVPacket时间戳从一个时间基(timebase)转换到另一个时间基的函数。

一、函数原型

复制代码
void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational dst_tb);

参数说明

  • pkt: 要转换时间戳的AVPacket指针

  • tb_src: 源时间基(当前packet使用的时间基)

  • dst_tb: 目标时间基(要转换到的时间基)

二、工作原理

2.1 时间基转换公式

复制代码
新时间戳 = 旧时间戳 × (src_timebase / dst_timebase)

2.2 内部实现逻辑

复制代码
// 简化的av_packet_rescale_ts实现
void av_packet_rescale_ts_impl(AVPacket *pkt, AVRational src_tb, AVRational dst_tb) {
    if (pkt->pts != AV_NOPTS_VALUE)
        pkt->pts = av_rescale_q(pkt->pts, src_tb, dst_tb);
    if (pkt->dts != AV_NOPTS_VALUE)
        pkt->dts = av_rescale_q(pkt->dts, src_tb, dst_tb);
    if (pkt->duration > 0)
        pkt->duration = av_rescale_q(pkt->duration, src_tb, dst_tb);
    if (pkt->duration < 0)
        pkt->duration = 0;
}

三、基本用法

3.1 基本时间戳转换

复制代码
#include <libavcodec/avcodec.h>
#include <stdio.h>

void basic_rescale_example() {
    AVPacket *pkt = av_packet_alloc();
    if (!pkt) {
        return;
    }
    
    // 模拟一个packet
    pkt->pts = 1000;
    pkt->dts = 900;
    pkt->duration = 100;
    
    // 定义时间基
    // 源时间基: 1/1000 (毫秒)
    AVRational src_tb = {1, 1000};
    
    // 目标时间基: 1/90000 (常见于MPEG-TS)
    AVRational dst_tb = {1, 90000};
    
    printf("转换前:\n");
    printf("  PTS: %ld\n", pkt->pts);
    printf("  DTS: %ld\n", pkt->dts);
    printf("  Duration: %ld\n", pkt->duration);
    printf("  源时间基: %d/%d\n", src_tb.num, src_tb.den);
    printf("  目标时间基: %d/%d\n", dst_tb.num, dst_tb.den);
    
    // 转换时间戳
    av_packet_rescale_ts(pkt, src_tb, dst_tb);
    
    printf("\n转换后:\n");
    printf("  PTS: %ld\n", pkt->pts);
    printf("  DTS: %ld\n", pkt->dts);
    printf("  Duration: %ld\n", pkt->duration);
    
    // 验证转换结果
    double src_pts_seconds = pkt->pts * (double)src_tb.num / src_tb.den;
    double dst_pts_seconds = pkt->pts * (double)dst_tb.num / dst_tb.den;
    printf("  PTS转换验证: %.3fs (应保持不变)\n", dst_pts_seconds);
    
    av_packet_free(&pkt);
}

3.2 在流之间转换时间戳

复制代码
#include <libavformat/avformat.h>

int rescale_between_streams_example(const char *input_filename, 
                                    const char *output_filename) {
    AVFormatContext *input_ctx = NULL;
    AVFormatContext *output_ctx = NULL;
    AVPacket *pkt = NULL;
    int ret = 0;
    
    // 打开输入文件
    if ((ret = avformat_open_input(&input_ctx, input_filename, NULL, NULL)) < 0) {
        fprintf(stderr, "无法打开输入文件: %s\n", av_err2str(ret));
        return ret;
    }
    
    // 获取流信息
    if ((ret = avformat_find_stream_info(input_ctx, NULL)) < 0) {
        fprintf(stderr, "无法获取流信息: %s\n", av_err2str(ret));
        goto cleanup;
    }
    
    // 创建输出格式上下文
    if ((ret = avformat_alloc_output_context2(&output_ctx, NULL, NULL, output_filename)) < 0) {
        fprintf(stderr, "无法创建输出上下文: %s\n", av_err2str(ret));
        goto cleanup;
    }
    
    pkt = av_packet_alloc();
    if (!pkt) {
        ret = AVERROR(ENOMEM);
        goto cleanup;
    }
    
    printf("输入文件信息:\n");
    printf("  格式: %s\n", input_ctx->iformat->name);
    printf("  时长: %.2f秒\n", (double)input_ctx->duration / AV_TIME_BASE);
    
    // 读取并转换packet
    int packet_count = 0;
    while (1) {
        av_packet_unref(pkt);
        
        ret = av_read_frame(input_ctx, pkt);
        if (ret < 0) {
            if (ret == AVERROR_EOF) {
                printf("文件读取完成,共 %d 个packet\n", packet_count);
                ret = 0;
            } else {
                fprintf(stderr, "读取错误: %s\n", av_err2str(ret));
            }
            break;
        }
        
        packet_count++;
        
        // 获取输入流的时间基
        AVStream *in_stream = input_ctx->streams[pkt->stream_index];
        AVRational src_tb = in_stream->time_base;
        
        // 定义目标时间基(例如转换为毫秒精度)
        AVRational dst_tb = {1, 1000};  // 1/1000,毫秒级
        
        // 保存原始时间戳
        int64_t original_pts = pkt->pts;
        int64_t original_dts = pkt->dts;
        int64_t original_duration = pkt->duration;
        
        // 转换时间戳
        av_packet_rescale_ts(pkt, src_tb, dst_tb);
        
        // 计算时间(秒)
        double src_pts_seconds = original_pts * av_q2d(src_tb);
        double dst_pts_seconds = pkt->pts * av_q2d(dst_tb);
        
        printf("Packet #%d:\n", packet_count);
        printf("  原始: PTS=%ld (%.3fs), DTS=%ld, Duration=%ld\n", 
               original_pts, src_pts_seconds, original_dts, original_duration);
        printf("  转换后: PTS=%ld (%.3fs), DTS=%ld, Duration=%ld\n", 
               pkt->pts, dst_pts_seconds, pkt->dts, pkt->duration);
        
        // 验证转换准确性
        if (fabs(src_pts_seconds - dst_pts_seconds) > 0.001) {
            printf("  警告: 时间戳转换可能存在误差!\n");
        }
    }
    
cleanup:
    av_packet_free(&pkt);
    if (input_ctx) {
        avformat_close_input(&input_ctx);
    }
    if (output_ctx) {
        avformat_free_context(output_ctx);
    }
    
    return ret;
}

四、实际应用场景

4.1 转封装时的应用

复制代码
#include <libavformat/avformat.h>

int remux_with_rescale(const char *input_file, const char *output_file) {
    AVFormatContext *in_ctx = NULL, *out_ctx = NULL;
    AVPacket *pkt = av_packet_alloc();
    int ret = 0;
    int *stream_mapping = NULL;
    int stream_mapping_size = 0;
    
    if (!pkt) {
        return AVERROR(ENOMEM);
    }
    
    // 1. 打开输入文件
    if ((ret = avformat_open_input(&in_ctx, input_file, NULL, NULL)) < 0) {
        fprintf(stderr, "无法打开输入文件: %s\n", av_err2str(ret));
        goto end;
    }
    
    if ((ret = avformat_find_stream_info(in_ctx, NULL)) < 0) {
        fprintf(stderr, "无法获取流信息: %s\n", av_err2str(ret));
        goto end;
    }
    
    // 2. 创建输出上下文
    avformat_alloc_output_context2(&out_ctx, NULL, NULL, output_file);
    if (!out_ctx) {
        fprintf(stderr, "无法创建输出上下文\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }
    
    stream_mapping_size = in_ctx->nb_streams;
    stream_mapping = av_malloc_array(stream_mapping_size, sizeof(*stream_mapping));
    if (!stream_mapping) {
        ret = AVERROR(ENOMEM);
        goto end;
    }
    
    // 3. 创建输出流
    for (int i = 0; i < in_ctx->nb_streams; i++) {
        AVStream *in_stream = in_ctx->streams[i];
        AVStream *out_stream = NULL;
        
        if (in_stream->codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
            in_stream->codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
            in_stream->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
            stream_mapping[i] = -1;
            continue;
        }
        
        out_stream = avformat_new_stream(out_ctx, NULL);
        if (!out_stream) {
            fprintf(stderr, "无法创建输出流\n");
            ret = AVERROR(ENOMEM);
            goto end;
        }
        
        // 复制编解码器参数
        ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
        if (ret < 0) {
            fprintf(stderr, "无法复制编解码器参数\n");
            goto end;
        }
        out_stream->codecpar->codec_tag = 0;
        
        stream_mapping[i] = i;
    }
    
    // 4. 打开输出文件
    if (!(out_ctx->oformat->flags & AVFMT_NOFILE)) {
        ret = avio_open(&out_ctx->pb, output_file, AVIO_FLAG_WRITE);
        if (ret < 0) {
            fprintf(stderr, "无法打开输出文件: %s\n", av_err2str(ret));
            goto end;
        }
    }
    
    // 5. 写入头部
    ret = avformat_write_header(out_ctx, NULL);
    if (ret < 0) {
        fprintf(stderr, "写入文件头失败: %s\n", av_err2str(ret));
        goto end;
    }
    
    // 6. 读取、转换并写入packet
    while (1) {
        AVStream *in_stream, *out_stream;
        
        ret = av_read_frame(in_ctx, pkt);
        if (ret < 0) {
            break;  // 文件结束或错误
        }
        
        in_stream  = in_ctx->streams[pkt->stream_index];
        int stream_idx = stream_mapping[pkt->stream_index];
        
        if (stream_idx < 0) {
            av_packet_unref(pkt);
            continue;
        }
        
        pkt->stream_index = stream_idx;
        out_stream = out_ctx->streams[pkt->stream_index];
        
        // 关键:转换时间戳
        av_packet_rescale_ts(pkt, in_stream->time_base, out_stream->time_base);
        
        // 写入packet
        ret = av_interleaved_write_frame(out_ctx, pkt);
        if (ret < 0) {
            fprintf(stderr, "写入frame错误: %s\n", av_err2str(ret));
            break;
        }
        
        av_packet_unref(pkt);
    }
    
    // 7. 写入尾部
    av_write_trailer(out_ctx);
    
    printf("转封装完成\n");
    
end:
    av_packet_free(&pkt);
    if (in_ctx) {
        avformat_close_input(&in_ctx);
    }
    if (out_ctx && !(out_ctx->oformat->flags & AVFMT_NOFILE)) {
        avio_closep(&out_ctx->pb);
    }
    if (out_ctx) {
        avformat_free_context(out_ctx);
    }
    av_free(stream_mapping);
    
    return ret;
}

4.2 处理B帧的时间戳

复制代码
void handle_b_frames_with_rescale(AVPacket *pkt, AVStream *stream) {
    // 检查是否是B帧
    int is_b_frame = 0;
    if (pkt->pts != AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE) {
        is_b_frame = (pkt->pts < pkt->dts);
    }
    
    if (is_b_frame) {
        printf("检测到B帧: PTS=%ld, DTS=%ld\n", pkt->pts, pkt->dts);
        
        // 保存原始时间戳
        int64_t original_pts = pkt->pts;
        int64_t original_dts = pkt->dts;
        
        // 转换为目标时间基
        AVRational dst_tb = {1, 90000};  // 常见的目标时间基
        av_packet_rescale_ts(pkt, stream->time_base, dst_tb);
        
        printf("转换后: PTS=%ld, DTS=%ld (时间基: %d/%d)\n", 
               pkt->pts, pkt->dts, dst_tb.num, dst_tb.den);
        
        // 计算时间差
        double pts_seconds = pkt->pts * av_q2d(dst_tb);
        double dts_seconds = pkt->dts * av_q2d(dst_tb);
        double time_diff = pts_seconds - dts_seconds;
        
        printf("  时间差: PTS-DTS = %.3f秒\n", time_diff);
        
        // 验证B帧属性仍然保持
        if (pkt->pts < pkt->dts) {
            printf("  B帧属性保持\n");
        }
    }
}

五、时间基转换详解

5.1 常见的时间基

复制代码
void common_timebases_example() {
    printf("常见时间基:\n");
    
    // 1. 微秒级 (1/1,000,000)
    AVRational us_tb = {1, 1000000};
    printf("1. 微秒: %d/%d\n", us_tb.num, us_tb.den);
    
    // 2. 毫秒级 (1/1,000)
    AVRational ms_tb = {1, 1000};
    printf("2. 毫秒: %d/%d\n", ms_tb.num, ms_tb.den);
    
    // 3. MPEG-TS 时钟 (1/90,000)
    AVRational mpegts_tb = {1, 90000};
    printf("3. MPEG-TS: %d/%d\n", mpegts_tb.num, mpegts_tb.den);
    
    // 4. 毫秒的1.001倍 (用于NTSC)
    AVRational ntsc_tb = {1001, 30000};
    printf("4. NTSC: %d/%d\n", ntsc_tb.num, ntsc_tb.den);
    
    // 5. 采样率作为时间基 (音频常见)
    AVRational audio_tb = {1, 44100};  // 44.1kHz
    printf("5. 音频(44.1kHz): %d/%d\n", audio_tb.num, audio_tb.den);
}

5.2 时间基转换工具函数

复制代码
#include <math.h>

// 将时间戳转换为秒
double timestamp_to_seconds(int64_t ts, AVRational time_base) {
    if (ts == AV_NOPTS_VALUE) {
        return NAN;
    }
    return ts * av_q2d(time_base);
}

// 将秒转换为时间戳
int64_t seconds_to_timestamp(double seconds, AVRational time_base) {
    if (isnan(seconds)) {
        return AV_NOPTS_VALUE;
    }
    return (int64_t)(seconds / av_q2d(time_base));
}

// 手动转换时间戳(不使用av_packet_rescale_ts)
void manual_rescale_example() {
    AVPacket *pkt = av_packet_alloc();
    pkt->pts = 1000;
    pkt->dts = 900;
    pkt->duration = 100;
    
    AVRational src_tb = {1, 1000};  // 毫秒
    AVRational dst_tb = {1, 90000}; // MPEG-TS
    
    printf("手动转换示例:\n");
    
    // 1. 先将时间戳转换为秒
    double pts_seconds = timestamp_to_seconds(pkt->pts, src_tb);
    double dts_seconds = timestamp_to_seconds(pkt->dts, src_tb);
    double duration_seconds = timestamp_to_seconds(pkt->duration, src_tb);
    
    printf("  转换为秒: PTS=%.3fs, DTS=%.3fs, Duration=%.3fs\n", 
           pts_seconds, dts_seconds, duration_seconds);
    
    // 2. 再将秒转换为目标时间基
    int64_t new_pts = seconds_to_timestamp(pts_seconds, dst_tb);
    int64_t new_dts = seconds_to_timestamp(dts_seconds, dst_tb);
    int64_t new_duration = seconds_to_timestamp(duration_seconds, dst_tb);
    
    printf("  转换为目标时间基: PTS=%ld, DTS=%ld, Duration=%ld\n", 
           new_pts, new_dts, new_duration);
    
    // 3. 使用av_packet_rescale_ts验证
    av_packet_rescale_ts(pkt, src_tb, dst_tb);
    printf("  av_packet_rescale_ts结果: PTS=%ld, DTS=%ld, Duration=%ld\n", 
           pkt->pts, pkt->dts, pkt->duration);
    
    av_packet_free(&pkt);
}

六、高级应用

6.1 批量转换时间戳

复制代码
void batch_rescale_packets(AVPacket **packets, int count, 
                           AVRational src_tb, AVRational dst_tb) {
    printf("批量转换 %d 个packet的时间戳\n", count);
    printf("从时间基 %d/%d 到 %d/%d\n", 
           src_tb.num, src_tb.den, dst_tb.num, dst_tb.den);
    
    for (int i = 0; i < count; i++) {
        if (packets[i]) {
            int64_t original_pts = packets[i]->pts;
            int64_t original_dts = packets[i]->dts;
            
            av_packet_rescale_ts(packets[i], src_tb, dst_tb);
            
            printf("Packet[%d]: PTS %ld -> %ld, DTS %ld -> %ld\n", 
                   i, original_pts, packets[i]->pts, 
                   original_dts, packets[i]->dts);
        }
    }
}

6.2 时间戳对齐

复制代码
typedef struct {
    int64_t start_pts;
    AVRational time_base;
} StreamTiming;

int align_stream_timestamps(StreamTiming *streams, int num_streams, 
                           AVPacket *pkt, int stream_index) {
    if (stream_index < 0 || stream_index >= num_streams) {
        return AVERROR(EINVAL);
    }
    
    StreamTiming *stream = &streams[stream_index];
    
    // 如果是第一个packet,设置起始PTS
    if (stream->start_pts == AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE) {
        stream->start_pts = pkt->pts;
        printf("流 %d 起始PTS设置为: %ld\n", stream_index, stream->start_pts);
    }
    
    // 调整PTS使其从0开始
    if (pkt->pts != AV_NOPTS_VALUE && stream->start_pts != AV_NOPTS_VALUE) {
        pkt->pts -= stream->start_pts;
    }
    
    // 调整DTS
    if (pkt->dts != AV_NOPTS_VALUE && stream->start_pts != AV_NOPTS_VALUE) {
        pkt->dts -= stream->start_pts;
    }
    
    return 0;
}

七、常见问题与解决方案

7.1 时间戳溢出

复制代码
void handle_timestamp_overflow() {
    AVPacket *pkt = av_packet_alloc();
    AVRational src_tb = {1, 1};  // 秒
    AVRational dst_tb = {1, 90000};  // MPEG-TS
    
    // 非常大的时间戳
    pkt->pts = INT64_MAX / 2;  // 很大的值
    pkt->dts = pkt->pts - 1000;
    
    printf("处理大时间戳:\n");
    printf("原始PTS: %ld (可能溢出)\n", pkt->pts);
    
    // 使用av_rescale_q手动转换,检查溢出
    int64_t new_pts = av_rescale_q(pkt->pts, src_tb, dst_tb);
    int64_t new_dts = av_rescale_q(pkt->dts, src_tb, dst_tb);
    
    // 检查是否溢出
    if (new_pts < 0 || new_dts < 0) {
        printf("警告: 时间戳转换可能溢出!\n");
    }
    
    // 使用av_packet_rescale_ts
    av_packet_rescale_ts(pkt, src_tb, dst_tb);
    printf("转换后PTS: %ld\n", pkt->pts);
    
    av_packet_free(&pkt);
}

7.2 负时间戳处理

复制代码
void handle_negative_timestamps() {
    AVPacket *pkt = av_packet_alloc();
    AVRational src_tb = {1, 1000};  // 毫秒
    AVRational dst_tb = {1, 90000};  // MPEG-TS
    
    // 负时间戳(某些情况下可能出现)
    pkt->pts = -100;  // -0.1秒
    pkt->dts = -200;  // -0.2秒
    pkt->duration = 100;
    
    printf("处理负时间戳:\n");
    printf("转换前: PTS=%ld, DTS=%ld\n", pkt->pts, pkt->dts);
    
    // 转换
    av_packet_rescale_ts(pkt, src_tb, dst_tb);
    printf("转换后: PTS=%ld, DTS=%ld\n", pkt->pts, pkt->dts);
    
    // 验证转换结果
    if (pkt->pts < 0 || pkt->dts < 0) {
        printf("注意: 时间戳仍为负值\n");
        
        // 如果需要,可以偏移到0
        int64_t offset = -pkt->dts;  // 使用DTS作为偏移
        pkt->pts += offset;
        pkt->dts += offset;
        printf("偏移后: PTS=%ld, DTS=%ld\n", pkt->pts, pkt->dts);
    }
    
    av_packet_free(&pkt);
}

八、性能优化

8.1 重用时间基转换结果

复制代码
typedef struct {
    AVRational src_tb;
    AVRational dst_tb;
    double conversion_factor;  // 转换因子
} RescaleContext;

void init_rescale_context(RescaleContext *ctx, 
                          AVRational src_tb, AVRational dst_tb) {
    ctx->src_tb = src_tb;
    ctx->dst_tb = dst_tb;
    ctx->conversion_factor = av_q2d(src_tb) / av_q2d(dst_tb);
}

int64_t fast_rescale(RescaleContext *ctx, int64_t timestamp) {
    if (timestamp == AV_NOPTS_VALUE) {
        return AV_NOPTS_VALUE;
    }
    return (int64_t)(timestamp * ctx->conversion_factor);
}

void fast_batch_rescale(RescaleContext *ctx, AVPacket **packets, int count) {
    for (int i = 0; i < count; i++) {
        if (packets[i]) {
            packets[i]->pts = fast_rescale(ctx, packets[i]->pts);
            packets[i]->dts = fast_rescale(ctx, packets[i]->dts);
            if (packets[i]->duration > 0) {
                packets[i]->duration = fast_rescale(ctx, packets[i]->duration);
            }
        }
    }
}

九、最佳实践

9.1 完整的时间戳处理流程

复制代码
int process_packet_with_timestamps(AVPacket *pkt, AVStream *stream, 
                                   AVRational target_timebase) {
    int ret = 0;
    
    // 1. 检查时间戳有效性
    if (pkt->pts == AV_NOPTS_VALUE) {
        fprintf(stderr, "警告: PTS未设置\n");
        // 可以使用DTS或估计值
    }
    
    if (pkt->dts == AV_NOPTS_VALUE) {
        fprintf(stderr, "警告: DTS未设置\n");
        // 如果PTS有效,可以使用PTS
        if (pkt->pts != AV_NOPTS_VALUE) {
            pkt->dts = pkt->pts;
        }
    }
    
    // 2. 验证时间戳顺序
    if (pkt->pts != AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE) {
        if (pkt->dts > pkt->pts) {
            printf("注意: DTS > PTS,可能是B帧\n");
        }
    }
    
    // 3. 转换时间戳
    av_packet_rescale_ts(pkt, stream->time_base, target_timebase);
    
    // 4. 验证转换结果
    if (pkt->pts != AV_NOPTS_VALUE && pkt->pts < 0) {
        printf("注意: 转换后PTS为负值\n");
    }
    
    if (pkt->dts != AV_NOPTS_VALUE && pkt->dts < 0) {
        printf("注意: 转换后DTS为负值\n");
    }
    
    // 5. 记录时间信息
    double pts_seconds = timestamp_to_seconds(pkt->pts, target_timebase);
    double dts_seconds = timestamp_to_seconds(pkt->dts, target_timebase);
    
    printf("处理完成: PTS=%.3fs, DTS=%.3fs\n", pts_seconds, dts_seconds);
    
    return ret;
}

十、总结

  1. 功能: 转换AVPacket中的pts、dts、duration到新的时间基

  2. 使用场景: 转封装、时间基标准化、多流同步等

  3. 注意事项:

    • 会自动处理AV_NOPTS_VALUE

    • 使用av_rescale_q避免溢出

    • 只转换有效的duration (duration > 0)

  4. 最佳实践:

    • 总是在写入输出前转换时间戳

    • 检查时间戳的有效性

    • 处理负时间戳和溢出的情况

    • 记录时间基转换的日志

  5. 性能考虑: 批量处理时可以使用缓存转换因子

    // 标准使用模式
    void standard_usage(AVPacket *pkt, AVStream *src_stream, AVStream *dst_stream) {
    // 保存原始时间戳(用于调试)
    int64_t original_pts = pkt->pts;
    int64_t original_dts = pkt->dts;

    复制代码
     // 转换时间戳
     av_packet_rescale_ts(pkt, src_stream->time_base, dst_stream->time_base);
     
     // 记录转换
     printf("时间戳转换: PTS %ld->%ld, DTS %ld->%ld\n", 
            original_pts, pkt->pts, original_dts, pkt->dts);

    }

相关推荐
子夜江寒2 小时前
基于 OpenCV 的身份证号码识别系统详解
python·opencv·计算机视觉
管牛牛11 小时前
图像的卷积操作
人工智能·深度学习·计算机视觉
roman_日积跬步-终至千里13 小时前
【计算机视觉-作业1】从图像到向量:kNN数据预处理完整流程
人工智能·计算机视觉
dream_home840716 小时前
拉普拉斯算子识别图像模糊详解
人工智能·计算机视觉
AI即插即用17 小时前
即插即用系列 | AAAI 2026 WaveFormer: 当视觉建模遇上波动方程,频率-时间解耦的新SOTA
图像处理·人工智能·深度学习·神经网络·计算机视觉·视觉检测
机 _ 长19 小时前
YOLO26 改进 | 训练策略 | 知识蒸馏 (Response + Feature + Relation)
python·深度学习·yolo·目标检测·机器学习·计算机视觉
lixzest21 小时前
目标检测算法应用工程师 面试高频题 + 标准答案
python·yolo·目标检测·计算机视觉
机 _ 长1 天前
YOLO26 蒸馏改进全攻略:从理论到实战 (Response + Feature + Relation)
人工智能·深度学习·yolo·目标检测·计算机视觉
棒棒的皮皮1 天前
【OpenCV】Python图像处理矩特征之矩的计算/计算轮廓的面积
图像处理·python·opencv·计算机视觉