支持多页解码的JBIG2库

文章目录

头文件

cpp 复制代码
#if 1
#ifndef JBIG2_DECODER_HPP
#define JBIG2_DECODER_HPP

#include <cstdint>
#include <vector>
#include <memory>
#include <string>
#include <stdexcept>

// jbig2dec 头文件
extern "C" {
#include <jbig2.h>
}

/**
 * @brief JBIG2解码异常类
 */
class JBIG2DecoderException : public std::runtime_error {
public:
    explicit JBIG2DecoderException(const std::string& msg)
        : std::runtime_error(msg) {}

    explicit JBIG2DecoderException(const char* msg)
        : std::runtime_error(msg) {}
};

/**
 * @brief JBIG2图像信息结构
 */
struct JBIG2ImageInfo {
    int width = 0;           // 图像宽度(像素)
    int height = 0;          // 图像高度(像素)
    int xres = 72;           // 水平分辨率(DPI)
    int yres = 72;           // 垂直分辨率(DPI)
    int bitsPerPixel = 1;    // 每像素位数(JBIG2总是1)
    bool isMultiPage = false;// 是否为多页文档
    int pageCount = 0;       // 总页数
};

/**
 * @brief JBIG2解码器类
 *
 * 提供JBIG2图像的解码功能,支持单页和多页文档
 */
class JBIG2Decoder {
public:
    /**
     * @brief 构造函数
     * @param invertPixels 是否反转像素值(黑变白,白变黑)
     *                     JBIG2标准中0通常表示白,1表示黑
     *                     但某些系统可能相反
     */
    explicit JBIG2Decoder(bool invertPixels = false);

    /**
     * @brief 析构函数
     */
    ~JBIG2Decoder();

    // 禁用拷贝构造和赋值
    JBIG2Decoder(const JBIG2Decoder&) = delete;
    JBIG2Decoder& operator=(const JBIG2Decoder&) = delete;

    // 移动构造和赋值
    JBIG2Decoder(JBIG2Decoder&& other) noexcept;
    JBIG2Decoder& operator=(JBIG2Decoder&& other) noexcept;

    /**
     * @brief 从内存缓冲区解码JBIG2数据
     * @param data JBIG2数据指针
     * @param length 数据长度(字节)
     * @return 解码的页面数
     * @throws JBIG2DecoderException 解码失败时抛出
     */
    int decode(const uint8_t* data, size_t length);

    /**
     * @brief 从文件解码JBIG2数据
     * @param filename 文件名
     * @return 解码的页面数
     * @throws JBIG2DecoderException 解码失败时抛出
     */
    int decodeFromFile(const std::string& filename);

    /**
     * @brief 获取文档总页数
     * @return 页数,如果未解码返回0
     */
    int getPageCount() const;

    /**
     * @brief 获取指定页的图像信息
     * @param pageNum 页码(0-based)
     * @return 图像信息结构
     * @throws JBIG2DecoderException 页码无效或未解码时抛出
     */
    JBIG2ImageInfo getImageInfo(int pageNum = 0) const;

    /**
     * @brief 获取指定页的位图数据
     *
     * 返回的位图数据为8位灰度格式(0=黑, 255=白)
     * 每行数据连续存储,无填充字节
     *
     * @param pageNum 页码(0-based)
     * @return 8位灰度位图数据向量
     * @throws JBIG2DecoderException 页码无效或未解码时抛出
     */
    std::vector<uint8_t> getBitmapData(int pageNum = 0) const;

    /**
     * @brief 获取指定页的1位位图数据
     *
     * 返回的位图数据为1位格式,每字节存储8个像素
     * 每个像素的MSB在前(bit 7 = 第一个像素)
     *
     * @param pageNum 页码(0-based)
     * @return 1位位图数据向量
     * @throws JBIG2DecoderException 页码无效或未解码时抛出
     */
    std::vector<uint8_t> getBitmapData1Bit(int pageNum = 0) const;

    /**
     * @brief 保存指定页为PNG文件
     *
     * 需要编译时链接libpng库,使用条件编译
     *
     * @param pageNum 页码(0-based)
     * @param filename 输出文件名
     * @return 成功返回true
     * @throws JBIG2DecoderException 保存失败时抛出
     */
    bool saveAsPNG(int pageNum, const std::string& filename) const;

