2025年3月28日 星期五 甲辰(龙)年 月廿七 设为首页 加入收藏
rss
您当前的位置:首页 > 电子 > 单片机

算法篇_RGB图像数据压缩与解压(单片机使用)

时间:10-31来源:作者:点击数:25
城东书院 www.cdsy.xyz

书接上回(上一篇文章已经讲解了如何封装BMP格式图片保存到本地SD卡)。

一、前言

在当今物联网(IoT)技术快速发展的背景下,嵌入式系统在各种应用场景中扮演着越来越重要的角色。随着物联网设备数量的不断增长,数据传输成为了关键的一环,尤其是在资源受限的环境中,如何高效地传输数据变得尤为重要。本文将介绍一个基于STM32F103ZET6微控制器的图像采集系统,该系统利用OV7725摄像头采集RGB565格式的图像,并通过MQTT协议将图像数据上传至阿里云物联网平台。 但是原始RGB565图像数据量巨大,直接传输会带来较高的网络负载,需要设计有效的压缩算法来减小数据体积。

考虑到单片机资源有限,无法使用第三方优秀的开源库,当前就基于Run-Length Encoding (RLE)无损压缩算法来减小图像数据的传输负担。RLE算法是一种简单有效的压缩方法,尤其适用于图像中存在大量连续重复像素值的情况。通过对重复像素的值和重复次数进行记录,可以显著减小图像数据的大小,从而降低网络传输的负载。下面会详细介绍RLE算法的设计与实现过程,以及如何在STM32F103ZET6微控制器上实现图像数据的压缩与解压,最终实现在阿里云物联网平台上进行高效的数据传输,并通过APP上位机进行图像数据的拉取与显示。

当前文章最重要的知识点是介绍: 数据的压缩和解压。

下面这个图是用的chargptAI自动生成的,不是实物图。

image-20240808155718461

二、算法选型

在单片机上进行图像数据压缩,需要选择是使用轻量级、简单但有效的压缩算法。

以下2个是可以用的压缩算法,适合在单片机上实现并能够有效减小数据量。

2.1 Run-Length Encoding (RLE)

RLE 是一种非常简单的无损压缩算法,特别适用于图像中有大量连续重复像素值的情况。通过记录重复像素的值和重复次数来实现压缩。

下面是算法的核心实现: 解压和压缩

  • #include <stdint.h>
  • #include <stdlib.h>
  • #include <stdio.h>
  • // RLE 压缩函数
  • size_t RLE_compress(const uint16_t *input, size_t length, uint8_t *output) {
  • size_t out_index = 0;
  • for (size_t i = 0; i < length; ) {
  • uint16_t value = input[i];
  • size_t run_length = 1;
  • while (i + run_length < length && input[i + run_length] == value && run_length < 255) {
  • run_length++;
  • }
  • output[out_index++] = (uint8_t)(run_length);
  • output[out_index++] = (uint8_t)(value >> 8);
  • output[out_index++] = (uint8_t)(value & 0xFF);
  • i += run_length;
  • }
  • return out_index;
  • }
  • // RLE 解压函数
  • size_t RLE_decompress(const uint8_t *input, size_t length, uint16_t *output) {
  • size_t out_index = 0;
  • for (size_t i = 0; i < length; i += 3) {
  • uint8_t run_length = input[i];
  • uint16_t value = (input[i + 1] << 8) | input[i + 2];
  • for (size_t j = 0; j < run_length; j++) {
  • output[out_index++] = value;
  • }
  • }
  • return out_index;
  • }

2.2 Differential Pulse-Code Modulation (DPCM)

DPCM 适用于图像中连续像素值变化较小的情况。通过记录相邻像素值的差值来实现压缩。

下面是算法的核心实现: 解压和压缩

  • #include <stdint.h>
  • #include <stdlib.h>
  • #include <stdio.h>
  • // DPCM 压缩函数
  • size_t DPCM_compress(const uint16_t *input, size_t length, int16_t *output) {
  • output[0] = input[0];
  • for (size_t i = 1; i < length; i++) {
  • output[i] = input[i] - input[i - 1];
  • }
  • return length;
  • }
  • // DPCM 解压函数
  • size_t DPCM_decompress(const int16_t *input, size_t length, uint16_t *output) {
  • output[0] = input[0];
  • for (size_t i = 1; i < length; i++) {
  • output[i] = output[i - 1] + input[i];
  • }
  • return length;
  • }

