UISlider & output ‘%’

UISlider使用详解
本文探讨了在iOS开发中使用UISlider遇到的问题及解决方案,包括如何正确获取UISlider的值并将其转换为百分比形式,以及如何在Objective-C中正确显示百分比符号。

第一篇文章还是原创的好,嘎嘎……

在学习使用UISlider的时候,根据书(iPhone开发基础教程 Dave Mark & Jeff LaMarche)上是这样的:

UISlider *slider = (UISlider *) sender; int per = (int) (slider.value * 100); 

但是我发现per只有0和1,无语了……估计slider.value已经是百分比了,不过只是浮点数吧,然后改成:

int per = (int) (slider.value * 100); 

对了,百分比没错了,然后呢就是输出‘%’的问题了,这个简单啦,不是说objective-c拥有c的一切特性么?

NSString *str = [[NSString alloc] initWithFormat:@"%d/%", per]; 

再次杯具了,无法打印‘%’,在网上查了一下,原来‘%’要这样 %% 来输出的……

我猜objective-c的特殊字符都是用 %打印的,而不是 / 

后来试了一下,输出双引号又是用 /" ,可能只有特殊几个符号是用 % 输出的。。。

我现在给出完整代码,下一个问题会提出修改需求,请先读取我提供的代码function voice_timbre_conversion() % 主函数:基于傅里叶变换的音色转换工具 close all; clc; % 创建GUI界面 fig = uifigure('Name', '基于傅里叶变换的音色转换', 'Position', [100 100 600 400]); % 创建控件 uilabel(fig, 'Text', '输入音频文件:', 'Position', [20 350 100 22]); filePath = uieditfield(fig, 'text', 'Position', [130 350 300 22], 'Value', 'input.wav'); uibutton(fig, 'push', 'Text', '浏览...', 'Position', [450 350 80 22],... 'ButtonPushedFcn', @(btn,event) browseFile(filePath)); uilabel(fig, 'Text', '频率缩放因子:', 'Position', [20 300 100 22]); formantSlider = uislider(fig, 'Position', [130 300 300 3],... 'Limits', [0.8 1.5], 'Value', 1.2); uilabel(fig, 'Text', '频谱显示范围 (Hz):', 'Position', [20 250 120 22]); freqRange = uieditfield(fig, 'text', 'Position', [150 250 100 22], 'Value', '5000'); uibutton(fig, 'push', 'Text', '播放原始音频', 'Position', [100 180 120 30],... 'ButtonPushedFcn', @(btn,event) playAudio(filePath.Value)); uibutton(fig, 'push', 'Text', '转换并播放', 'Position', [250 180 120 30],... 'ButtonPushedFcn', @(btn,event) convertAndPlay(filePath.Value, formantSlider.Value, str2double(freqRange.Value))); uibutton(fig, 'push', 'Text', '保存结果', 'Position', [400 180 120 30],... 'ButtonPushedFcn', @(btn,event) saveResult(filePath.Value, formantSlider.Value)); % 频谱分析区域 ax1 = uiaxes(fig, 'Position', [50 50 250 120], 'Title', '原始频谱'); ax2 = uiaxes(fig, 'Position', [300 50 250 120], 'Title', '转换后频谱'); linkaxes([ax1 ax2], 'xy'); end function browseFile(editField) % 文件浏览函数 [file, path] = uigetfile({'*.wav;*.mp3;*.ogg', '音频文件 (*.wav, *.mp3, *.ogg)'}); if isequal(file,0) return; end editField.Value = fullfile(path, file); end function playAudio(filename) % 播放原始音频 try [y, fs] = audioread(filename); soundsc(y, fs); catch ME errordlg(['文件错误: ' ME.message], '播放错误'); end end function convertAndPlay(filename, shiftFactor, freqRange) % 转换并播放函数 try [y, fs] = audioread(filename); [converted, origSpectrum, convSpectrum] = timbreShift(y, fs, shiftFactor, freqRange); soundsc(converted, fs); % 更新频谱显示 fig = gcf; ax1 = findobj(fig, 'Title', '原始频谱'); ax2 = findobj(fig, 'Title', '转换后频谱'); plotSpectrum(ax1, origSpectrum, freqRange, '原始频谱'); plotSpectrum(ax2, convSpectrum, freqRange, '转换后频谱'); catch ME errordlg(['转换错误: ' ME.message], '处理错误'); end end function saveResult(filename, shiftFactor) % 保存结果函数 try [y, fs] = audioread(filename); converted = timbreShift(y, fs, shiftFactor, 5000); [file, path] = uiputfile({'*.wav', 'WAV文件 (*.wav)'}, '保存转换后的音频'); if isequal(file,0) return; end audiowrite(fullfile(path, file), converted, fs); msgbox('音频保存成功!', '完成'); catch ME errordlg(['保存错误: ' ME.message], '文件错误'); end end function [output, origSpectrum, convSpectrum] = timbreShift(input, fs, shiftFactor, freqRange) % 基于傅里叶变换的音色转换核心函数 % 输入参数: % input - 输入音频信号 % fs - 采样率 % shiftFactor - 频率缩放因子(男变女:1.2-1.5, 女变男:0.8-0.9) % freqRange - 频谱显示频率范围 % 预处理:转为单声道并预加重 if size(input, 2) > 1 input = mean(input, 2); end input = filter([1 -0.95], 1, input); % 预加重滤波器 % FFT参数设置 N = length(input); f = (0:N-1)*(fs/N); % 频率向量 % 原始频谱分析 F = fft(input); origSpectrum = abs(F(1:floor(N/2)+1)); % 单边频谱 % 创建新的频率轴并插值 f_new = f * shiftFactor; f_max = min(fs/2, max(f_new)); % 防止超出Nyquist频率 % 对频谱进行傅里叶变换后的缩放处理 F_new = zeros(size(F)); F_new(1) = F(1); % 保留DC分量 % 使用样条插值迁移频谱 F_mag = abs(F); F_phase = angle(F); % 对正频率部分处理 idx = 2:ceil(N/2); F_mag_new = interp1(f(idx), F_mag(idx), f_new(idx), 'pchip', 0); % 对称处理负频率部分 F_mag_new_full = [F_mag_new; flip(F_mag_new(2:end-1))]; if mod(N, 2) == 0 F_mag_new_full = [F_mag_new_full; F_mag_new_full(1)]; end % 重新组合幅度和相位 F_new = F_mag_new_full .* exp(1i*F_phase); % 逆向傅里叶变换 output = real(ifft(F_new)); % 后处理:去加重 output = filter(1, [1 -0.95], output); % 转换后频谱分析 F_conv = fft(output); convSpectrum = abs(F_conv(1:floor(N/2)+1)); end function plotSpectrum(ax, spectrum, freqRange, titleText) % 在指定坐标轴上绘制频谱图 plot(ax, linspace(0, freqRange, length(spectrum)), spectrum); title(ax, titleText); xlabel(ax, '频率 (Hz)'); ylabel(ax, '幅度'); grid(ax, 'on'); end
06-13
clear;clc; %% 轨迹生成部分 % XY平面参数 R =input('输入元件的检测半径 (mm): '); % 目标圆半径 (mm) r = input('输入检测光源的有效半径 (mm): '); % 采样圆半径 (mm) alpha = input('输入重叠率 (mm): ');% 重叠率 dr = 1.5*r*(1-alpha); % 径向步长 % 交互式输入接口 [核心修改] cx = input('输入圆心X坐标 (mm): '); cy = input('输入圆心Y坐标 (mm): '); cz0 = input('输入Z起始高度 (mm): '); % 新增Z初始位置 dz = input('输入Z方向切片间距 (mm): '); % 新增Z方向间隔 nz = input('输入Z方向切片数量: '); % 新增切片层数 %% 生成单层径向扫描轨迹 base_points = [cx, cy]; % 基点为自定义圆心 for k = 1:ceil(R/dr) radius = k * dr; if radius > (R-r) , break; end % 动态计算每圈点数 circumference = 2 * pi * radius; n_theta = max(6, ceil(circumference / (2*r*(1-alpha)))); theta = linspace(0, 2*pi, n_theta+1)'; theta(end) = []; % 生成环形采样点 x = cx + radius * cos(theta); y = cy + radius * sin(theta); base_points = [base_points; x, y]; % 保存为二维点 end % 筛选有效点 dist = sqrt((base_points(:,1)-cx).^2 + (base_points(:,2)-cy).^2); valid_base = base_points(dist <= R - r, :); % 路径排序 (径向分组) radial_groups = round(sqrt((valid_base(:,1)-cx).^2 + (valid_base(:,2)-cy).^2)/dr); sorted_base = []; for g = unique(radial_groups)' group_idx = radial_groups == g; group_points = valid_base(group_idx, :); % 按角度排序 rel_angle = atan2(group_points(:,2)-cy, group_points(:,1)-cx); [~, idx] = sort(rel_angle); sorted_base = [sorted_base; group_points(idx, :)]; end all_points = []; % 存储所有三维点 for z_layer = 1:nz z_val = cz0 + (z_layer-1)*dz; % 计算当前Z高度 layer_points = [sorted_base, repmat(z_val, size(sorted_base,1), 1)]; % 添加Z坐标 all_points = [all_points; layer_points]; % 添加到总点集 % 添加层间过渡点 (NaN断开) if z_layer < nz all_points = [all_points; [NaN, NaN, NaN]]; end end %% 覆盖率计算 (XY平面单层) num_mc = 1e6; mc_x = cx - R + 2*R*rand(num_mc, 1); mc_y = cy - R + 2*R*rand(num_mc, 1); inside_target = ((mc_x-cx).^2 + (mc_y-cy).^2 <= R^2); covered = false(num_mc, 1); for i = 1:size(sorted_base,1) dist = sqrt((mc_x - sorted_base(i,1)).^2 + (mc_y - sorted_base(i,2)).^2); covered = covered | (dist <= r); end coverage_ratio = sum(covered & inside_target) / sum(inside_target) * 100; fprintf('\n===== 扫描参数统计 =====\n'); fprintf('总采样点数: %d (单层) × %d层 = %d点\n', size(sorted_base,1), nz, size(all_points,1)-nz+1); fprintf('Z轴范围: %.1fmm 到 %.1fmm\n', cz0, cz0+(nz-1)*dz); fprintf('单层覆盖率: %.2f%%\n', coverage_ratio); %% 坐标导出 output_points = all_points(~isnan(all_points(:,1)), :); % 移除NaN分隔符 writematrix(output_points, '3D_scan_coordinates.csv'); fprintf('三维坐标已导出至: 3D_scan_coordinates.csv\n'); %% 图像处理 % 定义裁剪区域 [起始X, 起始Y, 宽度, 高度] input_path = uigetdir('','选择需要处理图像所在文件夹'); if input_path == 0 error('用户取消了图像处理所在文件夹选择'); end output_path = uigetdir('','选择裁剪图像存储文件夹'); if output_path == 0 error('用户取消了图像处理所在文件夹选择'); end center_x = 300; % 固定圆心X坐标 center_y = 250; % 固定圆心Y坐标 radius = 140; % 固定半径 %% 裁剪图像 batchImageCrop(input_path, output_path,center_x, center_y, radius); %% 消除不均匀 batch_illumination_correction(); %% 抠图 % 配置路径 inputFolder2 = 'D:\01-工作\02-实验数据\图像采集\20250825\0825-ZJCL'; % 存放BMP图像的文件夹 outputFolder2 = 'D:\01-工作\02-实验数据\图像采集\20250825\0825-koutu'; % 新建输出文件夹 % 创建输出目录(如果不存在) if ~exist(outputFolder2, 'dir') mkdir(outputFolder2); end % 获取所有BMP文件 fileList = dir(fullfile(inputFolder2, '*.bmp')); totalFiles = length(fileList); % 处理参数设置 tolerance = 10; % 黑色检测容差 (0-255) % 进度显示 fprintf('开始处理 %d 个BMP文件...\n', totalFiles); % 批量处理循环 for i = 1:totalFiles try % 读取当前文件 bmpFile = fullfile(inputFolder2, fileList(i).name); img = imread(bmpFile); % 确保为RGB格式(处理灰度图) if size(img, 3) == 1 img = cat(3, img, img, img); end % 创建透明通道 % 原理:$ \text{alpha}(x,y) = % \begin{cases} % 0 & \text{若 } \forall c \in \{R,G,B\}, \, I_c(x,y) \leq \text{tolerance} \\ % 255 & \text{其他} % \end{cases} $ alpha = all(img <= tolerance, 3); alpha = uint8(255 * ~alpha); % 黑色区域透明 % 生成输出文件名(保持原名称) [~, baseName] = fileparts(fileList(i).name); pngFile = fullfile(outputFolder2, [baseName, '.png']); % 保存透明PNG imwrite(img, pngFile, 'png', 'Alpha', alpha); fprintf('已处理: %s -> %s\n', fileList(i).name, [baseName, '.png']); catch ME fprintf('文件 %s 处理失败: %s\n', fileList(i).name, ME.message); end end fprintf('\n处理完成! 共成功转换 %d/%d 个文件\n', totalFiles, length(dir(fullfile(outputFolder2, '*.png')))); %% 图像拼接 imageDir = 'D:\01-工作\02-实验数据\图像采集\20250825\0825-koutu'; WidthCm=7.44; HeightCm=7.44; imageStitching(imageDir, WidthCm, HeightCm); %% 主体函数 %% 裁剪主函数 function batchImageCrop(input_folder, output_folder, center_x, center_y, radius) % 参数说明: % input_folder: 输入图像文件夹路径 % output_folder: 输出图像文件夹路径 % center_x: 圆形中心点x坐标(固定值) % center_y: 圆形中心点y坐标(固定值) % radius: 圆形半径(固定值) % 创建输出文件夹 if ~exist(output_folder, 'dir') mkdir(output_folder); end % 获取所有JPG图像文件 img_files = dir(fullfile(input_folder, '**', '*.bmp')); total_imgs = length(img_files); % 处理进度显示 fprintf('开始处理%d张图像...\n', total_imgs); progress_interval = max(floor(total_imgs/10), 1); % 进度更新间隔 % 固定位置圆形掩码(只需计算一次) [xx, yy] = meshgrid(1:2*radius+1, 1:2*radius+1); mask = (xx - radius - 1).^2 + (yy - radius - 1).^2 <= radius^2; % 遍历所有图像 for i = 1:total_imgs % 读取图像 img_path = fullfile(img_files(i).folder, img_files(i).name); img = imread(img_path); % 固定位置圆形裁剪 cropped = circular_crop(img, center_x, center_y, radius, mask); % 保存结果 [~, name, ext] = fileparts(img_files(i).name); output_path = fullfile(output_folder, [name '_cropped' ext]); imwrite(cropped, output_path); % 显示进度 if mod(i, progress_interval) == 0 fprintf('已处理: %d/%d (%.1f%%)\n', ... i, total_imgs, 100*i/total_imgs); end end fprintf('处理完成! 结果保存在: %s\n', output_folder); end function cropped = circular_crop(img, cx, cy, radius, mask) % 执行固定位置圆形裁剪 % 计算裁剪区域边界 x_start = max(1, round(cx - radius)); x_end = min(size(img, 2), round(cx + radius)); y_start = max(1, round(cy - radius)); y_end = min(size(img, 1), round(cy + radius)); % 裁剪矩形区域 rect_crop = img(y_start:y_end, x_start:x_end, :); % 调整掩码尺寸以适应实际裁剪区域 actual_height = size(rect_crop, 1); actual_width = size(rect_crop, 2); resized_mask = mask(1:actual_height, 1:actual_width); % 应用圆形遮罩 cropped = rect_crop; for c = 1:size(img, 3) channel = cropped(:,:,c); channel(~resized_mask) = 0; % 圆形外部设为黑色 cropped(:,:,c) = channel; end end %% 消除不均匀 function batch_illumination_correction() % 批量光照校正主函数 % 1. 获取用户输入的文件夹路径 bg_dir = uigetdir('', '选择背景图像文件夹'); if bg_dir == 0 error('用户取消了背景文件夹选择'); end target_dir = uigetdir('', '选择待校正目标图像文件夹'); if target_dir == 0 error('用户取消了目标文件夹选择'); end output_dir = uigetdir('', '选择校正后图像保存文件夹'); if output_dir == 0 error('用户取消了输出文件夹选择'); end % 2. 读取背景图像 bg_files = dir(fullfile(bg_dir, '*.bmp')); if isempty(bg_files) error('背景文件夹中未找到BMP图像'); end fprintf('正在加载背景图像...\n'); bg_stack = []; for i = 1:length(bg_files) img_path = fullfile(bg_dir, bg_files(i).name); img = imread(img_path); % 转换为灰度图(如果背景是彩色) if size(img, 3) == 3 img = rgb2gray(img); end % 添加到背景堆栈 if isempty(bg_stack) bg_stack = double(img); else bg_stack = cat(3, bg_stack, double(img)); end fprintf('已加载背景图像: %s\n', bg_files(i).name); end % 3. 计算平均背景和校正映射 fprintf('计算光照校正映射...\n'); avg_bg = mean(bg_stack, 3); global_mean = mean(avg_bg(:)); correction_map = global_mean ./ avg_bg; % 处理除零错误 correction_map(isinf(correction_map)) = 1; correction_map(isnan(correction_map)) = 1; % 4. 处理目标图像 target_files = dir(fullfile(target_dir, '*.bmp')); if isempty(target_files) error('目标文件夹中未找到BMP图像'); end fprintf('开始批量校正图像...\n'); for i = 1:length(target_files) % 读取目标图像 img_path = fullfile(target_dir, target_files(i).name); target_img = imread(img_path); % 5. 应用校正 if size(target_img, 3) == 3 % 处理彩色图像 corrected_img = zeros(size(target_img), 'like', target_img); for channel = 1:3 % 转换到double进行运算 channel_data = double(target_img(:, :, channel)); % 应用校正映射 corrected_channel = channel_data .* correction_map; % 归一化并转换回原始数据类型 corrected_channel = uint8(255 * mat2gray(corrected_channel)); corrected_img(:, :, channel) = corrected_channel; end else % 处理灰度图像 target_img_double = double(target_img); corrected_img = target_img_double .* correction_map; % 归一化并转换回原始数据类型 corrected_img = uint8(255 * mat2gray(corrected_img)); end % 6. 保存结果 output_path = fullfile(output_dir, target_files(i).name); imwrite(corrected_img, output_path); fprintf('已校正并保存: %s\n', target_files(i).name); end fprintf('\n处理完成! 共校正 %d 张图像\n', length(target_files)); fprintf('输出文件夹: %s\n', output_dir); % 7. 显示示例结果 try fig = figure('Name', '光照校正结果对比', 'NumberTitle', 'off'); subplot(1,2,1); imshow(imread(fullfile(target_dir, target_files(1).name))); title('原始图像'); subplot(1,2,2); imshow(imread(fullfile(output_dir, target_files(1).name))); title('校正后图像'); catch warning('结果预览失败,但仍已保存所有图像'); end end %% 增强版图像拼接程序 - 基于强度的融合处理 %% 增强版图像拼接程序 - 支持Z分层 function fullImage = imageStitching(imageDir, pptWidthCm, pptHeightCm) % 参数说明: % imageDir: 图像文件夹路径 % pptWidthCm: PPT中图像显示的宽度(cm) % pptHeightCm: PPT中图像显示的高度(cm) %% 步骤1: 读取并解析图像文件(包含Z坐标) [imageFiles, positions, zValues] = parseImageFilesWithZ(imageDir); uniqueZ = unique(zValues); numLayers = numel(uniqueZ); %% 按Z层分组处理 for zIdx = 1:numLayers currentZ = uniqueZ(zIdx); fprintf('\n====== 处理 Z=%.3f 的图像层 ======\n', currentZ); % 筛选当前Z层的图像 layerIdx = (zValues == currentZ); layerFiles = imageFiles(layerIdx); layerPositions = positions(layerIdx, :); % 检查是否为空 if isempty(layerFiles) fprintf('警告: Z=%.3f 没有找到图像,跳过\n', currentZ); continue; end % 加载第一张图像确定基准尺寸 img1 = imread(fullfile(imageDir, layerFiles(1).name)); [height, width, numChannels] = size(img1); % 计算缩放因子 scaleFactor = calculateScaleFactor(pptWidthCm, pptHeightCm, width, height); %% 计算像素位置并创建结果画布 [pixelPositions, minPos, maxPos] = computeCanvasSize(layerPositions, scaleFactor, width, height); canvasWidth = ceil(maxPos(1) - minPos(1) + width); canvasHeight = ceil(maxPos(2) - minPos(2) + height); % 创建带透明通道的结果图像 fullImage = zeros(canvasHeight, canvasWidth, numChannels, 'like', img1); alphaCanvas = zeros(canvasHeight, canvasWidth, 'single'); weightAccumulator = zeros(canvasHeight, canvasWidth, 'single'); fprintf('当前层图像数: %d\n', numel(layerFiles)); fprintf('画布尺寸: %dx%d 像素\n', canvasWidth, canvasHeight); %% 逐图像处理并拼接 for i = 1:numel(layerFiles) % 读取当前图像 imgPath = fullfile(imageDir, layerFiles(i).name); currentImg = imread(imgPath); % 确保所有图像尺寸一致 if any(size(currentImg) ~= [height, width, numChannels]) currentImg = imresize(currentImg, [height, width]); end % 处理无效区域 [processedImg, alpha, validMask] = removeBlackBorders(currentImg); % 计算当前图像在画布上的位置 canvasX = round(pixelPositions(i, 1) - minPos(1)); canvasY = round(pixelPositions(i, 2) - minPos(2)); % 计算权重图 weightMap = calculateWeightMap(validMask); % 将图像放置在画布上 [fullImage, alphaCanvas, weightAccumulator] = intensityBlend(... fullImage, alphaCanvas, weightAccumulator, ... processedImg, alpha, weightMap, canvasX, canvasY); % 进度显示 if mod(i, 10) == 0 fprintf('已处理 %d/%d (%.1f%%)\n', ... i, numel(layerFiles), i/numel(layerFiles)*100); end end %% 应用最终融合处理 fullImage = applyFinalBlend(fullImage, weightAccumulator); %% 转换alpha通道 alphaCanvasUint8 = im2uint8(alphaCanvas); %% 保存带Z坐标的拼接结果 outputPath = fullfile(imageDir, sprintf('Z=%.3f_stitched.png', currentZ)); imwrite(fullImage, outputPath, 'Alpha', alphaCanvasUint8); fprintf('拼接完成! 结果保存至: %s\n', outputPath); end end %% 辅助函数 - 解析包含Z坐标的图像文件 function [imageFiles, positions, zValues] = parseImageFilesWithZ(imageDir) files = dir(fullfile(imageDir, '*.png')); if isempty(files) files = dir(fullfile(imageDir, '*.jpg')); end positions = zeros(numel(files), 2); zValues = zeros(numel(files), 1); for i = 1:numel(files) filename = files(i).name; % 增强正则表达式匹配 X,Y,Z tokens = regexp(filename, 'X=([\d\.\-]+)[,_\s]Y=([\d\.\-]+)[,_\s]Z=([\d\.\-]+)', 'tokens'); if ~isempty(tokens) x = str2double(tokens{1}{1}); y = str2double(tokens{1}{2}); z = str2double(tokens{1}{3}); positions(i, :) = [x, y]; zValues(i) = z; else tokens = regexp(filename, '([\d\.\-]+)_([\d\.\-]+)_([\d\.\-]+)', 'tokens'); if ~isempty(tokens) && numel(tokens{1}) >= 3 x = str2double(tokens{1}{1}); y = str2double(tokens{1}{2}); z = str2double(tokens{1}{3}); positions(i, :) = [x, y]; zValues(i) = z; else % 使用文件名序号作为位置 positions(i, :) = [i*10, i*10]; zValues(i) = 0; fprintf('警告: 无法解析位置,使用默认值: %s\n', filename); end end end imageFiles = files; end %% 基于强度融合的核心函数 function [canvas, alphaCanvas, weightAccumulator] = intensityBlend(... canvas, alphaCanvas, weightAccumulator, img, alpha, weightMap, x, y) % 获取尺寸信息 [imgH, imgW, imgC] = size(img); [canvasH, canvasW, canvasC] = size(canvas); % 计算有效区域 x1 = max(1, x); y1 = max(1, y); x2 = min(canvasW, x + imgW - 1); y2 = min(canvasH, y + imgH - 1); % 计算图像中对应的区域 imgX1 = max(1, 1 - (x - x1)); imgY1 = max(1, 1 - (y - y1)); imgX2 = min(imgW, imgW - ((x + imgW - 1) - x2)); imgY2 = min(imgH, imgH - ((y + imgH - 1) - y2)); % 提取画布区域 canvasRegion = canvas(y1:y2, x1:x2, :); alphaRegion = alphaCanvas(y1:y2, x1:x2); weightRegion = weightAccumulator(y1:y2, x1:x2); % 提取图像区域 imgRegion = img(imgY1:imgY2, imgX1:imgX2, :); alphaImg = alpha(imgY1:imgY2, imgX1:imgX2); weightImg = weightMap(imgY1:imgY2, imgX1:imgX2); % 验证尺寸一致性 regionH = size(canvasRegion, 1); regionW = size(canvasRegion, 2); if size(imgRegion, 1) ~= regionH || size(imgRegion, 2) ~= regionW % 调整图像区域尺寸以匹配画布区域 imgRegion = imresize(imgRegion, [regionH, regionW]); alphaImg = imresize(alphaImg, [regionH, regionW]); weightImg = imresize(weightImg, [regionH, regionW]); end % 仅在有效区域应用融合(忽略透明区域) validMask = alphaImg > 0.01; % 处理每个通道 for ch = 1:canvasC srcCh = single(imgRegion(:, :, ch)); dstCh = single(canvasRegion(:, :, ch)); % 计算混合权重 weightSum = weightRegion + weightImg; weightSum(weightSum == 0) = 1; % 避免除以零 % 仅在有效区域应用基于强度的融合 blended = dstCh; idx = validMask; blended(idx) = (dstCh(idx) .* weightRegion(idx) + ... srcCh(idx) .* weightImg(idx)) ./ weightSum(idx); canvasRegion(:, :, ch) = blended; end % 更新透明通道(取最大值) alphaRegion = max(alphaRegion, alphaImg); % 更新权重累加器 weightRegion = max(weightRegion, weightImg); % 更新画布 canvas(y1:y2, x1:x2, :) = canvasRegion; alphaCanvas(y1:y2, x1:x2) = alphaRegion; weightAccumulator(y1:y2, x1:x2) = weightRegion; end %% 计算权重图 - 基于有效区域的距离变换 function weightMap = calculateWeightMap(validMask) % 计算距离变换:距离有效区域中心越近,权重越高 distMap = bwdist(~validMask); % 距离边缘的距离 % 高斯滤波器平滑权重 weightMap = imgaussfilt(single(distMap), 5); % 标准偏差为5 % 归一化权重(0-1范围) weightMap = weightMap / max(weightMap(:)); % 设置无效区域权重为0 weightMap(~validMask) = 0; end %% 修复后的最终融合处理函数 function blendedImage = applyFinalBlend(image, weightAccumulator) % 创建掩码:只处理重叠区域(权重>1的区域) overlapMask = weightAccumulator > 1.3; % 如果没有重叠区域,直接返回 if ~any(overlapMask(:)) blendedImage = image; return; end % 转换为双精度以进行高质量处理 doubleImage = im2double(image); blendedResult = doubleImage; % 复制原始图像作为基准 % 获取图像尺寸 [h, w, numChannels] = size(doubleImage); % 创建三维重叠掩码 overlapMask3D = repmat(overlapMask, [1, 1, numChannels]); % 分离处理每个通道 for ch = 1:numChannels % 提取当前通道 channelData = doubleImage(:, :, ch); % 应用导向滤波(边缘感知平滑) smoothedChannel = imguidedfilter(... channelData, ... 'NeighborhoodSize', [15 15], ... 'DegreeOfSmoothing', 0.01); % 仅在重叠区域应用平滑结果 blendedChannel = channelData; blendedChannel(overlapMask) = smoothedChannel(overlapMask); blendedResult(:, :, ch) = blendedChannel; end % 转换回原始数据类型 if isa(image, 'uint8') blendedImage = im2uint8(blendedResult); elseif isa(image, 'uint16') blendedImage = im2uint16(blendedResult); elseif isa(image, 'single') blendedImage = im2single(blendedResult); else blendedImage = blendedResult; end end %% 辅助函数 - 最终结果显示 function showResultImage(fullImage, alphaCanvas) % 计算缩放比例以适合屏幕显示 screenSize = get(0, 'ScreenSize'); maxWidth = screenSize(3) * 0.8; maxHeight = screenSize(4) * 0.8; [h, w, ~] = size(fullImage); scale = min([maxWidth/w, maxHeight/h, 1]); if scale < 1 smallImg = imresize(fullImage, scale); smallAlpha = imresize(alphaCanvas, scale); else smallImg = fullImage; smallAlpha = alphaCanvas; end % 创建图形窗口 fig = figure('Name', '拼接结果', 'NumberTitle', 'off', ... 'CloseRequestFcn', @closeWindowCallback); % 显示图像 if size(fullImage, 3) == 1 % 灰度图像 hImg = imshow(smallImg, 'Border', 'tight'); set(hImg, 'AlphaData', smallAlpha); else % RGB图像 hImg = imshow(smallImg, 'Border', 'tight'); set(hImg, 'AlphaData', smallAlpha); end % 添加帮助信息 uicontrol('Style', 'text', ... 'Position', [10 10 300 30], ... 'String', '提示: 按任意键关闭窗口或点击右上角X按钮', ... 'BackgroundColor', [1 1 1 0.7], ... 'FontSize', 10); pan on; zoom on; % 设置键盘回调 set(fig, 'KeyPressFcn', @keyPressCallback); % 等待用户操作(按键或关闭窗口) waitfor(fig); %% 嵌套回调函数 function closeWindowCallback(src, ~) delete(src); end function keyPressCallback(~, ~) delete(fig); end end %% 辅助函数 - 移除黑色边框(返回有效区域掩码) function [processedImg, alpha, validMask] = removeBlackBorders(img) [h, w, c] = size(img); % 创建透明度通道 alpha = ones(h, w, 'single'); validMask = true(h, w); % 检测无效区域(黑色边框) if c == 3 grayImg = rgb2gray(img); else grayImg = img; end % 自适应阈值检测黑色区域 threshold = graythresh(grayImg) * 50; blackMask = grayImg < threshold; % 形态学操作去除噪点 se = strel('disk', 7); cleanedMask = imclose(blackMask, se); % 创建圆形蒙版 [x, y] = meshgrid(1:w, 1:h); centerX = w/2; centerY = h/2; radius = min(w, h) * 0.55; circleMask = ((x - centerX).^2 + (y - centerY).^2) <= (radius^2); % 组合蒙版:黑色区域或圆形区域外为透明 invalidMask = cleanedMask | ~circleMask; alpha(invalidMask) = 0; validMask(invalidMask) = false; processedImg = img; end %% 辅助函数 - 计算画布大小 function [pixelPositions, minPos, maxPos] = computeCanvasSize(positions, scaleFactor, imgW, imgH) % 坐标系转换 pixelX = positions(:,2) * scaleFactor; pixelY = -positions(:,1) * scaleFactor; % 计算左上角位置 topLeftX = pixelX - imgW/2; topLeftY = pixelY - imgH/2; pixelPositions = [topLeftX, topLeftY]; % 计算画布边界 minPos = min(pixelPositions); maxPos = max(pixelPositions) + [imgW, imgH]; % 添加安全边界 border = 50; minPos = minPos - border; maxPos = maxPos + border; end %% 辅助函数 - 计算缩放因子 function scaleFactor = calculateScaleFactor(pptW, pptH, imgW, imgH) physicalWidthUm = pptW * 10000; % cm→μm转换 physicalHeightUm = pptH * 10000; pxWidth = physicalWidthUm / imgW; pxHeight = physicalHeightUm / imgH; positionUnitUm = 10000; % 1单位位置 = 10,000μm = 1cm scaleX = positionUnitUm / pxWidth; scaleY = positionUnitUm / pxHeight; scaleFactor = (scaleX + scaleY)/2; fprintf('自动缩放因子:水平=%.4f, 垂直=%.4f, 使用=%.4f px/单位\n', ... scaleX, scaleY, scaleFactor); end %% 辅助函数 - 解析图像文件名 function [imageFiles, positions] = parseImageFiles(imageDir) files = dir(fullfile(imageDir, '*.png')); if isempty(files) files = dir(fullfile(imageDir, '*.jpg')); end positions = zeros(numel(files), 2); for i = 1:numel(files) filename = files(i).name; % 更健壮的正则表达式匹配 tokens = regexp(filename, 'X=([\d\.\-]+)[,_\s]Y=([\d\.\-]+)', 'tokens'); if ~isempty(tokens) x = str2double(tokens{1}{1}); y = str2double(tokens{1}{2}); positions(i, :) = -[x, y]; else % 尝试其他模式 tokens = regexp(filename, '([\d\.\-]+)_([\d\.\-]+)', 'tokens'); if ~isempty(tokens) && numel(tokens{1}) >= 2 x = str2double(tokens{1}{1}); y = str2double(tokens{1}{2}); positions(i, :) = -[x, y]; else % 使用文件名序号作为位置 positions(i, :) = [i*10, i*10]; fprintf('警告: 无法解析位置,使用默认值: %s\n', filename); end end end imageFiles = files; end 将这一程序打包成一个整体,并为其设计一个软件界面
09-23
我不要你精简我就要你按照你觉得需要修改的地方对:classdef CarMotionDetectorApp < matlab.apps.AppBase % 主应用程序类 - 基于光流场的汽车运动检测系统 properties (Access = public) % UI组件 UIFigure matlab.ui.Figure UIAxes matlab.ui.control.UIAxes LoadVideoButton matlab.ui.control.Button ProcessButton matlab.ui.control.Button ThresholdSlider matlab.ui.control.Slider ThresholdLabel matlab.ui.control.Label StatusLabel matlab.ui.control.Label FrameRateEdit matlab.ui.control.NumericEditField FrameRateLabel matlab.ui.control.Label SaveResultsButton matlab.ui.control.Button % 数据处理相关 VideoReader % 视频读取对象 VideoPath char = '' % 视频路径 FlowObj % 光流对象(修复:移除初始类型定义) FlowThreshold double = 0.2 % 光流幅值阈值 IsProcessing logical = false % 处理状态标志 OutputVideoWriter % 输出视频写入对象 end methods (Access = private) % 加载视频按钮回调 function onLoadVideoButtonPushed(app, ~) [file, path] = uigetfile({'*.mp4;*.avi;*.mov','视频文件 (*.mp4, *.avi, *.mov)'}, '选择视频文件'); if isequal(file,0) return; % 用户取消选择 end app.VideoPath = fullfile(path, file); try app.VideoReader = VideoReader(app.VideoPath); % 显示第一帧 if hasFrame(app.VideoReader) frame = readFrame(app.VideoReader); imshow(frame, 'Parent', app.UIAxes); title(app.UIAxes, '视频第一帧'); app.StatusLabel.Text = '视频加载完成,点击"开始处理"'; % 重置视频读取器 app.VideoReader = VideoReader(app.VideoPath); end % 初始化光流对象(关键修正) app.FlowObj = opticalFlowFarneback; app.FlowObj.PyramidScale = 0.5; app.FlowObj.NumPyramidLevels = 3; app.FlowObj.FilterSize = 15; app.FlowObj.NumIterations = 3; app.FlowObj.NeighborhoodSize = ceil(1.2 * 4); catch ME uialert(app.UIFigure, sprintf('视频加载失败: %s', ME.message), '错误'); end end % 处理按钮回调 function onProcessButtonPushed(app, ~) if isempty(app.VideoReader) || isempty(app.VideoPath) uialert(app.UIFigure, '请先加载视频文件', '未选择视频'); return; end % 切换处理状态 if ~app.IsProcessing app.IsProcessing = true; app.ProcessButton.Text = '停止处理'; app.LoadVideoButton.Enable = 'off'; app.SaveResultsButton.Enable = 'off'; processVideo(app); else app.IsProcessing = false; app.ProcessButton.Text = '开始处理'; app.LoadVideoButton.Enable = 'on'; app.SaveResultsButton.Enable = 'on'; app.StatusLabel.Text = '处理已停止'; end end % 保存结果按钮回调 function onSaveResultsButtonPushed(app, ~) if isempty(app.VideoReader) || isempty(app.VideoPath) uialert(app.UIFigure, '请先加载并处理视频', '无处理结果'); return; end [file, path] = uiputfile({'*.avi','AVI 视频文件 (*.avi)'}, '保存结果视频'); if isequal(file,0) return; % 用户取消保存 end outputPath = fullfile(path, file); try % 创建输出视频写入器 app.OutputVideoWriter = VideoWriter(outputPath, 'Motion JPEG AVI'); app.OutputVideoWriter.FrameRate = app.VideoReader.FrameRate; open(app.OutputVideoWriter); % 重置视频读取器和光流对象 app.VideoReader = VideoReader(app.VideoPath); reset(app.FlowObj); % 重置光流状态 app.IsProcessing = true; app.ProcessButton.Enable = 'off'; app.LoadVideoButton.Enable = 'off'; app.SaveResultsButton.Enable = 'off'; app.StatusLabel.Text = '正在保存结果...'; % 处理第一帧初始化 if hasFrame(app.VideoReader) frame = readFrame(app.VideoReader); currentGray = app.convertToGray(frame); estimateFlow(app.FlowObj, currentGray); % 初始化光流 end frameCount = 0; % 处理并保存每一帧 while hasFrame(app.VideoReader) && app.IsProcessing frame = readFrame(app.VideoReader); frameCount = frameCount + 1; currentGray = app.convertToGray(frame); % 计算光流(使用预初始化的对象) flowVectors = estimateFlow(app.FlowObj, currentGray); Vx = flowVectors.Vx; Vy = flowVectors.Vy; magnitude = sqrt(Vx.^2 + Vy.^2); motionMask = magnitude > app.FlowThreshold; % 可视化 imshow(frame, 'Parent', app.UIAxes); hold(app.UIAxes, 'on'); % 绘制运动区域(红色半透明) redMask = cat(3, ones(size(motionMask)), zeros(size(motionMask)), zeros(size(motionMask))); h = imshow(redMask, 'Parent', app.UIAxes); set(h, 'AlphaData', 0.3 * motionMask); % 绘制光流向量(采样绘制) [h, w] = size(magnitude); [X, Y] = meshgrid(1:10:w, 1:10:h); U = Vx(1:10:h, 1:10:w); V = Vy(1:10:h, 1:10:w); quiver(app.UIAxes, X(:), Y(:), U(:), V(:), 2, 'Color', 'g', 'LineWidth', 1); hold(app.UIAxes, 'off'); title(app.UIAxes, '汽车运动检测(红色区域为运动区域)'); % 获取当前帧图像并写入视频 frameWithOverlay = getframe(app.UIAxes); writeVideo(app.OutputVideoWriter, frameWithOverlay.cdata); % 每10帧释放内存(性能优化) if mod(frameCount, 10) == 0 drawnow; clear frameWithOverlay redMask motionMask magnitude Vx Vy; end app.StatusLabel.Text = sprintf('保存中... 帧: %d', frameCount); drawnow limitrate; % 优化刷新性能 end close(app.OutputVideoWriter); app.IsProcessing = false; app.ProcessButton.Enable = 'on'; app.LoadVideoButton.Enable = 'on'; app.SaveResultsButton.Enable = 'on'; app.StatusLabel.Text = sprintf('结果已保存至: %s', outputPath); catch ME uialert(app.UIFigure, sprintf('保存失败: %s', ME.message), '错误'); app.IsProcessing = false; app.ProcessButton.Enable = 'on'; app.LoadVideoButton.Enable = 'on'; app.SaveResultsButton.Enable = 'on'; if ~isempty(app.OutputVideoWriter) && isvalid(app.OutputVideoWriter) close(app.OutputVideoWriter); end end end % 视频处理主函数 function processVideo(app) % 重置视频读取器和光流对象 app.VideoReader = VideoReader(app.VideoPath); reset(app.FlowObj); % 重置光流状态 % 获取帧率控制值 frameRate = app.FrameRateEdit.Value; if frameRate <= 0 frameRate = app.VideoReader.FrameRate; end pauseTime = 1/frameRate; % 处理第一帧初始化 if hasFrame(app.VideoReader) frame = readFrame(app.VideoReader); currentGray = app.convertToGray(frame); estimateFlow(app.FlowObj, currentGray); % 初始化光流 imshow(frame, 'Parent', app.UIAxes); title(app.UIAxes, '视频第一帧(已初始化)'); end frameCount = 0; % 循环处理每一帧 while hasFrame(app.VideoReader) && app.IsProcessing frameCount = frameCount + 1; % 读取当前帧 frame = readFrame(app.VideoReader); % 转换为灰度图像(版本兼容) currentGray = app.convertToGray(frame); % 计算光流(使用预初始化的对象) flowVectors = estimateFlow(app.FlowObj, currentGray); % 获取光流向量 Vx = flowVectors.Vx; Vy = flowVectors.Vy; magnitude = sqrt(Vx.^2 + Vy.^2); % 光流幅值 % 检测运动区域(超过阈值的区域) motionMask = magnitude > app.FlowThreshold; % 可视化 imshow(frame, 'Parent', app.UIAxes); hold(app.UIAxes, 'on'); % 绘制运动区域(红色半透明) redMask = cat(3, ones(size(motionMask)), zeros(size(motionMask)), zeros(size(motionMask))); h = imshow(redMask, 'Parent', app.UIAxes); set(h, 'AlphaData', 0.3 * motionMask); % 绘制光流向量(采样绘制) [h, w] = size(magnitude); [X, Y] = meshgrid(1:10:w, 1:10:h); U = Vx(1:10:h, 1:10:w); V = Vy(1:10:h, 1:10:w); quiver(app.UIAxes, X(:), Y(:), U(:), V(:), 2, 'Color', 'g', 'LineWidth', 1); hold(app.UIAxes, 'off'); title(app.UIAxes, '汽车运动检测(红色区域为运动区域)'); % 更新状态 app.StatusLabel.Text = sprintf('处理中... 当前帧: %d', frameCount); % 每10帧释放内存(性能优化) if mod(frameCount, 10) == 0 drawnow; clear redMask motionMask magnitude Vx Vy; end % 控制处理速度 pause(pauseTime); end % 处理完成或停止 if app.IsProcessing app.IsProcessing = false; app.ProcessButton.Text = '开始处理'; app.LoadVideoButton.Enable = 'on'; app.SaveResultsButton.Enable = 'on'; app.StatusLabel.Text = '处理完成!'; end end % 灰度转换(版本兼容) function gray = convertToGray(~, frame) % 检查MATLAB版本 if verLessThan('matlab', '9.8') % R2020a之前版本 gray = rgb2gray(frame); else gray = im2gray(frame); end end % 阈值滑动条回调 function onThresholdSliderValueChanged(app, ~) app.FlowThreshold = app.ThresholdSlider.Value; app.ThresholdLabel.Text = sprintf('运动阈值: %.2f', app.FlowThreshold); end % 帧率编辑框回调 function onFrameRateEditValueChanged(app, ~) frameRate = app.FrameRateEdit.Value; if frameRate <= 0 app.FrameRateEdit.Value = app.VideoReader.FrameRate; end end end methods (Access = private) % 创建UI组件 function createComponents(app) % 创建主窗口 app.UIFigure = uifigure('Name', '基于光流场的汽车运动检测系统', ... 'Position', [100 100 900 650]); % 创建坐标轴 app.UIAxes = uiaxes(app.UIFigure); app.UIAxes.Position = [50, 180, 800, 450]; app.UIAxes.XTick = []; app.UIAxes.YTick = []; title(app.UIAxes, '视频显示'); % 创建加载视频按钮 app.LoadVideoButton = uibutton(app.UIFigure, 'push', ... 'Position', [50, 130, 100, 30], ... 'Text', '加载视频', ... 'ButtonPushedFcn', @(src, event) onLoadVideoButtonPushed(app, event)); % 创建处理按钮 app.ProcessButton = uibutton(app.UIFigure, 'push', ... 'Position', [170, 130, 100, 30], ... 'Text', '开始处理', ... 'ButtonPushedFcn', @(src, event) onProcessButtonPushed(app, event)); % 创建保存结果按钮 app.SaveResultsButton = uibutton(app.UIFigure, 'push', ... 'Position', [290, 130, 100, 30], ... 'Text', '保存结果', ... 'Enable', 'off', ... 'ButtonPushedFcn', @(src, event) onSaveResultsButtonPushed(app, event)); % 创建阈值滑动条 app.ThresholdSlider = uislider(app.UIFigure, ... 'Position', [420, 150, 200, 3], ... 'Limits', [0.05, 1], ... 'Value', app.FlowThreshold, ... 'ValueChangedFcn', @(src, event) onThresholdSliderValueChanged(app, event)); % 创建阈值标签 app.ThresholdLabel = uilabel(app.UIFigure, ... 'Position', [420, 120, 200, 22], ... 'Text', sprintf('运动阈值: %.2f', app.FlowThreshold)); % 创建帧率标签 app.FrameRateLabel = uilabel(app.UIFigure, ... 'Position', [650, 120, 80, 22], ... 'Text', '处理帧率:'); % 创建帧率编辑框 app.FrameRateEdit = uieditfield(app.UIFigure, 'numeric', ... 'Position', [730, 120, 60, 22], ... 'Value', 10, ... 'Limits', [1, 60], ... 'ValueChangedFcn', @(src, event) onFrameRateEditValueChanged(app, event)); % 创建状态标签 app.StatusLabel = uilabel(app.UIFigure, ... 'Position', [50, 80, 800, 22], ... 'Text', '请加载视频文件'); end end methods (Access = public) % 构造函数 function app = CarMotionDetectorApp % 创建UI组件 createComponents(app); % 初始化光流对象(避免空数组错误) app.FlowObj = []; % 初始化为空,稍后实例化 end end end这段代码进行修改和增加高分项内容(结合之前的对话)
07-04
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值