    /**
     * @brief 保存指定页为PBM(Portable BitMap)文件
     *
     * PBM是简单的二值图像格式,许多图像查看器都支持
     *
     * @param pageNum 页码(0-based)
     * @param filename 输出文件名
     * @return 成功返回true
     * @throws JBIG2DecoderException 保存失败时抛出
     */
    bool saveAsPBM(int pageNum, const std::string& filename) const;

    /**
     * @brief 清除所有解码数据
     */
    void clear();

    /**
     * @brief 检查是否已解码数据
     * @return 已解码返回true
     */
    bool isDecoded() const;

    /**
     * @brief 设置像素反转选项
     * @param invert 是否反转像素值
     */
    void setInvertPixels(bool invert);

    /**
     * @brief 获取像素反转选项
     * @return 当前反转设置
     */
    bool getInvertPixels() const;

    /**
     * @brief 获取最后一条错误信息
     * @return 错误信息字符串
     */
    std::string getLastError() const;

private:
    // jbig2dec上下文结构
    struct JBIG2Context {
        Jbig2Ctx* ctx = nullptr;

        ~JBIG2Context() {
            if (ctx) jbig2_ctx_free(ctx);
        }
    };

    // 解码后的页面数据
    struct PageData {
        Jbig2Image* image = nullptr;
        JBIG2ImageInfo info;
        Jbig2Ctx* ctx = nullptr;  // 添加上下文指针

        PageData() : image(nullptr), ctx(nullptr) {}

        PageData(Jbig2Image* img, Jbig2Ctx* context)
            : image(img), ctx(context) {
        }

        ~PageData() {
            if (image && ctx) {
                jbig2_release_page(ctx, image);
            }
        }

        // 禁用拷贝
        PageData(const PageData&) = delete;
        PageData& operator=(const PageData&) = delete;

        // 允许移动
        PageData(PageData&& other) noexcept
            : image(other.image), info(std::move(other.info)), ctx(other.ctx) {
            other.image = nullptr;
            other.ctx = nullptr;
        }

        PageData& operator=(PageData&& other) noexcept {
            if (this != &other) {
                if (image && ctx) {
                    jbig2_release_page(ctx, image);
                }
                image = other.image;
                info = std::move(other.info);
                ctx = other.ctx;
                other.image = nullptr;
                other.ctx = nullptr;
            }
            return *this;
        }
    };

    // 静态回调函数
    static void errorCallback(void* data, const char* msg,
        Jbig2Severity severity, uint32_t seg_idx);

    // 内部解码方法
    void decodeInternal(const uint8_t* data, size_t length);

    // 收集所有页面
    void collectAllPages();

    // 转换像素数据
    std::vector<uint8_t> convertTo8Bit(const PageData& page) const;
    std::vector<uint8_t> convertTo1Bit(const PageData& page) const;

    // 成员变量
    std::unique_ptr<JBIG2Context> context_;
    std::vector<PageData> pages_;  // 改为存储对象而不是指针
    bool invertPixels_;
    std::string lastError_;
    bool isDecoded_;
};

#endif // JBIG2_DECODER_HPP
#endif

实现文件

cpp 复制代码
#if 1
#include "JBIG2Decoder.h"

#include <fstream>
#include <sstream>
#include <cstring>
#include <algorithm>
#define USE_LIBPNG
// PNG保存功能需要libpng库
#ifdef USE_LIBPNG
#include <png.h>
#endif

JBIG2Decoder::JBIG2Decoder(bool invertPixels)
    : invertPixels_(invertPixels), isDecoded_(false) {
}

JBIG2Decoder::~JBIG2Decoder() {
    clear();
}

JBIG2Decoder::JBIG2Decoder(JBIG2Decoder&& other) noexcept
    : context_(std::move(other.context_)),
    pages_(std::move(other.pages_)),
    invertPixels_(other.invertPixels_),
    lastError_(std::move(other.lastError_)),
    isDecoded_(other.isDecoded_) {
    other.isDecoded_ = false;
}

JBIG2Decoder& JBIG2Decoder::operator=(JBIG2Decoder&& other) noexcept {
    if (this != &other) {
        clear();
        context_ = std::move(other.context_);
        pages_ = std::move(other.pages_);
        invertPixels_ = other.invertPixels_;
        lastError_ = std::move(other.lastError_);
        isDecoded_ = other.isDecoded_;
        other.isDecoded_ = false;
    }
    return *this;
}

