[Video and Audio Data Processing] 分离YUV444P像素数据中的Y、U、V分量

本文详细介绍了如何使用C语言实现YUV444P格式图像的Y、U、V分量分离,提供了完整可运行的源代码,并指导解决编译问题,展示了分离后的各分量图像效果。

0. 前提

可参考我的以下博客搭建开发环境:

  1. https://blog.youkuaiyun.com/Codeliang666/article/details/106161156
  2. https://blog.youkuaiyun.com/Codeliang666/article/details/106355704

新建项目,把lena图放项目里面,lena图从以下地址获取
https://github.com/leixiaohua1020/simplest_mediadata_test
在这里插入图片描述

1. 可以跑通的源代码如下

extern "C"
{
#ifdef __cplusplus
#define __STDC_CONSTANT_MACROS

#endif

}
extern "C" {

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
}


/**
 * Split Y, U, V planes in YUV444P file.
 * @param url  Location of Input YUV file.
 * @param w    Width of Input YUV file.
 * @param h    Height of Input YUV file.
 * @param num  Number of frames to process.
 *
 */
int simplest_yuv444_split(const char* url, int w, int h, int num) {
	FILE* fp = fopen(url, "rb+");
	FILE* fp1 = fopen("output_444_y.y", "wb+");
	FILE* fp2 = fopen("output_444_u.y", "wb+");
	FILE* fp3 = fopen("output_444_v.y", "wb+");
	unsigned char* pic = (unsigned char*)malloc(w * h * 3);

	for (int i = 0; i < num; i++) {
		fread(pic, 1, w * h * 3, fp);

		//Y
		fwrite(pic, 1, w * h, fp1);
		//U
		fwrite(pic + w * h, 1, w * h, fp2);
		//V
		fwrite(pic + w * h * 2, 1, w * h, fp3);
	}

	free(pic);
	fclose(fp);
	fclose(fp1);
	fclose(fp2);
	fclose(fp3);

	return 0;
}


int main()
{
	simplest_yuv444_split("lena_256x256_yuv444p.yuv", 256, 256, 1);
    return 0;
}

注意要是遇到编译问题,记得修改:

项目 ->属性-> c/c++、预处理器 -> 预处理器定义 ->添加 _CRT_SECURE_NO_WARNINGS
在这里插入图片描述

2. 效果如下:

原始图像如下:先把像素格式切换到 YUV444:

在这里插入图片描述
在这里插入图片描述
把像素格式 切换到Y来查看,生成的Y、U、V分量图片即可:

在这里插入图片描述

Y分量效果如下:
在这里插入图片描述
U分量效果如下:

在这里插入图片描述

V分量效果如下:

在这里插入图片描述

参考链接:

  1. https://blog.youkuaiyun.com/leixiaohua1020/article/details/50534150