三、采用RLE算法实现图像压缩

下面这段代码采用Run-Length Encoding (RLE) 算法对 RGB565 格式图像数据进行压缩和解压。

代码里定义了一个常量数组 gImage_rgb565_100x100,存储 100x100 图像的 RGB565 像素数据(通过实际摄像头采集得到的)。

RLE 压缩函数 RLE_compress 遍历输入的 RGB565 数据数组,查找连续相同的像素值,并记录它们的长度和值,将这些信息存储到输出的压缩数组中。每次遇到一段连续的相同值时,它会记录该段的长度(最大为 255)和该像素值的高位和低位字节。压缩后的数据长度返回给调用者。解压函数 RLE_decompress 读取压缩后的数组,根据记录的长度和值还原原始数据,将这些值存储到输出的解压数组中。main 函数中,计算图像数据的长度,然后分配内存用于存储压缩和解压后的数据,分别调用压缩和解压函数进行处理。最后,打印出原始数据大小、压缩后的数据大小以及压缩率,以验证压缩效果。所有动态分配的内存最终被释放,以避免内存泄漏。通过这样的实现,可以在不使用第三方库的情况下,有效地减少图像数据的传输负荷,提高传输效率。

实现代码:

  • #include <stdint.h>
  • #include <stdio.h>
  • #include <stdlib.h>
  • // RLE 压缩函数
  • size_t RLE_compress(const uint16_t* input, size_t length, uint8_t* output) {
  • size_t out_index = 0;
  • for (size_t i = 0; i < length; ) {
  • uint16_t value = input[i];
  • size_t run_length = 1;
  • while (i + run_length < length && input[i + run_length] == value && run_length < 255) {
  • run_length++;
  • }
  • output[out_index++] = (uint8_t)(run_length);
  • output[out_index++] = (uint8_t)(value >> 8);
  • output[out_index++] = (uint8_t)(value & 0xFF);
  • i += run_length;
  • }
  • return out_index;
  • }
  • // RLE 解压函数
  • size_t RLE_decompress(const uint8_t* input, size_t length, uint16_t* output) {
  • size_t out_index = 0;
  • for (size_t i = 0; i < length; i += 3) {
  • uint8_t run_length = input[i];
  • uint16_t value = (input[i + 1] << 8) | input[i + 2];
  • for (size_t j = 0; j < run_length; j++) {
  • output[out_index++] = value;
  • }
  • }
  • return out_index;
  • }
  • int main() {
  • size_t length = sizeof(gImage_rgb565_100x100) / 2;
  • uint16_t* rgb565_array = (uint16_t*)gImage_rgb565_100x100;
  • // 分配 RLE 压缩后的数组
  • uint8_t* rle_compressed = (uint8_t*)malloc(length * 3 * sizeof(uint8_t));
  • if (rle_compressed == NULL) {
  • perror("Unable to allocate memory for RLE compressed array");
  • return 1;
  • }
  • // RLE 压缩
  • size_t rle_compressed_length = RLE_compress(rgb565_array, length, rle_compressed);
  • // 分配 RLE 解压后的数组
  • uint16_t* rle_decompressed = (uint16_t*)malloc(length * sizeof(uint16_t));
  • if (rle_decompressed == NULL) {
  • perror("Unable to allocate memory for RLE decompressed array");
  • return 1;
  • }
  • // RLE 解压
  • size_t rle_decompressed_length = RLE_decompress(rle_compressed, rle_compressed_length, rle_decompressed);
  • // 打印 RLE 压缩率
  • printf("RLE 压缩算法:\n");
  • printf("原始大小: %zu bytes\n", length * 2);
  • printf("压缩后大小: %zu bytes\n", rle_compressed_length);
  • printf("压缩比: %.2f%%\n", (double)rle_compressed_length / (length * 2) * 100);
  • // 释放内存
  • free(rle_compressed);
  • free(rle_decompressed);
  • return 0;
  • }