void JBIG2Decoder::errorCallback(void* data, const char* msg,
    Jbig2Severity severity, uint32_t seg_idx) {
    JBIG2Decoder* decoder = static_cast<JBIG2Decoder*>(data);
    if (!decoder) return;

    std::ostringstream oss;

    switch (severity) {
    case JBIG2_SEVERITY_FATAL:
        oss << "FATAL error: ";
        break;
    case JBIG2_SEVERITY_WARNING:
        oss << "Warning: ";
        break;
    case JBIG2_SEVERITY_INFO:
        oss << "Info: ";
        break;
    case JBIG2_SEVERITY_DEBUG:
        oss << "Debug: ";
        break;
    default:
        oss << "Unknown: ";
        break;
    }

    oss << msg << " (segment " << seg_idx << ")";
    decoder->lastError_ = oss.str();

    // 致命错误需要抛出异常
    if (severity == JBIG2_SEVERITY_FATAL) {
        throw JBIG2DecoderException(oss.str());
    }
}

int JBIG2Decoder::decode(const uint8_t* data, size_t length) {
    clear();

    try {
        decodeInternal(data, length);
        collectAllPages();
        isDecoded_ = true;
        return static_cast<int>(pages_.size());
    }
    catch (const JBIG2DecoderException&) {
        clear();
        throw;
    }
    catch (const std::exception& e) {
        clear();
        throw JBIG2DecoderException(std::string("Unexpected error: ") + e.what());
    }
}

int JBIG2Decoder::decodeFromFile(const std::string& filename) {
    // 读取文件
    std::ifstream file(filename, std::ios::binary | std::ios::ate);
    if (!file) {
        throw JBIG2DecoderException("Cannot open file: " + filename);
    }

    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);

    std::vector<uint8_t> buffer(size);
    if (!file.read(reinterpret_cast<char*>(buffer.data()), size)) {
        throw JBIG2DecoderException("Cannot read file: " + filename);
    }

    return decode(buffer.data(), static_cast<size_t>(size));
}

void JBIG2Decoder::decodeInternal(const uint8_t* data, size_t length) {
    // 创建上下文
    context_ = std::make_unique<JBIG2Context>();

    // 使用默认分配器,使用默认选项0而不是JBIG2_OPTIONS_EMBEDDED
    context_->ctx = jbig2_ctx_new(nullptr, (Jbig2Options)0,
        nullptr, errorCallback, this);

    if (!context_->ctx) {
        throw JBIG2DecoderException("Failed to create JBIG2 context");
    }

    // 输入数据
    if (jbig2_data_in(context_->ctx, data, length) < 0) {
        throw JBIG2DecoderException("Failed to process JBIG2 data");
    }

    // 完成页面
    if (jbig2_complete_page(context_->ctx) < 0) {
        std::string error = "Failed to complete JBIG2 page";
        if (!lastError_.empty()) {
            error += ": " + lastError_;
        }
        throw JBIG2DecoderException(error);
    }
}

void JBIG2Decoder::collectAllPages() {
    if (!context_ || !context_->ctx) {
        return;
    }

    // 收集所有页面
    Jbig2Image* image;
    int pageNum = 0;

    while ((image = jbig2_page_out(context_->ctx)) != nullptr) {
        // 创建PageData对象,传入正确的上下文指针
        PageData pageData(image, context_->ctx);

        // 填充图像信息
        pageData.info.width = image->width;
        pageData.info.height = image->height;
        pageData.info.xres = 72;  // JBIG2通常不包含分辨率信息
        pageData.info.yres = 72;
        pageData.info.bitsPerPixel = 1;
        pageData.info.pageCount = ++pageNum;

        pages_.push_back(std::move(pageData));
    }

    // 更新多页信息
    if (!pages_.empty()) {
        bool isMultiPage = pages_.size() > 1;
        for (auto& page : pages_) {
            page.info.isMultiPage = isMultiPage;
            page.info.pageCount = static_cast<int>(pages_.size());
        }
    }
}

int JBIG2Decoder::getPageCount() const {
    return static_cast<int>(pages_.size());
}