import os import sys import cv2 import numpy as np import subprocess import tempfile import shutil import random import wave import struct from PIL import Image, ImageDraw, ImageFont # 尝试导入PyQt5,如果失败则提供友好的错误信息 try: from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog, QMessageBox, QProgressBar, QGroupBox, QTextEdit, QCheckBox, QListWidget, QListWidgetItem, QComboBox) from PyQt5.QtCore import Qt, QThread, pyqtSignal from PyQt5.QtGui import QFont, QPalette, QColor, QIcon except ImportError: print("错误: 需要安装PyQt5库") print("请运行: pip install PyQt5") sys.exit(1) # 尝试导入GPU相关库 try: import pyopencl as cl OPENCL_AVAILABLE = True except ImportError: OPENCL_AVAILABLE = False try: import cupy as cp CUDA_AVAILABLE = True except ImportError: CUDA_AVAILABLE = False # 尝试导入scipy try: import scipy.io.wavfile as wavfile SCIPY_AVAILABLE = True except ImportError: SCIPY_AVAILABLE = False class AudioSteganography: """音频隐写处理类""" @staticmethod def embed_message(audio_path, message, output_path): """使用改进的LSB算法在音频中嵌入消息""" try: # 读取音频文件 with wave.open(audio_path, 'rb') as audio: params = audio.getparams() frames = audio.readframes(audio.getnframes()) # 将音频数据转换为字节数组 audio_data = bytearray(frames) # 将消息转换为二进制 binary_message = ''.join(format(ord(char), '08b') for char in message) binary_message += '00000000' # 添加终止符 # 检查音频容量是否足够 if len(binary_message) > len(audio_data) * 8: raise ValueError("音频文件太小,无法嵌入消息") # 使用改进的LSB算法嵌入消息(每4个样本嵌入1位) sample_interval = 4 # 每4个样本嵌入1位 message_index = 0 for i in range(0, len(audio_data), sample_interval): if message_index >= len(binary_message): break # 修改每个样本的最低有效位 audio_data[i] = (audio_data[i] & 0xFE) | int(binary_message[message_index]) message_index += 1 # 保存带有隐写信息的音频 with wave.open(output_path, 'wb') as output_audio: output_audio.setparams(params) output_audio.writeframes(bytes(audio_data)) return True, "音频隐写成功" except Exception as e: return False, f"音频隐写失败: {str(e)}" @staticmethod def extract_message(audio_path): """从音频中提取隐藏的消息""" try: # 读取音频文件 with wave.open(audio_path, 'rb') as audio: frames = audio.readframes(audio.getnframes()) # 将音频数据转换为字节数组 audio_data = bytearray(frames) # 提取LSB位 binary_message = '' sample_interval = 4 # 与嵌入时保持一致 for i in range(0, len(audio_data), sample_interval): binary_message += str(audio_data[i] & 1) # 将二进制转换为字符 message = '' for i in range(0, len(binary_message), 8): byte = binary_message[i:i+8] if len(byte) < 8: break char = chr(int(byte, 2)) if char == '\0': # 遇到终止符停止 break message += char return True, message except Exception as e: return False, f"消息提取失败: {str(e)}" class VideoProcessor(QThread): progress_updated = pyqtSignal(int) status_updated = pyqtSignal(str) finished = pyqtSignal(bool, str) batch_progress = pyqtSignal(int, int) # 当前处理, 总计 def __init__(self, video_a_path, video_b_paths, output_dir, use_gpu=False, gpu_type="auto"): super().__init__() self.video_a_path = video_a_path self.video_b_paths = video_b_paths self.output_dir = output_dir self.use_gpu = use_gpu self.gpu_type = gpu_type self.temp_dir = tempfile.mkdtemp() self.gpu_context = None self.gpu_queue = None self.gpu_device = None # 初始化GPU环境 if self.use_gpu: self.init_gpu() def init_gpu(self): """初始化GPU环境""" try: if self.gpu_type == "cuda" or (self.gpu_type == "auto" and CUDA_AVAILABLE): # 使用CUDA self.status_updated.emit("初始化CUDA环境...") # 检查可用GPU devices = cp.cuda.runtime.getDeviceCount() if devices > 0: self.status_updated.emit(f"找到 {devices} 个NVIDIA GPU") # 使用第一个可用的GPU cp.cuda.Device(0).use() self.gpu_type = "cuda" return True else: self.status_updated.emit("未找到NVIDIA GPU,尝试使用OpenCL") self.gpu_type = "opencl" if self.gpu_type == "opencl" or (self.gpu_type == "auto" and OPENCL_AVAILABLE): # 使用OpenCL self.status_updated.emit("初始化OpenCL环境...") platforms = cl.get_platforms() # 优先寻找Intel Arc显卡 intel_arc_found = False for platform in platforms: devices = platform.get_devices(device_type=cl.device_type.GPU) for device in devices: device_name = device.name if "Intel" in device_name and ("Arc" in device_name or "A770" in device_name): self.status_updated.emit(f"找到Intel Arc显卡: {device_name}") self.gpu_context = cl.Context([device]) self.gpu_queue = cl.CommandQueue(self.gpu_context) self.gpu_device = device self.gpu_type = "opencl" intel_arc_found = True break if intel_arc_found: break # 如果没有找到Intel Arc,寻找其他GPU if not intel_arc_found: for platform in platforms: devices = platform.get_devices(device_type=cl.device_type.GPU) if devices: self.gpu_context = cl.Context(devices) self.gpu_queue = cl.CommandQueue(self.gpu_context) self.gpu_device = devices[0] self.status_updated.emit(f"找到OpenCL GPU: {devices[0].name}") self.gpu_type = "opencl" return True # 如果没有找到GPU,尝试使用CPU if not intel_arc_found: for platform in platforms: devices = platform.get_devices(device_type=cl.device_type.CPU) if devices: self.gpu_context = cl.Context(devices) self.gpu_queue = cl.CommandQueue(self.gpu_context) self.gpu_device = devices[0] self.status_updated.emit(f"使用OpenCL CPU: {devices[0].name}") self.gpu_type = "opencl" return True self.status_updated.emit("未找到OpenCL设备,将使用CPU") self.use_gpu = False return False else: self.status_updated.emit("未安装GPU支持库,将使用CPU") self.use_gpu = False return False except Exception as e: self.status_updated.emit(f"GPU初始化失败: {str(e)},将使用CPU") self.use_gpu = False return False def run(self): try: # 创建OK文件夹 ok_dir = os.path.join(self.output_dir, "OK") os.makedirs(ok_dir, exist_ok=True) total_videos = len(self.video_b_paths) for idx, video_b_path in enumerate(self.video_b_paths): output_filename = f"output_{os.path.basename(video_b_path).split('.')[0]}.mp4" output_path = os.path.join(ok_dir, output_filename) self.batch_progress.emit(idx + 1, total_videos) self.status_updated.emit(f"处理视频 {idx + 1}/{total_videos}: {os.path.basename(video_b_path)}") # 处理单个视频对 success, message = self.process_single_video(self.video_a_path, video_b_path, output_path) if not success: self.finished.emit(False, f"处理失败: {message}") return self.finished.emit(True, f"批量处理完成!共处理 {total_videos} 个视频,输出保存在 {ok_dir}") except Exception as e: import traceback error_details = traceback.format_exc() self.finished.emit(False, f"处理过程中出现错误: {str(e)}\n详细信息:\n{error_details}") finally: # 清理临时文件 if os.path.exists(self.temp_dir): try: shutil.rmtree(self.temp_dir) except: pass def process_single_video(self, video_a_path, video_b_path, output_path): """处理单个视频对""" try: self.status_updated.emit("开始处理视频...") # 提取音频 self.status_updated.emit("提取音频...") audio_path = self.extract_audio(video_a_path) self.progress_updated.emit(10) # 处理视频A self.status_updated.emit("处理视频A...") a_frames_dir = self.process_video_a(video_a_path) self.progress_updated.emit(30) # 处理视频B self.status_updated.emit("处理视频B...") b_frames_dir = self.process_video_b(video_b_path, len(os.listdir(a_frames_dir))) self.progress_updated.emit(50) # 嵌入隐写 self.status_updated.emit("嵌入隐写信息...") stego_frames_dir = self.embed_steganography(a_frames_dir, b_frames_dir) self.progress_updated.emit(70) # 处理音频并合成最终视频 self.status_updated.emit("处理音频并合成最终视频...") self.process_audio_and_assemble(stego_frames_dir, audio_path, output_path) self.progress_updated.emit(90) # 添加随机元数据 self.status_updated.emit("添加元数据...") self.add_random_metadata(output_path) self.progress_updated.emit(100) return True, "处理完成" except Exception as e: import traceback error_details = traceback.format_exc() return False, f"处理过程中出现错误: {str(e)}\n详细信息:\n{error_details}" def extract_audio(self, video_path): """提取音频并转换为单声道""" audio_path = os.path.join(self.temp_dir, "audio.wav") # 使用ffmpeg提取音频并转换为单声道 cmd = [ 'ffmpeg', '-i', video_path, '-vn', '-ac', '1', '-ar', '44100', '-y', audio_path ] try: subprocess.run(cmd, check=True, capture_output=True) return audio_path except subprocess.CalledProcessError as e: # 如果提取失败,创建一个默认的音频文件 self.status_updated.emit("警告: 无法提取音频,将创建默认音频") try: # 创建一个1秒的静音音频 sample_rate = 44100 duration = 10 # 默认10秒 # 获取视频时长 probe_cmd = ['ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', video_path] result = subprocess.run(probe_cmd, capture_output=True, text=True) if result.returncode == 0: duration = float(result.stdout.strip()) # 生成静音 t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False) silence = np.zeros_like(t) # 保存为WAV文件 if SCIPY_AVAILABLE: wavfile.write(audio_path, sample_rate, silence.astype(np.float32)) else: # 使用wave模块创建WAV文件 with wave.open(audio_path, 'wb') as wav_file: wav_file.setnchannels(1) wav_file.setsampwidth(2) wav_file.setframerate(sample_rate) # 将静音数据转换为字节 max_amplitude = 32767 # 16位有符号整数的最大值 for sample in silence: data = struct.pack('<h', int(sample * max_amplitude)) wav_file.writeframesraw(data) return audio_path except Exception as e2: self.status_updated.emit(f"创建默认音频失败: {str(e2)}") # 最后的手段,创建一个空的音频文件 open(audio_path, 'a').close() return audio_path def process_video_a(self, video_path): """处理视频A""" # 创建输出目录 output_dir = os.path.join(self.temp_dir, "video_a_frames") os.makedirs(output_dir, exist_ok=True) # 获取视频信息 cap = cv2.VideoCapture(video_path) total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) fps = cap.get(cv2.CAP_PROP_FPS) width, height = 1080, 2336 # 生成不可见黑色扰动背景 for i in range(total_frames): # 创建带有轻微扰动的黑色背景 background = np.random.randint(0, 3, (height, width, 3), dtype=np.uint8) # 读取原始帧 ret, frame = cap.read() if not ret: break # 调整原始帧大小并居中放置 h, w = frame.shape[:2] scale = min((width-10)/w, (height-10)/h) new_w, new_h = int(w * scale), int(h * scale) resized_frame = cv2.resize(frame, (new_w, new_h)) # 将调整后的帧放置在背景上,设置不透明度为98% x_offset = (width - new_w) // 2 y_offset = (height - new_h) // 2 # 创建叠加层 overlay = background.copy() roi = overlay[y_offset:y_offset+new_h, x_offset:x_offset+new_w] # 使用加权叠加实现98%不透明度 cv2.addWeighted(resized_frame, 0.98, roi, 0.02, 0, roi) # 保存帧 cv2.imwrite(os.path.join(output_dir, f"frame_{i:06d}.png"), overlay) # 更新进度 if i % 10 == 0: self.status_updated.emit(f"处理视频A帧: {i}/{total_frames}") cap.release() return output_dir def process_video_b(self, video_path, total_frames_needed): """处理视频B""" # 创建输出目录 output_dir = os.path.join(self.temp_dir, "video_b_frames") os.makedirs(output_dir, exist_ok=True) # 获取视频B的信息 cap_b = cv2.VideoCapture(video_path) total_frames_b = int(cap_b.get(cv2.CAP_PROP_FRAME_COUNT)) fps_b = cap_b.get(cv2.CAP_PROP_FPS) width, height = 1080, 2336 # 计算需要从视频B中提取的帧 start_frame = 0 if total_frames_b > total_frames_needed: start_frame = random.randint(0, total_frames_b - total_frames_needed) # 生成不可见黑色扰动背景并处理视频B for i in range(total_frames_needed): # 创建带有轻微扰动的黑色背景 background = np.random.randint(0, 3, (height, width, 3), dtype=np.uint8) # 读取原始帧(从适当的位置) frame_idx = min(start_frame + i, total_frames_b - 1) cap_b.set(cv2.CAP_PROP_POS_FRAMES, frame_idx) ret, frame = cap_b.read() if not ret: break # 调整原始帧大小并居中放置 h, w = frame.shape[:2] scale = min((width-10)/w, (height-10)/h) new_w, new_h = int(w * scale), int(h * scale) resized_frame = cv2.resize(frame, (new_w, new_h)) # 将调整后的帧放置在背景上 x_offset = (width - new_w) // 2 y_offset = (height - new_h) // 2 # 创建叠加层 overlay = background.copy() roi = overlay[y_offset:y_offset+new_h, x_offset:x_offset+new_w] # 叠加帧 cv2.addWeighted(resized_frame, 1.0, roi, 0.0, 0, roi) # 保存帧 cv2.imwrite(os.path.join(output_dir, f"frame_{i:06d}.png"), overlay) # 更新进度 if i % 10 == 0: self.status_updated.emit(f"处理视频B帧: {i}/{total_frames_needed}") cap_b.release() return output_dir def dct_embed_gpu(self, carrier, secret): """使用GPU加速的DCT隐写""" if self.gpu_type == "cuda" and CUDA_AVAILABLE and self.use_gpu: # 使用CuPy进行GPU加速 carrier_gpu = cp.asarray(carrier) secret_gpu = cp.asarray(secret) # 转换为YUV颜色空间 carrier_yuv = cp.zeros_like(carrier_gpu) secret_yuv = cp.zeros_like(secret_gpu) # RGB到YUV转换矩阵 transform = cp.array([[0.299, 0.587, 0.114], [-0.14713, -0.28886, 0.436], [0.615, -0.51499, -0.10001]]) # 应用转换矩阵 for i in range(carrier_gpu.shape[0]): for j in range(carrier_gpu.shape[1]): carrier_yuv[i, j] = cp.dot(transform, carrier_gpu[i, j]) secret_yuv[i, j] = cp.dot(transform, secret_gpu[i, j]) # 只使用Y通道进行DCT变换 carrier_y = carrier_yuv[:,:,0].astype(cp.float32) secret_y = secret_yuv[:,:,0].astype(cp.float32) # 对载体和秘密图像进行DCT变换 carrier_dct = cp.fft.dct(cp.fft.dct(carrier_y, axis=0), axis=1) secret_dct = cp.fft.dct(cp.fft.dct(secret_y, axis=0), axis=1) # 嵌入强度因子 alpha = 0.03 # 在DCT域中嵌入秘密图像 stego_dct = carrier_dct + alpha * secret_dct # 进行逆DCT变换 stego_y = cp.fft.idct(cp.fft.idct(stego_dct, axis=1), axis=0) # 将结果放回YUV图像中 stego_yuv = carrier_yuv.copy() stego_yuv[:,:,0] = stego_y # YUV到RGB转换矩阵 inv_transform = cp.linalg.inv(transform) # 转换回RGB颜色空间 stego_bgr = cp.zeros_like(stego_yuv) for i in range(stego_yuv.shape[0]): for j in range(stego_yuv.shape[1]): stego_bgr[i, j] = cp.dot(inv_transform, stego_yuv[i, j]) return cp.clip(stego_bgr, 0, 255).astype(cp.uint8).get() elif self.gpu_type == "opencl" and OPENCL_AVAILABLE and self.use_gpu and self.gpu_context: # 使用OpenCL进行GPU加速,特别优化Intel Arc显卡 return self.dct_embed_opencl(carrier, secret) else: # 使用CPU版本 return self.dct_embed_cpu(carrier, secret) def dct_embed_opencl(self, carrier, secret): """使用OpenCL进行DCT隐写,特别优化Intel Arc显卡""" try: # 将图像转换为YUV颜色空间 carrier_yuv = cv2.cvtColor(carrier, cv2.COLOR_BGR2YUV) secret_yuv = cv2.cvtColor(secret, cv2.COLOR_BGR2YUV) # 只使用Y通道进行DCT变换 carrier_y = carrier_yuv[:,:,0].astype(np.float32) secret_y = secret_yuv[:,:,0].ast(np.float32) # 创建OpenCL缓冲区 carrier_buffer = cl.Buffer(self.gpu_context, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=carrier_y) secret_buffer = cl.Buffer(self.gpu_context, cl.mem_flags.READ_ONLY | cl.mem_flags.COPY_HOST_PTR, hostbuf=secret_y) # 创建输出缓冲区 stego_dct_buffer = cl.Buffer(self.gpu_context, cl.mem_flags.WRITE_ONLY, carrier_y.nbytes) stego_y_buffer = cl.Buffer(self.gpu_context, cl.mem_flags.WRITE_ONLY, carrier_y.nbytes) # 编译OpenCL程序 - 修复了内核代码中的语法错误 dct_program = cl.Program(self.gpu_context, """ __kernel void dct_embed(__global float* carrier, __global float* secret, __global float* stego_dct, __global float* stego_y, float alpha, int width, int height) { int x = get_global_id(0); int y = get_global_id(1); int idx = y * width + x; // DCT变换 (简化实现) // 在实际应用中,这里应该实现完整的2D DCT算法 // 这里使用简化的DCT近似 float dct_carrier = carrier[idx] * cos((2*x+1)*y*M_PI/(2*width)); float dct_secret = secret[idx] * cos((2*x+1)*y*M_PI/(2*width)); // 嵌入秘密图像 stego_dct[idx] = dct_carrier + alpha * dct_secret; // 逆DCT变换 stego_y[idx] = stego_dct[idx] * cos((2*x+1)*y*M_PI/(2*width)); } """).build() # 设置内核参数 width, height = carrier_y.shape[1], carrier_y.shape[0] alpha = np.float32(0.03) # 执行内核 dct_program.dct_embed(self.gpu_queue, carrier_y.shape, None, carrier_buffer, secret_buffer, stego_dct_buffer, stego_y_buffer, alpha, np.int32(width), np.int32(height)) # 读取结果 stego_y = np.empty_like(carrier_y) cl.enqueue_copy(self.gpu_queue, stego_y, stego_y_buffer) # 将结果放回YUV图像中 stego_yuv = carrier_yuv.copy() stego_yuv[:,:,0] = stego_y # 转换回BGR颜色空间 stego_bgr = cv2.cvtColor(stego_yuv, cv2.COLOR_YUV2BGR) return np.clip(stego_bgr, 0, 255).astype(np.uint8) except Exception as e: self.status_updated.emit(f"OpenCL处理失败: {str(e)},将使用CPU") return self.dct_embed_cpu(carrier, secret) def dct_embed_cpu(self, carrier, secret): """在DCT域中嵌入秘密图像(CPU版本)""" # 将图像转换为YUV颜色空间 carrier_yuv = cv2.cvtColor(carrier, cv2.COLOR_BGR2YUV) secret_yuv = cv2.cvtColor(secret, cv2.COLOR_BGR2YUV) # 只使用Y通道进行DCT变换 carrier_y = carrier_yuv[:,:,0].astype(np.float32) secret_y = secret_yuv[:,:,0].astype(np.float32) # 对载体和秘密图像进行DCT变换 carrier_dct = cv2.dct(carrier_y) secret_dct = cv2.dct(secret_y) # 嵌入强度因子 alpha = 0.03 # 在DCT域中嵌入秘密图像 stego_dct = carrier_dct + alpha * secret_dct # 进行逆DCT变换 stego_y = cv2.idct(stego_dct) # 将结果放回YUV图像中 stego_yuv = carrier_yuv.copy() stego_yuv[:,:,0] = stego_y # 转换回BGR颜色空间 stego_bgr = cv2.cvtColor(stego_yuv, cv2.COLOR_YUV2BGR) return np.clip(stego_bgr, 0, 255).ast(np.uint8) def embed_steganography(self, a_frames_dir, b_frames_dir): """嵌入隐写信息""" # 创建输出目录 output_dir = os.path.join(self.temp_dir, "stego_frames") os.makedirs(output_dir, exist_ok=True) # 获取帧列表 a_frames = sorted([f for f in os.listdir(a_frames_dir) if f.endswith('.png')]) total_frames = len(a_frames) for i, frame_name in enumerate(a_frames): # 读取A视频帧 carrier_frame = cv2.imread(os.path.join(a_frames_dir, frame_name)) # 读取B视频帧 secret_frame = cv2.imread(os.path.join(b_frames_dir, frame_name)) # 调整秘密图像大小以匹配载体图像 secret_frame = cv2.resize(secret_frame, (carrier_frame.shape[1], carrier_frame.shape[0])) # 在DCT域中嵌入秘密图像 if self.use_gpu: stego_frame = self.dct_embed_gpu(carrier_frame, secret_frame) else: stego_frame = self.dct_embed_cpu(carrier_frame, secret_frame) # 添加数字水印(版权保护)- 透明度极低,人眼几乎不可见 stego_frame = self.add_watermark(stego_frame) # 保存处理后的帧 cv2.imwrite(os.path.join(output_dir, frame_name), stego_frame) # 更新进度 if i % 10 == 0: self.status_updated.emit(f"嵌入隐写帧: {i}/{total_frames}") return output_dir def add_watermark(self, image): """添加数字水印 - 透明度极低,人眼几乎不可见""" # 将OpenCV图像转换为PIL图像 pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) # 创建一个绘图对象 draw = ImageDraw.Draw(pil_image, 'RGBA') # 使用默认字体 try: font = ImageFont.load_default() # 尝试加载系统字体 try: font = ImageFont.truetype("arial.ttf", 20) except: pass except: pass # 添加水印文本 - 使用极低的透明度 (约0.5%) watermark_text = "Copyright Protected" # 获取文本尺寸 try: # 对于较新版本的Pillow bbox = draw.textbbox((0, 0), watermark_text, font=font) text_width = bbox[2] - bbox[0] text_height = bbox[3] - bbox[1] except: # 对于较旧版本的Pillow try: text_width, text_height = draw.textsize(watermark_text, font=font) except: # 如果所有方法都失败,使用估计值 text_width, text_height = 150, 20 # 在多个位置添加水印 positions = [ (10, 10), # 左上角 (image.shape[1] - text_width - 10, 10), # 右上角 (10, image.shape[0] - text_height - 10), # 左下角 (image.shape[1] - text_width - 10, image.shape[0] - text_height - 10) # 右下角 ] for position in positions: # 添加文本 - 使用极低的透明度 (约0.5%) draw.text(position, watermark_text, (255, 255, 255, 2), font=font) # 透明度从5降低到2 # 转换回OpenCV图像 return cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR) def process_audio_and_assemble(self, frames_dir, audio_path, output_path): """处理音频并合成最终视频""" # 生成随机噪声音频 noise_audio_path = os.path.join(self.temp_dir, "noise_audio.wav") # 获取音频信息 try: cmd = ['ffprobe', '-i', audio_path, '-show_entries', 'format=duration', '-v', 'quiet', '-of', 'csv=p=0'] result = subprocess.run(cmd, capture_output=True, text=True) duration = float(result.stdout.strip()) # 生成随机噪声(极低音量) sample_rate = 44100 t = np.linspace(0, duration, int(sample_rate * duration), endpoint=False) noise = 0.0005 * np.random.randn(len(t)) # 极低音量噪声 # 保存噪声音频 if SCIPY_AVAILABLE: wavfile.write(noise_audio_path, sample_rate, noise.astype(np.float32)) else: # 使用wave模块创建WAV文件 with wave.open(noise_audio_path, 'wb') as wav_file: wav_file.setnchannels(1) wav_file.setsampwidth(2) wav_file.setframerate(sample_rate) # 将噪声数据转换为字节 max_amplitude = 32767 # 16位有签名整数的最大值 for sample in noise: data = struct.pack('<h', int(sample * max_amplitude)) wav_file.writeframesraw(data) except: # 如果生成噪声失败,创建一个空的音频文件 open(noise_audio_path, 'a').close() # 在音频中嵌入隐写信息 stego_audio_path = os.path.join(self.temp_dir, "stego_audio.wav") message = "HiddenSteganoMessage2023" success, msg = AudioSteganography.embed_message(audio_path, message, stego_audio_path) if not success: self.status_updated.emit(f"音频隐写警告: {msg}") stego_audio_path = audio_path # 使用原始音频 # 合并音频(左声道为隐写音频,右声道为噪声) mixed_audio_path = os.path.join(self.temp_dir, "mixed_audio.wav") cmd = [ 'ffmpeg', '-y', '-i', stego_audio_path, '-i', noise_audio_path, '-filter_complex', '[0:a][1:a]amerge=inputs=2,pan=stereo|c0<c0+c1|c1<c2+c3[aout]', '-map', '[aout]', mixed_audio_path ] try: subprocess.run(cmd, check=True, capture_output=True) except: # 如果合并失败,使用隐写音频 mixed_audio_path = stego_audio_path # 使用ffmpeg从帧序列创建视频 frame_pattern = os.path.join(frames_dir, "frame_%06d.png") # 生成随机比特率 (5000-7000kbps) bitrate = random.randint(5000, 7000) self.status_updated.emit(f"使用比特率: {bitrate}kbps") # 使用H.264编码和指定的参数 cmd = [ 'ffmpeg', '-y', '-framerate', '30', '-i', frame_pattern, '-i', mixed_audio_path, '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-crf', '18', # 较低CRF值以获得更高质量 '-preset', 'fast' if not self.use_gpu else 'medium', '-b:v', f'{bitrate}k', '-maxrate', f'{bitrate + 1000}k', '-bufsize', f'{bitrate * 2}k', '-s', '1080x2336', '-c:a', 'aac', '-b:a', '128k', '-metadata', 'title=Processed Video', output_path ] try: subprocess.run(cmd, check=True, capture_output=True) except subprocess.CalledProcessError as e: # 如果添加音频失败,尝试创建没有音频的视频 cmd_no_audio = [ 'ffmpeg', '-y', '-framerate', '30', '-i', frame_pattern, '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-crf', '18', '-preset', 'fast' if not self.use_gpu else 'medium', '-b:v', f'{bitrate}k', '-maxrate', f'{bitrate + 1000}k', '-bufsize', f'{bitrate * 2}k', '-s', '1080x2336', '-metadata', 'title=Processed Video', output_path ] subprocess.run(cmd_no_audio, check=True, capture_output=True) # 添加声纹去重处理(简化实现) self.add_voiceprint_processing(output_path) def add_voiceprint_processing(self, video_path): """添加声纹去重处理(简化实现)""" # 在实际应用中,这里应该实现复杂的声纹处理算法 # 这里只是一个简单的示例,添加一些元数据标记 temp_output = video_path + ".temp.mp4" cmd = [ 'ffmpeg', '-i', video_path, '-metadata', 'voiceprint_processed=true', '-metadata', 'voiceprint_hash=' + ''.join(random.choices('0123456789abcdef', k=32)), '-codec', 'copy', '-y', temp_output ] try: subprocess.run(cmd, check=True, capture_output=True) # 替换原文件 os.replace(temp_output, video_path) except: # 如果添加元数据失败,保持原文件不变 if os.path.exists(temp_output): os.remove(temp_output) def add_random_metadata(self, video_path): """添加随机元数据到视频文件""" metadata_options = { 'creation_time': ['2023-01-15T10:30:00', '2023-02-20T14:45:30', '2023-03-10T09:15:45'], 'location': ['New York, USA', 'London, UK', 'Tokyo, Japan', 'Paris, France'], 'device': ['iPhone 14 Pro', 'Samsung Galaxy S23', 'Canon EOS R5', 'Sony A7IV'], 'description': ['Beautiful landscape', 'Urban exploration', 'Nature documentary', 'Travel vlog'], 'encoder': ['H.264', 'HEVC', 'AV1', 'VP9'] } # 随机选择元数据 selected_metadata = { 'creation_time': random.choice(metadata_options['creation_time']), 'location': random.choice(metadata_options['location']), 'device': random.choice(metadata_options['device']), 'description': random.choice(metadata_options['description']), 'encoder': random.choice(metadata_options['encoder']) } # 创建临时输出文件 temp_output = video_path + ".temp.mp4" # 使用ffmpeg添加元数据 cmd = [ 'ffmpeg', '-i', video_path, '-metadata', f'creation_time={selected_metadata["creation_time"]}', '-metadata', f'location={selected_metadata["location"]}', '-metadata', f'device={selected_metadata["device"]}', '-metadata', f'description={selected_metadata["description"]}', '-metadata', f'encoder={selected_metadata["encoder"]}', '-codec', 'copy', '-y', temp_output ] try: subprocess.run(cmd, check=True, capture_output=True) # 替换原文件 os.replace(temp_output, video_path) except: # 如果添加元数据失败,保持原文件不变 if os.path.exists(temp_output): os.remove(temp_output) class VideoSteganographyApp(QMainWindow): def __init__(self): super().__init__() self.video_a_path = "" self.video_b_paths = [] self.output_dir = "" self.use_gpu = False self.gpu_type = "auto" self.initUI() def initUI(self): self.setWindowTitle('视频隐写处理工具 - 最终版') self.setGeometry(100, 100, 900, 800) # 设置应用程序图标 if hasattr(sys, '_MEIPASS'): # 打包后的路径 icon_path = os.path.join(sys._MEIPASS, 'app_icon.ico') else: # 开发时的路径 icon_path = 'app_icon.ico' if os.path.exists(icon_path): self.setWindowIcon(QIcon(icon_path)) # 设置暗色主题样式 self.setStyleSheet(""" QMainWindow { background-color: #2b2b2b; color: #cccccc; } QGroupBox { font-weight: bold; border: 2px solid #444444; border-radius: 5px; margin-top: 1ex; padding-top: 10px; background-color: #3c3c3c; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px 0 5px; color: #ffffff; } QPushButton { background-color: #4CAF50; border: none; color: white; padding: 10px 20px; text-align: center; text-decoration: none; font-size: 16px; margin: 4px 2px; border-radius: 5px; } QPushButton:hover { background-color: #45a049; } QPushButton:disabled { background-color: #555555; } QPushButton:checked { background-color: #2196F3; } QLabel { padding: 5px; color: #cccccc; } QProgressBar { border: 2px solid #444444; border-radius: 5px; text-align: center; background-color: #3c3c3c; } QProgressBar::chunk { background-color: #4CAF50; width: 10px; } QCheckBox { padding: 5px; color: #cccccc; } QCheckBox::indicator { width: 15px; height: 15px; } QCheckBox::indicator:unchecked { border: 1px solid #555555; background-color: #3c3c3c; } QCheckBox::indicator:checked { border: 1px solid #555555; background-color: #4CAF50; } QListWidget { border: 1px solid #444444; border-radius: 3px; background-color: #3c3c3c; color: #cccccc; } QComboBox { border: 1px solid #444444; border-radius: 3px; padding: 5px; background-color: #3c3c3c; color: #cccccc; } QComboBox QAbstractItemView { border: 1px solid #444444; background-color: #3c3c3c; color: #cccccc; selection-background-color: #4CAF50; } QTextEdit { background-color: #3c3c3c; color: #cccccc; border: 1px solid #444444; border-radius: 3px; } """) central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) # 标题 title_label = QLabel("视频隐写处理工具 - 最终版") title_label.setAlignment(Qt.AlignCenter) title_font = QFont() title_font.setPointSize(20) title_font.setBold(True) title_label.setFont(title_font) layout.addWidget(title_label) # 加速选项 acceleration_group = QGroupBox("加速选项") acceleration_layout = QHBoxLayout() self.gpu_checkbox = QCheckBox("使用GPU加速") self.gpu_checkbox.setChecked(False) self.gpu_checkbox.stateChanged.connect(self.toggle_gpu_acceleration) acceleration_layout.addWidget(self.gpu_checkbox) self.gpu_type_combo = QComboBox() self.gpu_type_combo.addItems(["自动检测", "NVIDIA CUDA", "OpenCL (Intel/AMD)"]) self.gpu_type_combo.currentIndexChanged.connect(self.change_gpu_type) acceleration_layout.addWidget(QLabel("GPU类型:")) acceleration_layout.addWidget(self.gpu_type_combo) acceleration_group.setLayout(acceleration_layout) layout.addWidget(acceleration_group) # 视频A选择区域 video_a_group = QGroupBox("视频A (主视频)") video_a_layout = QVBoxLayout() self.video_a_label = QLabel("未选择文件") video_a_layout.addWidget(self.video_a_label) video_a_btn = QPushButton("选择视频A") video_a_btn.clicked.connect(self.select_video_a) video_a_layout.addWidget(video_a_btn) video_a_group.setLayout(video_a_layout) layout.addWidget(video_a_group) # 视频B选择区域 video_b_group = QGroupBox("视频B (隐写视频 - 可多选)") video_b_layout = QVBoxLayout() self.video_b_list = QListWidget() video_b_layout.addWidget(self.video_b_list) video_b_btn_layout = QHBoxLayout() add_video_b_btn = QPushButton("添加视频B") add_video_b_btn.clicked.connect(self.add_video_b) video_b_btn_layout.addWidget(add_video_b_btn) remove_video_b_btn = QPushButton("移除选中") remove_video_b_btn.clicked.connect(self.remove_video_b) video_b_btn_layout.addWidget(remove_video_b_btn) clear_video_b_btn = QPushButton("清空列表") clear_video_b_btn.clicked.connect(self.clear_video_b) video_b_btn_layout.addWidget(clear_video_b_btn) video_b_layout.addLayout(video_b_btn_layout) video_b_group.setLayout(video_b_layout) layout.addWidget(video_b_group) # 输出选择区域 output_group = QGroupBox("输出设置") output_layout = QVBoxLayout() self.output_label = QLabel("未选择输出目录") output_layout.addWidget(self.output_label) output_btn = QPushButton("选择输出目录") output_btn.clicked.connect(self.select_output) output_layout.addWidget(output_btn) output_group.setLayout(output_layout) layout.addWidget(output_group) # 进度区域 progress_group = QGroupBox("处理进度") progress_layout = QVBoxLayout() self.batch_label = QLabel("准备就绪") progress_layout.addWidget(self.batch_label) self.status_label = QLabel("等待开始...") progress_layout.addWidget(self.status_label) self.progress_bar = QProgressBar() self.progress_bar.setValue(0) progress_layout.addWidget(self.progress_bar) progress_group.setLayout(progress_layout) layout.addWidget(progress_group) # 处理按钮 self.process_btn = QPushButton("开始批量处理") self.process_btn.clicked.connect(self.process_videos) self.process_btn.setEnabled(False) layout.addWidget(self.process_btn) # 日志区域 log_group = QGroupBox("处理日志") log_layout = QVBoxLayout() self.log_text = QTextEdit() self.log_text.setReadOnly(True) log_layout.addWidget(self.log_text) log_group.setLayout(log_layout) layout.addWidget(log_group) # 初始化日志 self.log_text.append("应用程序已启动") self.log_text.append(f"CUDA可用: {CUDA_AVAILABLE}") self.log_text.append(f"OpenCL可用: {OPENCL_AVAILABLE}") self.log_text.append(f"Scipy可用: {SCIPY_AVAILABLE}") def toggle_gpu_acceleration(self, state): self.use_gpu = (state == Qt.Checked) if self.use_gpu: self.log_text.append("已启用GPU加速") else: self.log_text.append("已禁用GPU加速,使用CPU处理") def change_gpu_type(self, index): if index == 0: self.gpu_type = "auto" self.log_text.append("GPU类型: 自动检测") elif index == 1: self.gpu_type = "cuda" self.log_text.append("GPU类型: NVIDIA CUDA") elif index == 2: self.gpu_type = "opencl" self.log_text.append("GPU类型: OpenCL (Intel/AMD)") def select_video_a(self): file_path, _ = QFileDialog.getOpenFileName( self, "选择视频A文件", "", "视频文件 (*.mp4 *.avi *.mov *.mkv)" ) if file_path: self.video_a_path = file_path self.video_a_label.setText(f"已选择: {os.path.basename(file_path)}") self.log_text.append(f"已选择视频A: {file_path}") self.check_ready() def add_video_b(self): file_paths, _ = QFileDialog.getOpenFileNames( self, "选择视频B文件", "", "视频文件 (*.mp4 *.avi *.mov *.mkv)" ) if file_paths: for file_path in file_paths: if file_path not in self.video_b_paths: self.video_b_paths.append(file_path) self.video_b_list.addItem(os.path.basename(file_path)) self.log_text.append(f"已添加视频B: {file_path}") self.check_ready() def remove_video_b(self): selected_items = self.video_b_list.selectedItems() for item in selected_items: index = self.video_b_list.row(item) removed_path = self.video_b_paths.pop(index) self.video_b_list.takeItem(index) self.log_text.append(f"已移除视频B: {removed_path}") self.check_ready() def clear_video_b(self): self.video_b_paths.clear() self.video_b_list.clear() self.log_text.append("已清空视频B列表") self.check_ready() def select_output(self): dir_path = QFileDialog.getExistingDirectory( self, "选择输出目录" ) if dir_path: self.output_dir = dir_path self.output_label.setText(f"输出目录: {dir_path}") self.log_text.append(f"已选择输出目录: {dir_path}") self.check_ready() def check_ready(self): if self.video_a_path and self.video_b_paths and self.output_dir: self.process_btn.setEnabled(True) else: self.process_btn.setEnabled(False) def process_videos(self): self.process_btn.setEnabled(False) self.log_text.append("开始批量处理视频...") self.processor = VideoProcessor( self.video_a_path, self.video_b_paths, self.output_dir, self.use_gpu, self.gpu_type ) self.processor.progress_updated.connect(self.update_progress) self.processor.status_updated.connect(self.update_status) self.processor.finished.connect(self.processing_finished) self.processor.batch_progress.connect(self.update_batch_progress) self.processor.start() def update_progress(self, value): self.progress_bar.setValue(value) def update_status(self, message): self.status_label.setText(message) self.log_text.append(message) def update_batch_progress(self, current, total): self.batch_label.setText(f"处理进度: {current}/{total}") self.log_text.append(f"开始处理第 {current} 个视频,共 {total} 个") def processing_finished(self, success, message): self.process_btn.setEnabled(True) self.status_label.setText("处理完成" if success else "处理失败") self.log_text.append(message) if success: QMessageBox.information(self, "成功", message) else: QMessageBox.warning(self, "错误", message) def main(): app = QApplication(sys.argv) # 设置应用程序样式为Fusion,支持暗色主题 app.setStyle('Fusion') # 设置调色板为暗色主题 palette = QPalette() palette.setColor(QPalette.Window, QColor(43, 43, 43)) palette.setColor(QPalette.WindowText, Qt.white) palette.setColor(QPalette.Base, QColor(25, 25, 25)) palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53)) palette.setColor(QPalette.ToolTipBase, Qt.white) palette.setColor(QPalette.ToolTipText, Qt.white) palette.setColor(QPalette.Text, Qt.white) palette.setColor(QPalette.Button, QColor(53, 53, 53)) palette.setColor(QPalette.ButtonText, Qt.white) palette.setColor(QPalette.BrightText, Qt.red) palette.setColor(QPalette.Link, QColor(42, 130, 218)) palette.setColor(QPalette.Highlight, QColor(42, 130, 218)) palette.setColor(QPalette.HighlightedText, Qt.black) app.setPalette(palette) window = VideoSteganographyApp() window.show() sys.exit(app.exec_()) if __name__ == '__main__': main() 生成的软件提示: 处理失败: 处理过程中出现错误: 'numpy.ndarray' object has no attribute 'ast' 详细信息: Traceback (most recent call last): File "main.py", line 278, in process_single_video File "main.py", line 642, in embed_steganography File "main.py", line 616, in dct_embed_cpu AttributeError: 'numpy.ndarray' object has no attribute 'ast' 请修正一下
最新发布
08-22
给我详细但是又通俗的讲一下: AMS设计文档 版本/状态 作者 起止日期 描述 V0.1/草稿 杨威 2018-2-26 创建文档 V0.2 许楚萍 2019.7.9 框架更新,新增结果后处理 1. 引言 AMS(Algorithmic Management System)旨在对IPC上运行的音视频算法进行统一管理。在原有libSmart的基础上,主要做了如下改进: AMS模块完全由数据驱动,每个模块可看成独立的数据加工者,消耗数据并产生新的数据 拆解Common部分成各个独立的模块,使其能独立参与编译(有依赖关系的除外) 与芯片底层的数据交互(如帧、硬件帧差)使用环作为缓存 与芯片或机型相关的配置从AMS代码中抽离成config文件,减少AMS的重编译和发布次数 ​ 2. 架构和数据流 AMS(Algorithmic Management System)旨在对IPC上运行的音视频算法进行统一管理。 2.1 文件组织 对AMS进行文件划分如下: 音频侦测与识别(ADR,audio detection and recognition) 音频质量改善(AQI,audio quality improvement) 视频侦测与识别(VDR,video detection and recognition) 视频质量改善(VQI,video quality improvement) 每个模块下辖多个子模块,具体如下图所示: 每个子模块的文件夹下包含该模块的头文件、源文件和Makefile。 AMS使用repo进行代码版本管理(repo用于方便管理多个git库而开发的python脚本)。AMS的库划分如下所示: ams库:ams文件夹下除/src/vdr、/src/adr、/src/vqi之外的所有文件,主要包含ams框架和pc_dbug /vdr/basic_processing库:/ams/src/vdr/basic_processing /vdr/behavior库:/ams/src/vdr/behavior /vdr/detection库:/ams/src/vdr/detection /vdr/functional库:/ams/src/vdr/functional /vdr/recognition库:/ams/src/vdr/recognition /vdr/TENN库:/ams/src/vdr/TENN /vdr/utils库:/ams/src/vdr/utils /adr/aframe库:/ams/src/adr/aframe /adr/functional库:/ams/src/adr/functional /adr/recognition库:/ams/src/adr/recognition /adr/utils库:/ams/src/adr/utils 所有库都可以使用git进行独立的版本控制。 2.2 数据流 VDR包含的算法及数据流走向如下图所示: 整个VDR可能由一种或多种来自于芯片底层的 数据源驱动 。当底层的这些数据有更新的时候,会通过某种方式通知AMS,并将新的数据传给AMS,算法因此产生一条消耗处理该数据的流水线,并相应的产生一次结果。 2.2.1 数据源的接收和分发 AMS需要接收芯片底层的源数据,然后分发给内部算法进行处理。在设计方面,需要考虑以下几点(以接收视频帧数据为例): 在有新帧的时候,才进行收帧和分发处理的动作 分发处理不能对收帧线程产生阻塞 避免重复拷贝 基于以上考虑,我们采用注册回调机制、设计缓存数据环来对收发帧数据进行管理。要点如下(参照下图): ams 向数据采集模块(avdc)注册回调函数:vframe_recv_cb avdc 在获取到新帧的时候,调用 vframe_recv_cb 函数,该回调函数主要完成以下任务: 取出环上空余位置(tail),如果没有空余位置,则舍弃掉该帧 将该帧数据拷贝(图中粗实线)至环上 以某种方式通知ams有新的一帧数据到来 ams模块在收到有新帧的消息时,调用 vframe_new_data_cb 函数,该函数主要完成以下任务: 取出环上一帧数据(head),如果没有,则直接返回 遍历 vdr_list 中的所有子模块,如果该模块关注帧数据(由attribute设置),则通过调用该模块的 trigger 函数将帧数据分发给其进行处理(分发的只是指针,并不做拷贝) AMS用g_trigger_node数组记录所有DATA_TYPE与算法模块的callback(即各模块的trigger 函数)之间的驱动关系。 g_trigger_node数组结构如下图所示: B模块在程序初始化阶段,自己的callback通过以下方式注册给g_trigger_node数组。 C /* * 以MD模块为例 * DATA_TYPE_FG_DIFF2是MD模块的驱动数据 * md_trigger是MD模块的trigger函数,用于接收和处理模块的驱动数据 */ alg_trigger_add(md_trigger, DATA_TYPE_FG_DIFF2); 当模块产生新数据DATA_TYPE_xx时,在g_trigger_node查找并触发其对应的算法callback,将此数据分发给此callback进行处理,此callback继续产生新的数据,依次往下按照深度优先顺序嵌套调用。 以vframe为例,具体调用过程如下图所示: 2.1.2. 环的设计与管理 以帧数据环的设计为例,目前使用数组来实现环。数组只保存帧数据的描述结构(VFRAME_DESC),分发也只分发该描述。由于描述中带有具体数据的地址,因此分发过程不涉及拷贝,其映射关系如下图: 具体数据结构定义如下: C /* 环上每个节点的内容 */ typedef struct _VFRAME_DESC { U64 stamps; /* 帧产生的时间 */ U16 refcnt; /* 引用计数,表明多少上层应用正在使用该帧 */ U16 collect_type; /* 采集的帧类型:单独Y、YUV420、YUV444. */ VFRAME *y, *u, *v; }VFRAME_DESC; /* 环 */ typedef struct _VFRAME_DESC_RING { VFRAME_DESC buffer[VFRAME_DESC_RING_SIZE]; /* 静态数组实现环 */ U8 head; /* 环头 */ U8 tail; /* 环尾 */ U16 reserved; /* 保留字段 */ VFRAME *data; /* 帧数据的首地址 */ }VFRAME_DESC_RING; 环上数据的生产者和消费者处于不同线程。需要使用 head、tail 以及每个环数据的 refcnt 来对环进行管理。tail 用于标识可能的放数据的位置;head 用于标识可能的取数据的位置;refcnt 用于标识某个数据的引用计数,不为0表示该数据正在被占用,不能被覆盖。环的管理方式具体如下: 在取出环尾空余位置时,如果尾部下一个位置(tail+1)的 refcnt 不为0,则返回NULL,表示没有空余位置 在取出环头数据进行分发后,直接向head后移;接收模块在使用该desc前对其attach(refcnt+1),使用后对其detach(refcnt-1) 2 模块抽象 VDR模块中的每个算法子模块都是一个VDR数据结构的一个实例,VDR的抽象结构如下所示: C /* 视频事件侦测与识别算法 */ typedef struct _VDR { char name[VDR_NAME_SIZE]; U8 event; /* 关注的事件类型 */ S32 (*init)(); /* 算法初始化,读取用户配置、系统参数,并配置到算法&ISP生效; */ S32 (*deinit)(); /* 算法去初始化,释放各功能资源 */ S32 (*reload)(); /* 算法配置更新。NSD有更新对应的参数时,会发送DMS消息,对应的进程关注消息并重载; */ S32 (*control)(U8 event_type, ALG_CONTROL alg_control); /* 算法控制,包括算法暂停、继续、清除缓存等 */ U32 (*status)(); /* 获取算法当前状态,如启用、停用等,详细待定 */ }VDR; typedef ALG_NODE VDR; VDR的抽象结构如上所示。对于隶属于VDR模块的每个子模块,如vframe、md等,均是VDR数据结构的一个实例。各成员具体含义如下: name 算法的名字。如MD算法模块的名字为"vdr_md",OD算法模块的名字为"vdr_od"。 event 算法关注的事件类型,包括电机转动事件、画面变化所引发的reset事件、COM口事件 init 算法的初始化函数。程序启动阶段会被调用,用于读取用户配置、系统参数,并配置到算法 deinit 算法的去初始化函数。AMS线程关闭时被调用,用于释放各功能资源。 reload 算法的加载配置函数。在该模块的相关配置数据被更改时会被调用,主要用于读取用户配置。 status 上层获取算法内部数据或信息的函数。 control 算法的控制函数。当事件发生时被调用,用于修改算法开关、控制算法状态(包括STATE_RUNNING, STATE_RESET, STATE_RELOAD)。每个算法在新的驱动源到来时处理算法主流程之前,首先根据算法状态来调用reload或reset,然后根据算法开关来决定是否运行算法主流程。 ##3 算法配置 3.1 配置数据 参考《AMS配置数据.md》,AMS涉及的配置主要有以下四种: 编译时配置 该配置需在编译时指定,用于确定参与编译的文件及相关宏定义 运行时不变配置 该配置是指在运行时不会被改变的配置 此配置只需在程序启动的时候通过init进行一次加载,之后不需要也不会被改变 运行时可变系统配置 该配置是指IPC系统本身的一些属性信息,算法处理过程中需要知道这些属性,根据这些属性的不同做相应的处理,如红外灯状态、电机状态等 这些属性在运行时是可变的,需要系统实时的配置给算法 运行时可变用户配置 该配置是指用户在功能页面上可调的针对该功能的配置。如移动侦测的用户配置为:开关、灵敏度和检测区域。 ​ 3.2 配置的加载 各个模块的配置数据加载分两种情况: 程序启动时,init 函数中加载配置 程序运行时,配置数据有变化时加载配置 通过ds_read方法加载配置,配置路径和配置模型定义在”slp_model.h“。 C ds_read(MOTION_DETECT_PATH, (void *)&motion_det, sizeof(MOTION_DETECT)) 运行时加载配置仍然通过回调的形式,在 ams_event.h 中定义一个消息id,并为该消息关联一个reload函数,当dms收到该类型的消息时,表明配置数据有变化,然后调用reload函数加载更新。 C /* 关注用户配置变化 */ msg_attach_handler(AMS_VDR_MD_RELOAD, md_reload); 4 结果后处理 结果后处理是指对于有些算法结果(如MD_RESULT),根据用户配置或系统配置,需要对算法的结果再进一步处理(如人形增强MD)。目前需要后处理的模块包括: 人形增强:ID,CD, GR, TT, PF, HD, CGD 人脸增强:CGD 车辆增强:PKD 对于需要结果后处理的模块,与trigger类似,AMS用g_post_proc数组记录所有RESULT_TYPE与算法模块的后处理函数(即各模块的xx_integrate 函数)之间的对应关系。 模块在程序初始化阶段,将自己的后处理函数通过以下方式注册给g_post_proc数组。 C /* * 以MD模块为例 * RESULT_TYPE_MD是MD模块关注的结果数据 * md_integrate是MD模块的后处理函数,用于接收和处理结果数据 */ post_proc_add(md_integrate, RESULT_TYPE_MD); 当A模块产生新的结果类型RESULT_TYPE_A时,在g_post_proc查找其对应的算法后处理函数,如果B模块已将自己的后处理函数添加RESULT_TYPE_A的列表中,则触发此后处理函数将结果分发给B进行后处理。 C /* * 以MD模块为例 * RESULT_TYPE_MD是MD模块产生的结果数据 * md_context->md_result是MD模块的运行结果 */ post_proc_trigger((void *)&md_context->md_result, RESULT_TYPE_MD); 以MD后处理(PD增强MD)为例,后处理流程如下所示: MD模块的后处理指的是用PD结果增强MD,后处理所需要的结果数据由各模块发出并触发MD后处理,当所有所需数据到齐时MD执行后处理流程,将最终的MD结果发送给上层。 对于不需要后处理的模块,如AOD,可以直接在算法流程处理完之后发送结果给上层,无需添加AOD后处理。
08-21
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值