书接上回(上一篇文章已经讲解了如何封装BMP格式图片保存到本地SD卡)。
在当今物联网(IoT)技术快速发展的背景下,嵌入式系统在各种应用场景中扮演着越来越重要的角色。随着物联网设备数量的不断增长,数据传输成为了关键的一环,尤其是在资源受限的环境中,如何高效地传输数据变得尤为重要。本文将介绍一个基于STM32F103ZET6微控制器的图像采集系统,该系统利用OV7725摄像头采集RGB565格式的图像,并通过MQTT协议将图像数据上传至阿里云物联网平台。 但是原始RGB565图像数据量巨大,直接传输会带来较高的网络负载,需要设计有效的压缩算法来减小数据体积。
考虑到单片机资源有限,无法使用第三方优秀的开源库,当前就基于Run-Length Encoding (RLE)无损压缩算法来减小图像数据的传输负担。RLE算法是一种简单有效的压缩方法,尤其适用于图像中存在大量连续重复像素值的情况。通过对重复像素的值和重复次数进行记录,可以显著减小图像数据的大小,从而降低网络传输的负载。下面会详细介绍RLE算法的设计与实现过程,以及如何在STM32F103ZET6微控制器上实现图像数据的压缩与解压,最终实现在阿里云物联网平台上进行高效的数据传输,并通过APP上位机进行图像数据的拉取与显示。
当前文章最重要的知识点是介绍: 数据的压缩和解压。
下面这个图是用的chargptAI自动生成的,不是实物图。
在单片机上进行图像数据压缩,需要选择是使用轻量级、简单但有效的压缩算法。
以下2个是可以用的压缩算法,适合在单片机上实现并能够有效减小数据量。
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;
- }
-
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;
- }
-
下面这段代码采用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;
- }
-
-
-
运行效果如下:
哈夫曼编码是一种常用的无损压缩算法,通过构建哈夫曼树,根据字符频率生成最优编码,从而实现数据压缩。
下面代码使用哈夫曼编码对一段自定义数据进行压缩和解压,并打印压缩前后的大小和压缩率,验证压缩效果。
- #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;
- }
-
测试效果:
- #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;
- }
-