JBIG2ImageInfo JBIG2Decoder::getImageInfo(int pageNum) const {
    if (!isDecoded_ || pages_.empty()) {
        throw JBIG2DecoderException("No JBIG2 data decoded");
    }

    if (pageNum < 0 || pageNum >= static_cast<int>(pages_.size())) {
        throw JBIG2DecoderException("Invalid page number");
    }

    return pages_[pageNum].info;
}

std::vector<uint8_t> JBIG2Decoder::getBitmapData(int pageNum) const {
    if (!isDecoded_ || pages_.empty()) {
        throw JBIG2DecoderException("No JBIG2 data decoded");
    }

    if (pageNum < 0 || pageNum >= static_cast<int>(pages_.size())) {
        throw JBIG2DecoderException("Invalid page number");
    }

    return convertTo8Bit(pages_[pageNum]);
}

std::vector<uint8_t> JBIG2Decoder::getBitmapData1Bit(int pageNum) const {
    if (!isDecoded_ || pages_.empty()) {
        throw JBIG2DecoderException("No JBIG2 data decoded");
    }

    if (pageNum < 0 || pageNum >= static_cast<int>(pages_.size())) {
        throw JBIG2DecoderException("Invalid page number");
    }

    return convertTo1Bit(pages_[pageNum]);
}
//
//std::vector<uint8_t> JBIG2Decoder::convertTo8Bit(const PageData& page) const {
//    const Jbig2Image* image = page.image;
//    if (!image || !image->data) {
//        return {};
//    }
//
//    int width = image->width;
//    int height = image->height;
//    int stride = image->stride;
//
//    std::vector<uint8_t> result(width * height);
//
//    for (int y = 0; y < height; ++y) {
//        const uint8_t* src = image->data + y * stride;
//        uint8_t* dst = result.data() + y * width;
//
//        for (int x = 0; x < width; ++x) {
//            int byteIndex = x / 8;
//            int bitIndex = 7 - (x % 8);  // JBIG2使用MSB在前
//
//            bool bit = (src[byteIndex] >> bitIndex) & 1;
//
//            // 应用像素反转
//            if (invertPixels_) {
//                bit = !bit;
//            }
//
//            dst[x] = bit ? 255 : 0;  // 1=白(255), 0=黑(0)
//        }
//    }
//
//    return result;
//}
//
//std::vector<uint8_t> JBIG2Decoder::convertTo1Bit(const PageData& page) const {
//    const Jbig2Image* image = page.image;
//    if (!image || !image->data) {
//        return {};
//    }
//
//    int width = image->width;
//    int height = image->height;
//    int stride = image->stride;
//
//    // 计算1位格式的stride(向上取整到字节)
//    int dstStride = (width + 7) / 8;
//    std::vector<uint8_t> result(dstStride * height, 0);
//
//    for (int y = 0; y < height; ++y) {
//        const uint8_t* src = image->data + y * stride;
//        uint8_t* dst = result.data() + y * dstStride;
//
//        for (int x = 0; x < width; ++x) {
//            int srcByteIndex = x / 8;
//            int srcBitIndex = 7 - (x % 8);
//
//            int dstByteIndex = x / 8;
//            int dstBitIndex = 7 - (x % 8);
//
//            bool bit = (src[srcByteIndex] >> srcBitIndex) & 1;
//
//            // 应用像素反转
//            if (invertPixels_) {
//                bit = !bit;
//            }
//
//            if (bit) {
//                dst[dstByteIndex] |= (1 << dstBitIndex);
//            }
//        }
//    }
//
//    return result;
//}
std::vector<uint8_t> JBIG2Decoder::convertTo8Bit(const PageData& page) const {
    const Jbig2Image* image = page.image;
    if (!image || !image->data) {
        return {};
    }

    int width = image->width;
    int height = image->height;
    int stride = image->stride;

    std::vector<uint8_t> result(width * height);

    for (int y = 0; y < height; ++y) {
        const uint8_t* src = image->data + y * stride;
        uint8_t* dst = result.data() + y * width;

        for (int x = 0; x < width; ++x) {
            int byteIndex = x / 8;
            int bitIndex = 7 - (x % 8);  // JBIG2使用MSB在前

            bool bit = (src[byteIndex] >> bitIndex) & 1;

            // 修正:1=黑色(0), 0=白色(255)
            // 然后根据invertPixels_选项决定是否反转
            uint8_t pixelValue;
            if (bit) {  // 原始位为1,表示黑色
                pixelValue = 0;  // 黑色 = 0
            }
            else {    // 原始位为0,表示白色
                pixelValue = 255;  // 白色 = 255
            }

            // 应用像素反转选项
            if (invertPixels_) {
                pixelValue = 255 - pixelValue;  // 反转:0变255,255变0
            }

            dst[x] = pixelValue;
        }
    }

    return result;
}

