如何正确实现QImage与QByteArray互转并通过RPC无损传输?
问题根源
你现在的核心问题出在用了JPG格式做图像编码——JPG是有损压缩格式,哪怕把压缩质量拉到最高,也会丢失部分图像细节,根本做不到100%还原。另外服务器代码里还犯了个低级错误:重复定义了QImage image,后面操作的其实是个空的QImage,这也会导致图像异常。
解决方案
要实现完全一致的图像传输,有两种靠谱方案:
方案1:用无损压缩格式(优先选PNG)
PNG是无损压缩,编码解码过程正确的话,能完美还原原始图像,还能兼顾传输体积。
客户端修改
把所有JPG替换成PNG,还可以指定最高压缩级别(不影响无损性):
void ServerConnection::MakeRequest(QImage& imageData, const uint8_t imageId) { receivedImages_.clear(); grpc::ChannelArguments ch_args; ch_args.SetMaxReceiveMessageSize(-1); ch_args.SetMaxSendMessageSize(-1); channel_ = grpc::CreateCustomChannel(ip_ + ":" + std::to_string(port_), grpc::InsecureChannelCredentials(), ch_args); stub_ = ImageService::NewStub(channel_); ImageRequest request; request.set_image_id(std::to_string(imageId)); QByteArray byteArray; QBuffer buffer(&byteArray); buffer.open(QIODevice::WriteOnly); // 换成PNG格式,9是最高无损压缩级别 if (!imageData.isNull() && imageData.save(&buffer, "PNG", 9)) { request.set_image_data(byteArray.toBase64().toStdString()); ImageResponse response; grpc::ClientContext context; grpc::Status status = stub_->ProcessImage(&context, request, &response); if (status.ok()) { std::cout << "Received set ID: " << response.set_id() << std::endl; for (const auto& image_data : response.image_data()) { // 接收时也用PNG格式解析 QByteArray byteArray = QByteArray::fromBase64(QString::fromStdString(image_data).toUtf8()); QImage image = QImage::fromData(byteArray, "PNG"); receivedImages_.push_back(image); } } else { std::cerr << "Error: " << status.error_message() << std::endl; } } else { std::cerr << "Error: Failed to save QImage to QByteArray" << std::endl; } }
服务器修改
先修复重复定义QImage image的错误,再改成PNG处理逻辑:
grpc::Status ProcessImage(grpc::ServerContext* context, const ImageRequest* request, ImageResponse* response) override { QByteArray imageData = QByteArray::fromBase64(request->image_data().c_str()); if (imageData.isNull()) { return grpc::Status::CANCELLED; } std::cout << "Image data is valid" << std::endl; // 只定义一次QImage,用PNG格式加载 QImage image; if (!image.loadFromData(imageData, "PNG")) { return grpc::Status::INVALID_ARGUMENT; } // 保存也用PNG,避免二次损耗 QString savePath = "output_image.png"; image.save(savePath, "PNG"); // 如果需要回传图像给客户端,同样用PNG编码 QByteArray byteArray; QBuffer buffer(&byteArray); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG", 9); response->add_image_data(byteArray.toBase64().toStdString()); return grpc::Status::OK; }
方案2:直接传输原始像素数据(绝对无损耗)
如果对图像精度要求到极致,不在乎传输体积,直接传QImage的原始像素字节是最稳妥的,完全跳过压缩步骤。
客户端修改
需要先修改你的ImageRequest和ImageResponse proto文件,添加width、height、format三个字段用来传输图像元信息:
void ServerConnection::MakeRequest(QImage& imageData, const uint8_t imageId) { receivedImages_.clear(); grpc::ChannelArguments ch_args; ch_args.SetMaxReceiveMessageSize(-1); ch_args.SetMaxSendMessageSize(-1); channel_ = grpc::CreateCustomChannel(ip_ + ":" + std::to_string(port_), grpc::InsecureChannelCredentials(), ch_args); stub_ = ImageService::NewStub(channel_); ImageRequest request; request.set_image_id(std::to_string(imageId)); // 把图像的宽、高、格式传给服务器 request.set_width(imageData.width()); request.set_height(imageData.height()); request.set_format(static_cast<int>(imageData.format())); // 直接取原始像素字节 QByteArray byteArray(reinterpret_cast<const char*>(imageData.bits()), imageData.sizeInBytes()); request.set_image_data(byteArray.toBase64().toStdString()); ImageResponse response; grpc::ClientContext context; grpc::Status status = stub_->ProcessImage(&context, request, &response); if (status.ok()) { std::cout << "Received set ID: " << response.set_id() << std::endl; for (const auto& image_data : response.image_data()) { // 解析服务器回传的元信息 int width = response.width(); int height = response.height(); QImage::Format format = static_cast<QImage::Format>(response.format()); // 还原像素数据,必须用copy()做深拷贝,否则原字节数组释放后图像会失效 QByteArray byteArray = QByteArray::fromBase64(QString::fromStdString(image_data).toUtf8()); QImage image(reinterpret_cast<const uchar*>(byteArray.constData()), width, height, format); receivedImages_.push_back(image.copy()); } } else { std::cerr << "Error: " << status.error_message() << std::endl; } }
服务器修改
grpc::Status ProcessImage(grpc::ServerContext* context, const ImageRequest* request, ImageResponse* response) override { QByteArray imageData = QByteArray::fromBase64(request->image_data().c_str()); if (imageData.isNull()) { return grpc::Status::CANCELLED; } // 从请求里拿图像元信息 int width = request->width(); int height = request->height(); QImage::Format format = static_cast<QImage::Format>(request->format()); // 构造QImage后做深拷贝,避免浅拷贝的生命周期问题 QImage image(reinterpret_cast<const uchar*>(imageData.constData()), width, height, format); QImage deepCopiedImage = image.copy(); // 保存用PNG避免损耗 deepCopiedImage.save("output_image.png"); // 回传图像时同样带上元信息和原始像素 response->set_width(deepCopiedImage.width()); response->set_height(deepCopiedImage.height()); response->set_format(static_cast<int>(deepCopiedImage.format())); QByteArray byteArray(reinterpret_cast<const char*>(deepCopiedImage.bits()), deepCopiedImage.sizeInBytes()); response->add_image_data(byteArray.toBase64().toStdString()); return grpc::Status::OK; }
额外提醒
- 方案1(PNG)平衡了无损性和传输体积,适合绝大多数场景;
- 方案2(原始像素)体积最大,但绝对无损耗,适合对精度要求极高的场景;
- 传输Base64会额外增加约33%的体积,如果你的RPC支持直接传二进制数据,可以去掉Base64编码,效率会更高。
内容的提问来源于stack exchange,提问作者Lys4nder
相关产品推荐
相关产品推荐

