C++处理.raw12文件分离R/G/G/B通道并保存PPM图像的问题
Let's break down the issues in your code and fix them step by step — I've worked with Bayer RAW formats before, so let's get this sorted:
Key Issues in Your Current Code
Incorrect RAW12 Data Parsing
RAW12 packs two 12-bit pixels into 3 bytes:- Pixel 1 (12 bits):
Byte0 (8 bits) + Byte1's upper 4 bits - Pixel 2 (12 bits):
Byte1's lower 4 bits + Byte2 (8 bits)
Your code was splitting these bytes incorrectly, which mangled the color channels right at the source.
- Pixel 1 (12 bits):
PPM Format Mishandling
- The P6 format requires 3 bytes per pixel (R, G, B) in binary form. Your code was only writing raw R channel data without padding the G/B channels (or initializing them), leading to garbage output.
- You also had a mismatch in buffer sizes and uninitialized memory, which caused undefined behavior.
12-bit to 8-bit Conversion Missing
RAW12 values range from 0-4095, but PPM 8-bit needs 0-255. You didn't scale these values, so bright pixels would clip incorrectly (or show up as dark since 4095 in 12 bits is 255 only if divided by 16).Loop Logic Errors
- You were looping to
rowNum < 3072but your image height is 2048 — that's reading extra garbage data beyond the file. - Using a fixed buffer size without handling partial reads at the end of the file led to incomplete pixel parsing.
- You were looping to
Corrected Full Code
#include <fstream> #include <iostream> #include <cstdint> using namespace std; const int IMG_WIDTH = 1536; const int IMG_HEIGHT = 2048; // Total RAW12 bytes: (1536 * 2048 * 12) / 8 = 1536*2048*3/2 = 4,718,592 bytes const int RAW_TOTAL_BYTES = IMG_WIDTH * IMG_HEIGHT * 3 / 2; // PPM total bytes: 1536*2048*3 = 9,437,184 bytes const int PPM_TOTAL_BYTES = IMG_WIDTH * IMG_HEIGHT * 3; // Helper to convert 12-bit value to 8-bit (scale 0-4095 to 0-255) uint8_t convert12to8(uint16_t val) { // Option 1: Fast shift (equivalent to val / 16, since 4095 >>4 = 255) return static_cast<uint8_t>(val >> 4); // Option 2: Exact linear scaling (val * 255 / 4095) — use if you need precise mapping // return static_cast<uint8_t>((static_cast<uint32_t>(val) * 255) / 4095); } int main() { ifstream infile("portrait.raw12", ios::binary); if (!infile) { cerr << "Failed to open input file!" << endl; return 1; } ofstream outfile_red("Red.ppm", ios::binary); ofstream outfile_green("Green.ppm", ios::binary); ofstream outfile_blue("Blue.ppm", ios::binary); if (!outfile_red || !outfile_green || !outfile_blue) { cerr << "Failed to open output files!" << endl; return 1; } // Write PPM headers for each channel auto write_ppm_header = [](ofstream& file) { file << "P6\n"; file << IMG_WIDTH << " " << IMG_HEIGHT << "\n"; file << "255\n"; }; write_ppm_header(outfile_red); write_ppm_header(outfile_green); write_ppm_header(outfile_blue); // Allocate buffers for PPM data (each channel's output) uint8_t* ppm_red = new uint8_t[PPM_TOTAL_BYTES](); // Initialize to 0 uint8_t* ppm_green = new uint8_t[PPM_TOTAL_BYTES](); uint8_t* ppm_blue = new uint8_t[PPM_TOTAL_BYTES](); uint8_t* raw_buffer = new uint8_t[RAW_TOTAL_BYTES]; // Read entire RAW file into buffer (simpler than partial reads for this size) infile.read(reinterpret_cast<char*>(raw_buffer), RAW_TOTAL_BYTES); if (!infile) { cerr << "Failed to read complete RAW file!" << endl; return 1; } int raw_idx = 0; for (int y = 0; y < IMG_HEIGHT; ++y) { for (int x = 0; x < IMG_WIDTH; x += 2) { // Process 2 pixels per 3 bytes uint16_t pixel1, pixel2; // Parse RAW12 pair pixel1 = (static_cast<uint16_t>(raw_buffer[raw_idx]) << 4) | (raw_buffer[raw_idx+1] >> 4); pixel2 = ((static_cast<uint16_t>(raw_buffer[raw_idx+1]) & 0x0F) << 8) | raw_buffer[raw_idx+2]; raw_idx += 3; // Map to RGGB Bayer pattern if (y % 2 == 0) { // Even row: R G R G ... int ppm_pos = (y * IMG_WIDTH + x) * 3; ppm_red[ppm_pos] = convert12to8(pixel1); // R at (x,y) ppm_green[ppm_pos + 3] = convert12to8(pixel2); // G at (x+1,y) // Fill other channels with 0 for single-channel PPM ppm_red[ppm_pos + 1] = 0; ppm_red[ppm_pos + 2] = 0; ppm_red[ppm_pos + 3 + 1] = 0; ppm_red[ppm_pos + 3 + 2] = 0; ppm_green[ppm_pos] = 0; ppm_green[ppm_pos + 2] = 0; ppm_green[ppm_pos + 3 + 1] = 0; ppm_green[ppm_pos + 3 + 2] = 0; ppm_blue[ppm_pos] = 0; ppm_blue[ppm_pos + 1] = 0; ppm_blue[ppm_pos + 3] = 0; ppm_blue[ppm_pos + 3 + 1] = 0; ppm_blue[ppm_pos + 3 + 2] = 0; } else { // Odd row: G B G B ... int ppm_pos = (y * IMG_WIDTH + x) * 3; ppm_green[ppm_pos] = convert12to8(pixel1); // G at (x,y) ppm_blue[ppm_pos + 3] = convert12to8(pixel2); // B at (x+1,y) ppm_red[ppm_pos] = 0; ppm_red[ppm_pos + 1] = 0; ppm_red[ppm_pos + 2] = 0; ppm_red[ppm_pos + 3] = 0; ppm_red[ppm_pos + 3 + 1] = 0; ppm_red[ppm_pos + 3 + 2] = 0; ppm_green[ppm_pos + 1] = 0; ppm_green[ppm_pos + 2] = 0; ppm_green[ppm_pos + 3 + 1] = 0; ppm_green[ppm_pos + 3 + 2] = 0; ppm_blue[ppm_pos] = 0; ppm_blue[ppm_pos + 1] = 0; ppm_blue[ppm_pos + 2] = 0; ppm_blue[ppm_pos + 3 + 1] = 0; ppm_blue[ppm_pos + 3 + 2] = 0; } } } // Write all PPM data outfile_red.write(reinterpret_cast<char*>(ppm_red), PPM_TOTAL_BYTES); outfile_green.write(reinterpret_cast<char*>(ppm_green), PPM_TOTAL_BYTES); outfile_blue.write(reinterpret_cast<char*>(ppm_blue), PPM_TOTAL_BYTES); // Cleanup delete[] raw_buffer; delete[] ppm_red; delete[] ppm_green; delete[] ppm_blue; infile.close(); outfile_red.close(); outfile_green.close(); outfile_blue.close(); cout << "Conversion completed successfully!" << endl; return 0; }
What Changed & Why
Correct RAW12 Parsing
We now properly unpack the 3-byte pairs into 12-bit pixels, following the RAW12 spec: first pixel uses byte0 + upper 4 bits of byte1, second uses lower 4 bits of byte1 + byte2.Proper 12-bit to 8-bit Scaling
Theconvert12to8function handles scaling from 0-4095 to 0-255. The fast shift (>>4) is equivalent to dividing by 16 and works for most cases; use the linear scaling line if you need precise brightness mapping.Valid PPM Output
Each single-channel PPM has the correct 3-byte per pixel format (the active channel gets the scaled value, others are 0). We also write the header correctly with newline separators (critical for PPM parsers).Simplified File Reading
Reading the entire RAW file into a single buffer avoids partial read bugs and makes parsing easier, especially for a fixed-size image.Memory Safety
All buffers are initialized to 0 to avoid garbage data, and we properly deallocate memory at the end.
Quick Testing Tip
To verify the parsing, add a small debug section to print the first few 12-bit values and their 8-bit conversions — this will confirm the RAW data is being read correctly before writing the PPM files.
内容的提问来源于stack exchange,提问作者Tukai