std::vector<uint8_t> JBIG2Decoder::convertTo1Bit(const PageData& page) const {
    const Jbig2Image* image = page.image;
    if (!image || !image->data) {
        return {};
    }

    int width = image->width;
    int height = image->height;
    int stride = image->stride;

    // 计算1位格式的stride(向上取整到字节)
    int dstStride = (width + 7) / 8;
    std::vector<uint8_t> result(dstStride * height, 0);

    for (int y = 0; y < height; ++y) {
        const uint8_t* src = image->data + y * stride;
        uint8_t* dst = result.data() + y * dstStride;

        for (int x = 0; x < width; ++x) {
            int srcByteIndex = x / 8;
            int srcBitIndex = 7 - (x % 8);

            int dstByteIndex = x / 8;
            int dstBitIndex = 7 - (x % 8);

            bool bit = (src[srcByteIndex] >> srcBitIndex) & 1;

            // 修正:1=黑色(位值1),0=白色(位值0)
            bool outputBit = bit;  // 默认:1=黑,0=白

            // 应用像素反转选项
            if (invertPixels_) {
                outputBit = !bit;  // 反转:黑变白,白变黑
            }

            if (outputBit) {  // 1 = 黑色
                dst[dstByteIndex] |= (1 << dstBitIndex);
            }
            // 注意:白色(0)不需要设置,因为我们已经初始化为0
        }
    }

    return result;
}

bool JBIG2Decoder::saveAsPNG(int pageNum, const std::string& filename) const {
#ifdef USE_LIBPNG
    if (!isDecoded_ || pages_.empty()) {
        throw JBIG2DecoderException("No JBIG2 data decoded");
    }

    if (pageNum < 0 || pageNum >= static_cast<int>(pages_.size())) {
        throw JBIG2DecoderException("Invalid page number");
    }

    auto bitmap = getBitmapData(pageNum);
    const auto& info = pages_[pageNum].info;

    FILE* fp = nullptr;
#ifdef _WIN32
    errno_t err = fopen_s(&fp, filename.c_str(), "wb");
    if (err != 0) fp = nullptr;
#else
    fp = fopen(filename.c_str(), "wb");
#endif

    if (!fp) {
        throw JBIG2DecoderException("Cannot create PNG file");
    }

    png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
        nullptr, nullptr, nullptr);
    if (!png) {
        fclose(fp);
        throw JBIG2DecoderException("Failed to create PNG write structure");
    }

    png_infop info_ptr = png_create_info_struct(png);
    if (!info_ptr) {
        png_destroy_write_struct(&png, nullptr);
        fclose(fp);
        throw JBIG2DecoderException("Failed to create PNG info structure");
    }

    if (setjmp(png_jmpbuf(png))) {
        png_destroy_write_struct(&png, &info_ptr);
        fclose(fp);
        throw JBIG2DecoderException("Error during PNG creation");
    }

    png_init_io(png, fp);

    // 设置PNG头
    png_set_IHDR(png, info_ptr, info.width, info.height,
        8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE,
        PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

    png_write_info(png, info_ptr);

    // 写入行数据
    std::vector<uint8_t*> rowPointers(info.height);
    for (int y = 0; y < info.height; ++y) {
        rowPointers[y] = bitmap.data() + y * info.width;
    }

    png_write_image(png, rowPointers.data());
    png_write_end(png, nullptr);

    // 清理
    png_destroy_write_struct(&png, &info_ptr);
    fclose(fp);

    return true;
#else
    throw JBIG2DecoderException("PNG support not compiled in. Define USE_LIBPNG and link with libpng.");
#endif
}