运行效果如下:

image-20240808151916619

四、哈夫曼编码实现压缩和解压

哈夫曼编码是一种常用的无损压缩算法,通过构建哈夫曼树,根据字符频率生成最优编码,从而实现数据压缩。

4.1 哈夫曼编码压缩自定义数据与还原

下面代码使用哈夫曼编码对一段自定义数据进行压缩和解压,并打印压缩前后的大小和压缩率,验证压缩效果。

  • #include <stdio.h>
  • #include <stdlib.h>
  • #include <string.h>
  • // 定义树节点结构
  • typedef struct Node {
  • unsigned char ch;
  • unsigned int freq;
  • struct Node* left, * right;
  • } Node;
  • // 优先队列
  • typedef struct {
  • Node** nodes;
  • int size;
  • int capacity;
  • } PriorityQueue;
  • PriorityQueue* createPriorityQueue(int capacity) {
  • PriorityQueue* pq = (PriorityQueue*)malloc(sizeof(PriorityQueue));
  • pq->nodes = (Node**)malloc(capacity * sizeof(Node*));
  • pq->size = 0;
  • pq->capacity = capacity;
  • return pq;
  • }
  • void swapNodes(Node** a, Node** b) {
  • Node* t = *a;
  • *a = *b;
  • *b = t;
  • }
  • void heapify(PriorityQueue* pq, int idx) {
  • int smallest = idx;
  • int left = 2 * idx + 1;
  • int right = 2 * idx + 2;
  • if (left < pq->size && pq->nodes[left]->freq < pq->nodes[smallest]->freq) {
  • smallest = left;
  • }
  • if (right < pq->size && pq->nodes[right]->freq < pq->nodes[smallest]->freq) {
  • smallest = right;
  • }
  • if (smallest != idx) {
  • swapNodes(&pq->nodes[smallest], &pq->nodes[idx]);
  • heapify(pq, smallest);
  • }
  • }
  • Node* extractMin(PriorityQueue* pq) {
  • Node* temp = pq->nodes[0];
  • pq->nodes[0] = pq->nodes[pq->size - 1];
  • pq->size--;
  • heapify(pq, 0);
  • return temp;
  • }
  • void insertPriorityQueue(PriorityQueue* pq, Node* node) {
  • pq->size++;
  • int i = pq->size - 1;
  • while (i && node->freq < pq->nodes[(i - 1) / 2]->freq) {
  • pq->nodes[i] = pq->nodes[(i - 1) / 2];
  • i = (i - 1) / 2;
  • }
  • pq->nodes[i] = node;
  • }
  • int isLeaf(Node* node) {
  • return !(node->left) && !(node->right);
  • }
  • PriorityQueue* buildPriorityQueue(unsigned char data[], int freq[], int size) {
  • PriorityQueue* pq = createPriorityQueue(size);
  • for (int i = 0; i < size; ++i) {
  • if (freq[data[i]] > 0) {
  • Node* node = (Node*)malloc(sizeof(Node));
  • node->ch = data[i];
  • node->freq = freq[data[i]];
  • node->left = node->right = NULL;
  • pq->nodes[pq->size++] = node;
  • }
  • }
  • for (int i = (pq->size - 1) / 2; i >= 0; --i) {
  • heapify(pq, i);
  • }
  • return pq;
  • }
  • Node* buildHuffmanTree(unsigned char data[], int freq[], int size) {
  • Node* left, * right, * top;
  • PriorityQueue* pq = buildPriorityQueue(data, freq, size);
  • while (pq->size != 1) {
  • left = extractMin(pq);
  • right = extractMin(pq);
  • top = (Node*)malloc(sizeof(Node));
  • top->ch = '\0';
  • top->freq = left->freq + right->freq;
  • top->left = left;
  • top->right = right;
  • insertPriorityQueue(pq, top);
  • }
  • return extractMin(pq);
  • }
  • void printCodes(Node* root, int arr[], int top, char** huffmanCodes) {
  • if (root->left) {
  • arr[top] = 0;
  • printCodes(root->left, arr, top + 1, huffmanCodes);
  • }
  • if (root->right) {
  • arr[top] = 1;
  • printCodes(root->right, arr, top + 1, huffmanCodes);
  • }
  • if (isLeaf(root)) {
  • huffmanCodes[root->ch] = (char*)malloc(top + 1);
  • for (int i = 0; i < top; ++i) {
  • huffmanCodes[root->ch][i] = '0' + arr[i];
  • }
  • huffmanCodes[root->ch][top] = '\0';
  • }
  • }
  • char** buildHuffmanCodes(unsigned char data[], int freq[], int size) {
  • Node* root = buildHuffmanTree(data, freq, size);
  • int arr[100], top = 0;
  • char** huffmanCodes = (char**)malloc(256 * sizeof(char*));
  • for (int i = 0; i < 256; ++i) {
  • huffmanCodes[i] = NULL;
  • }
  • printCodes(root, arr, top, huffmanCodes);
  • return huffmanCodes;
  • }
  • void compress(unsigned char data[], int dataSize, char** huffmanCodes, unsigned char** compressedData, int* compressedSize) {
  • int bitCount = 0;
  • for (int i = 0; i < dataSize; ++i) {
  • bitCount += strlen(huffmanCodes[data[i]]);
  • }
  • *compressedSize = (bitCount + 7) / 8;
  • *compressedData = (unsigned char*)malloc(*compressedSize);
  • memset(*compressedData, 0, *compressedSize);
  • int byteIndex = 0, bitIndex = 0;
  • for (int i = 0; i < dataSize; ++i) {
  • char* code = huffmanCodes[data[i]];
  • for (int j = 0; code[j] != '\0'; ++j) {
  • if (code[j] == '1') {
  • (*compressedData)[byteIndex] |= (1 << (7 - bitIndex));
  • }
  • bitIndex++;
  • if (bitIndex == 8) {
  • bitIndex = 0;
  • byteIndex++;
  • }
  • }
  • }
  • }
  • void decompress(unsigned char* compressedData, int compressedSize, Node* root, unsigned char** decompressedData, int dataSize) {
  • *decompressedData = (unsigned char*)malloc(dataSize + 1);
  • Node* current = root;
  • int byteIndex = 0, bitIndex = 0;
  • for (int i = 0; i < dataSize; ++i) {
  • while (!isLeaf(current)) {
  • if (compressedData[byteIndex] & (1 << (7 - bitIndex))) {
  • current = current->right;
  • }
  • else {
  • current = current->left;
  • }
  • bitIndex++;
  • if (bitIndex == 8) {
  • bitIndex = 0;
  • byteIndex++;
  • }
  • }
  • (*decompressedData)[i] = current->ch;
  • current = root;
  • }
  • (*decompressedData)[dataSize] = '\0';
  • }
  • int main() {
  • // 自定义数据
  • unsigned char data[] = "我是DS小龙哥 我正在测试这一段数据,看看能不能压缩成功";
  • int dataSize = strlen((char*)data);
  • // 计算频率
  • int freq[256] = { 0 };
  • for (int i = 0; i < dataSize; ++i) {
  • freq[data[i]]++;
  • }
  • // 构建哈夫曼编码
  • char** huffmanCodes = buildHuffmanCodes(data, freq, 256);
  • // 压缩
  • unsigned char* compressedData;
  • int compressedSize;
  • compress(data, dataSize, huffmanCodes, &compressedData, &compressedSize);
  • // 解压
  • unsigned char* decompressedData;
  • Node* root = buildHuffmanTree(data, freq, 256);
  • decompress(compressedData, compressedSize, root, &decompressedData, dataSize);
  • // 打印压缩前后的大小和压缩率
  • printf("Original size: %d bytes\n", dataSize);
  • printf("Compressed size: %d bytes\n", compressedSize);
  • printf("Compression ratio: %.2f%%\n", (double)compressedSize / dataSize * 100);
  • // 验证解压结果
  • printf("Decompressed data: %s\n", decompressedData);
  • // 释放内存
  • free(compressedData);
  • free(decompressedData);
  • for (int i = 0; i < 256; ++i) {
  • if (huffmanCodes[i]) {
  • free(huffmanCodes[i]);
  • }
  • }
  • free(huffmanCodes);
  • return 0;
  • }

