C#中使用XOR修改Bitmap像素颜色后读取值异常求助
你的问题主要由三个核心原因导致,咱们逐个拆解:
1. MPEG4编码的有损压缩是主因
你使用的VideoCodec.MPEG4是有损压缩编码,编码器在写入视频时会为了减小文件体积,对像素数据进行不可逆的压缩优化(比如丢弃人眼不敏感的细节)。这就导致你修改后的精确像素值(比如250)在写入视频后,会被编码器“调整”,重新读取时自然得不到原始修改的值。
2. Bitmap像素格式不匹配引发的隐式转换
你代码里创建了PixelFormat.Format24bppRgb的Bitmap,但reader.ReadVideoFrame()返回的帧格式未必是这个(比如可能是32bppArgb)。直接image = videoFrame赋值后,后续用GetPixel/SetPixel操作时,会发生隐式的像素格式转换,这可能悄悄改变你的像素值。另外,24bppRgb格式本身没有Alpha通道,你代码里存储的A[j,k]其实是无效的(24bpp的Color.A默认是255,但实际Bitmap里并没有存储这个通道)。
3. GetPixel/SetPixel的低效与潜在问题
这两个方法是逐像素调用的,不仅效率极低,而且在处理不同格式的Bitmap时,会触发.NET的颜色管理逻辑,可能导致像素值被意外转换(比如色域映射),进一步加剧数值偏差。
解决方案
方案1:使用无损视频编码(优先推荐,保证像素精确性)
如果需要精确保留修改后的像素值,必须使用无损编码格式。比如改用VideoCodec.RawVideo(完全无压缩,文件体积会很大),或者配置支持无损模式的编码(比如AVC的无损配置,需要额外参数)。
修改写入代码:
writer.Open(@"E:\Video\Result.MP4", reader.Width, reader.Height, reader.FrameRate, VideoCodec.RawVideo);
方案2:统一Bitmap像素格式,避免隐式转换
在读取视频帧后,将其转换为你需要的Format24bppRgb格式,确保后续操作的一致性:
Bitmap videoFrame = reader.ReadVideoFrame(); // 转换为24bppRgb格式的Bitmap Bitmap image = new Bitmap(videoFrame, reader.Width, reader.Height, PixelFormat.Format24bppRgb); videoFrame.Dispose(); // 及时释放原帧资源
方案3:用LockBits直接操作内存(替代GetPixel/SetPixel)
这是处理Bitmap像素的高效且可靠的方式,直接操作内存中的字节数组,避免隐式转换问题。注意:24bppRgb格式在内存中的存储顺序是BGR,不是RGB,这是很多人踩坑的点!
修改你的Myfunc方法核心逻辑:
public static void Myfunc(VideoFileReader reader, VideoFileWriter writer) { byte[] mykey = { 0xff, 0x55, 0x00 }; for (int i = 0; i < reader.FrameCount; i++) { using (Bitmap videoFrame = reader.ReadVideoFrame()) { // 转换为24bppRgb格式 using (Bitmap image = new Bitmap(videoFrame, reader.Width, reader.Height, PixelFormat.Format24bppRgb)) { // 锁定Bitmap内存,直接操作字节 BitmapData bmpData = image.LockBits( new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadWrite, image.PixelFormat); IntPtr ptr = bmpData.Scan0; int totalBytes = bmpData.Stride * image.Height; byte[] pixelBytes = new byte[totalBytes]; // 从内存复制像素数据到数组 System.Runtime.InteropServices.Marshal.Copy(ptr, pixelBytes, 0, totalBytes); // 遍历每个像素,执行XOR操作(注意BGR顺序) for (int y = 0; y < image.Height; y++) { for (int x = 0; x < image.Width; x++) { int byteIndex = y * bmpData.Stride + x * 3; pixelBytes[byteIndex] ^= mykey[0]; // B通道 pixelBytes[byteIndex + 1] ^= mykey[0]; // G通道 pixelBytes[byteIndex + 2] ^= mykey[0]; // R通道 } } // 把修改后的数据复制回Bitmap内存 System.Runtime.InteropServices.Marshal.Copy(pixelBytes, 0, ptr, totalBytes); image.UnlockBits(bmpData); // 写入处理后的帧 writer.WriteVideoFrame(image); } } } }
验证方式
修改后,你可以先不写入视频,而是直接在内存中检查修改后的像素值(比如用GetPixel查看image的像素),确认XOR操作生效后,再写入无损编码的视频,此时重新读取就能得到预期的像素值了。
内容的提问来源于stack exchange,提问作者Edward