bool JBIG2Decoder::saveAsPBM(int pageNum, const std::string& filename) const {
    if (!isDecoded_ || pages_.empty()) {
        throw JBIG2DecoderException("No JBIG2 data decoded");
    }

    if (pageNum < 0 || pageNum >= static_cast<int>(pages_.size())) {
        throw JBIG2DecoderException("Invalid page number");
    }

    auto bitmap = getBitmapData1Bit(pageNum);
    const auto& info = pages_[pageNum].info;

    std::ofstream file(filename, std::ios::binary);
    if (!file) {
        throw JBIG2DecoderException("Cannot create PBM file");
    }

    // PBM头:P4表示二进制PBM,宽度和高度
    file << "P4\n" << info.width << " " << info.height << "\n";

    // 写入位图数据
    file.write(reinterpret_cast<const char*>(bitmap.data()), bitmap.size());

    return file.good();
}

void JBIG2Decoder::clear() {
    pages_.clear();  // PageData析构函数会自动释放图像
    context_.reset();
    lastError_.clear();
    isDecoded_ = false;
}

bool JBIG2Decoder::isDecoded() const {
    return isDecoded_;
}

void JBIG2Decoder::setInvertPixels(bool invert) {
    invertPixels_ = invert;
}

bool JBIG2Decoder::getInvertPixels() const {
    return invertPixels_;
}

std::string JBIG2Decoder::getLastError() const {
    return lastError_;
}

#endif

测试代码

cpp 复制代码
#include "JBIG2Decoder.h"
#include <iostream>
#include <vector>
#include <cstdint>
#define USE_LIBPNG
int main(int argc, char* argv[]) {
    //if (argc < 2) {
    //    std::cerr << "Usage: " << argv[0] << " <jbig2_file> [output_prefix]" << std::endl;
    //    return 1;
    //}

    std::string filename = "C:\\Code\\jbig2_tests\\generic_4960x7016.jb2";
    std::string outputPrefix = "C:\\Code\\jbig2_decode_output";

    try {
        JBIG2Decoder decoder;

        std::cout << "Decoding JBIG2 file: " << filename << std::endl;
        int pageCount = decoder.decodeFromFile(filename);

        std::cout << "Successfully decoded " << pageCount << " page(s)" << std::endl;

        for (int i = 0; i < pageCount; ++i) {
            auto info = decoder.getImageInfo(i);
            std::cout << "Page " << i + 1 << ": "
                << info.width << "x" << info.height
                << " pixels" << std::endl;

            // 保存为PBM
            std::string pbmName = outputPrefix + "_page" + std::to_string(i + 1) + ".pbm";
            if (decoder.saveAsPBM(i, pbmName)) {
                std::cout << "  Saved as: " << pbmName << std::endl;
            }

            // 如果支持PNG,也保存为PNG
#ifdef USE_LIBPNG
            std::string pngName = outputPrefix + "_page" + std::to_string(i + 1) + ".png";
            if (decoder.saveAsPNG(i, pngName)) {
                std::cout << "  Saved as: " << pngName << std::endl;
            }
#endif

            // 获取位图数据
            auto bitmap = decoder.getBitmapData(i);
            std::cout << "  Bitmap size: " << bitmap.size() << " bytes" << std::endl;

            // 获取1位位图数据
            auto bitmap1bit = decoder.getBitmapData1Bit(i);
            std::cout << "  1-bit bitmap size: " << bitmap1bit.size() << " bytes" << std::endl;
        }

        return 0;

    }
    catch (const JBIG2DecoderException& e) {
        std::cerr << "JBIG2 Decoder Error: " << e.what() << std::endl;
        return 1;
    }
    catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
}

#endif
相关推荐
断剑zou天涯4 小时前
【算法笔记】Manacher算法
java·笔记·算法
_一路向北_5 小时前
爬虫框架:Feapder使用心得
爬虫·python
皇族崛起5 小时前
【3D标注】- Unreal Engine 5.7 与 Python 交互基础
python·3d·ue5
你想知道什么?5 小时前
Python基础篇(上) 学习笔记
笔记·python·学习
monster000w5 小时前
大模型微调过程
人工智能·深度学习·算法·计算机视觉·信息与通信
曼巴UE55 小时前
UE5 C++ 动态多播
java·开发语言
小小晓.5 小时前
Pinely Round 4 (Div. 1 + Div. 2)
c++·算法
SHOJYS5 小时前
学习离线处理 [CSP-J 2022 山东] 部署
数据结构·c++·学习·算法
biter down5 小时前
c++:两种建堆方式的时间复杂度深度解析
算法
zhishidi5 小时前
推荐算法优缺点及通俗解读
算法·机器学习·推荐算法