测试效果:

image-20240808154356035

4.2 哈夫曼编码压缩完成图像的压缩和还原

  • #include <stdio.h>
  • #include <stdlib.h>
  • #include <string.h>
  • // 定义树节点结构
  • typedef struct Node {
  • unsigned short ch; // 使用 unsigned short 表示 RGB565 数据
  • unsigned int freq;
  • struct Node* left, * right;
  • } Node;
  • // 优先队列
  • typedef struct {
  • Node** nodes;
  • int size;
  • int capacity;
  • } PriorityQueue;
  • PriorityQueue* createPriorityQueue(int capacity) {
  • PriorityQueue* pq = (PriorityQueue*)malloc(sizeof(PriorityQueue));
  • pq->nodes = (Node**)malloc(capacity * sizeof(Node*));
  • pq->size = 0;
  • pq->capacity = capacity;
  • return pq;
  • }
  • void swapNodes(Node** a, Node** b) {
  • Node* t = *a;
  • *a = *b;
  • *b = t;
  • }
  • void heapify(PriorityQueue* pq, int idx) {
  • int smallest = idx;
  • int left = 2 * idx + 1;
  • int right = 2 * idx + 2;
  • if (left < pq->size && pq->nodes[left]->freq < pq->nodes[smallest]->freq) {
  • smallest = left;
  • }
  • if (right < pq->size && pq->nodes[right]->freq < pq->nodes[smallest]->freq) {
  • smallest = right;
  • }
  • if (smallest != idx) {
  • swapNodes(&pq->nodes[smallest], &pq->nodes[idx]);
  • heapify(pq, smallest);
  • }
  • }
  • Node* extractMin(PriorityQueue* pq) {
  • Node* temp = pq->nodes[0];
  • pq->nodes[0] = pq->nodes[pq->size - 1];
  • pq->size--;
  • heapify(pq, 0);
  • return temp;
  • }
  • void insertPriorityQueue(PriorityQueue* pq, Node* node) {
  • pq->size++;
  • int i = pq->size - 1;
  • while (i && node->freq < pq->nodes[(i - 1) / 2]->freq) {
  • pq->nodes[i] = pq->nodes[(i - 1) / 2];
  • i = (i - 1) / 2;
  • }
  • pq->nodes[i] = node;
  • }
  • int isLeaf(Node* node) {
  • return !(node->left) && !(node->right);
  • }
  • PriorityQueue* buildPriorityQueue(unsigned short data[], int freq[], int size) {
  • PriorityQueue* pq = createPriorityQueue(size);
  • for (int i = 0; i < size; ++i) {
  • if (freq[data[i]] > 0) {
  • Node* node = (Node*)malloc(sizeof(Node));
  • node->ch = data[i];
  • node->freq = freq[data[i]];
  • node->left = node->right = NULL;
  • pq->nodes[pq->size++] = node;
  • }
  • }
  • for (int i = (pq->size - 1) / 2; i >= 0; --i) {
  • heapify(pq, i);
  • }
  • return pq;
  • }
  • Node* buildHuffmanTree(unsigned short data[], int freq[], int size) {
  • Node* left, * right, * top;
  • PriorityQueue* pq = buildPriorityQueue(data, freq, size);
  • while (pq->size != 1) {
  • left = extractMin(pq);
  • right = extractMin(pq);
  • top = (Node*)malloc(sizeof(Node));
  • top->ch = 0;
  • top->freq = left->freq + right->freq;
  • top->left = left;
  • top->right = right;
  • insertPriorityQueue(pq, top);
  • }
  • return extractMin(pq);
  • }
  • void printCodes(Node* root, int arr[], int top, char** huffmanCodes) {
  • if (root->left) {
  • arr[top] = 0;
  • printCodes(root->left, arr, top + 1, huffmanCodes);
  • }
  • if (root->right) {
  • arr[top] = 1;
  • printCodes(root->right, arr, top + 1, huffmanCodes);
  • }
  • if (isLeaf(root)) {
  • huffmanCodes[root->ch] = (char*)malloc(top + 1);
  • for (int i = 0; i < top; ++i) {
  • huffmanCodes[root->ch][i] = '0' + arr[i];
  • }
  • huffmanCodes[root->ch][top] = '\0';
  • }
  • }
  • char** buildHuffmanCodes(unsigned short data[], int freq[], int size) {
  • Node* root = buildHuffmanTree(data, freq, size);
  • int arr[100], top = 0;
  • char** huffmanCodes = (char**)malloc(65536 * sizeof(char*)); // 65536 表示所有可能的 RGB565 值
  • for (int i = 0; i < 65536; ++i) {
  • huffmanCodes[i] = NULL;
  • }
  • printCodes(root, arr, top, huffmanCodes);
  • return huffmanCodes;
  • }
  • void compress(unsigned short data[], int dataSize, char** huffmanCodes, unsigned char** compressedData, int* compressedSize) {
  • int bitCount = 0;
  • for (int i = 0; i < dataSize; ++i) {
  • bitCount += strlen(huffmanCodes[data[i]]);
  • }
  • *compressedSize = (bitCount + 7) / 8;
  • *compressedData = (unsigned char*)malloc(*compressedSize);
  • memset(*compressedData, 0, *compressedSize);
  • int byteIndex = 0, bitIndex = 0;
  • for (int i = 0; i < dataSize; ++i) {
  • char* code = huffmanCodes[data[i]];
  • for (int j = 0; code[j] != '\0'; ++j) {
  • if (code[j] == '1') {
  • (*compressedData)[byteIndex] |= (1 << (7 - bitIndex));
  • }
  • bitIndex++;
  • if (bitIndex == 8) {
  • bitIndex = 0;
  • byteIndex++;
  • }
  • }
  • }
  • }
  • void decompress(unsigned char* compressedData, int compressedSize, Node* root, unsigned short** decompressedData, int dataSize) {
  • *decompressedData = (unsigned short*)malloc(dataSize * sizeof(unsigned short));
  • Node* current = root;
  • int byteIndex = 0, bitIndex = 0;
  • for (int i = 0; i < dataSize; ++i) {
  • while (!isLeaf(current)) {
  • if (compressedData[byteIndex] & (1 << (7 - bitIndex))) {
  • current = current->right;
  • }
  • else {
  • current = current->left;
  • }
  • bitIndex++;
  • if (bitIndex == 8) {
  • bitIndex = 0;
  • byteIndex++;
  • }
  • }
  • (*decompressedData)[i] = current->ch;
  • current = root;
  • }
  • }
  • int main() {
  • int dataSize = sizeof(gImage_rgb565_100x100) / 2;
  • unsigned short* data = (unsigned short*)gImage_rgb565_100x100;
  • // 计算频率
  • int freq[65536] = { 0 };
  • for (int i = 0; i < dataSize; ++i) {
  • freq[data[i]]++;
  • }
  • // 构建哈夫曼编码
  • char** huffmanCodes = buildHuffmanCodes(data, freq, 65536);
  • // 压缩
  • unsigned char* compressedData;
  • int compressedSize;
  • compress(data, dataSize, huffmanCodes, &compressedData, &compressedSize);
  • // 解压
  • unsigned short* decompressedData;
  • Node* root = buildHuffmanTree(data, freq, 65536);
  • decompress(compressedData, compressedSize, root, &decompressedData, dataSize);
  • // 打印压缩前后的大小和压缩率
  • printf("Original size: %d bytes\n", dataSize * 2);
  • printf("Compressed size: %d bytes\n", compressedSize);
  • printf("Compression ratio: %.2f%%\n", ((double)compressedSize / (dataSize * 2)) * 100);
  • // 验证解压结果(仅在开发时使用)
  • for (int i = 0; i < dataSize; ++i) {
  • if (data[i] != decompressedData[i]) {
  • printf("Error: Decompressed data does not match original data at index %d.\n", i);
  • break;
  • }
  • }
  • // 释放内存
  • free(compressedData);
  • free(decompressedData);
  • for (int i = 0; i < 65536; ++i) {
  • if (huffmanCodes[i]) {
  • free(huffmanCodes[i]);
  • }
  • }
  • free(huffmanCodes);
  • return 0;
  • }
城东书院 www.cdsy.xyz
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门
本栏推荐