课程组成:Shell、FFmpeg、OpenCV
- Shell脚本 作用:主要用于交叉编译,编译出移动端可用的 FFmpeg / OpenCV 二进制库(
.a静态库、.so动态库)。 不需要精通Shell全部语法,重点看懂编译脚本、configure配置、多平台编译逻辑,能修改编译参数即可。 - FFmpeg 作用:音视频编解码核心。负责读取音视频文件、解封装、解码,输出原始音视频帧数据(视频输出YUV帧,音频输出PCM);也可以把处理完成的原始帧重新编码、封装,输出成MP4等视频文件。 关注点:解封装‑解码‑渲染‑编码‑封装完整流程、
pts/dts时间戳、音视频同步。 - OpenCV 作用:图像算法处理,做滤镜、像素运算、轮廓检测等。 工作模式:接收FFmpeg解码出来的每一帧图像,做单帧图像处理;处理完成后,再把帧交还给FFmpeg进行编码导出。 核心数据结构:
cv::Mat,C++对象,OC不能直接调用,需要OC++混编。
完整数据流链路 视频文件 → FFmpeg解封装解码 → YUV原始帧 →(格式转换YUV→BGR)→ OpenCV做滤镜/图像处理 → 转回YUV → FFmpeg编码封装 → 输出新视频文件
openCV 实现图片的马赛克
C++前置知识。
arduino
// opencv的UIImage对象,对比UIImage,cv::Mat可以拿到每一个像素点
cv::Mat cvImage;
#import <opencv2/opencv.hpp>
cv::Mat cvImage; // 可以直接用命名空间+类名
// Mat cvImage3; 编译报错
using namespace cv; // 使用命名空间后,可以直接使用类名。
Mat cvImage2;
从opencv.org下载一个opencv2.framework。
ImageUtils.h
objectivec
#import <UIKit/UIKit.h>
@interface ImageUtils : NSObject
//定义方法:处理图片
+ (UIImage *)mosaicImage:(UIImage *)image level:(int)level;
@end
ImageUtils.mm 有C++文件,不能直接用.m后缀
这里为了强调哪些是opencv的类,没有使用命名空间。
arduino
#import "ImageUtils.h"
#import <opencv2/opencv.hpp>
#import <opencv2/imgcodecs/ios.h>
@implementation ImageUtils
+ (UIImage *)mosaicImage:(UIImage *)image level:(int)level{
cv::Mat cvImage;
// 将OC对象的UIImage转opencv格式的Mat对象
UIImageToMat(image, cvImage);
// 这个cv::Mat可以拿到宽度和高度
int width = cvImage.cols; // cols 列 = 宽度
int height = cvImage.rows; // rows 行 = 高度
NSLog(@"图片的宽是%d 高是%d",width,height);
// iOS 的 UIImage 转 Mat 之后,得到的是 RGBA4 通道,带透明度 Alpha
// CV_RGBA2RGB:将带透明通道的RGBA转成RGB的Mat对象
// 最后一个参数dstCn如果设置0,就会根据前面code自动推断输出通道;也可以写3
// 如果写4,会强制输出4通道,Alpha通道值填充为0
//
cv::Mat cvImage2;
cv::cvtColor(cvImage, cvImage2, CV_RGBA2RGB, 0);
// 开始进行马赛克
// x:列,横向遍历
for (int x=0; x < width-level; x+=level) {
// y:行,纵向遍历
for (int y=0; y<height-level; y+= level) {
// 创建一个矩形区域
// Rect2i
// React表示矩形
// 2 表示二维
// i 代表int整数类型,像素坐标
// 参数分别表示(左上角的X坐标,左上角的y坐标,右下角的x坐标,右下角的y坐标)
cv::Rect2i mosaicRect = cv::Rect2i(x, y, level, level);
// 从RGB的图像中取出这部分矩形的ROI区域
// 这里是浅拷贝,只生成新Mat头,像素数据和cvImage2共用同一块内存
// 修改roi的像素,会直接修改cvImage2原图的像素
cv::Mat roi = cvImage2(mosaicRect);
// 读取方块左上角(y行,x列)像素点的RGB值
// cv::Scalar是opencv的颜色容器,用来保存颜色分量
// ⚠️ .at<Vec3b>(行号y, 列号x),行在前,列在后!!
cv::Scalar scalar = cv::Scalar(cvImage2.at<cv::Vec3b>(y,x)[0],
cvImage2.at<cv::Vec3b>(y,x)[1],
cvImage2.at<cv::Vec3b>(y,x)[2]);
// 新建一张和马赛克方块一样大小的纯色图像,整块全部填充 scalar 指定的颜色。
// 8U:8位无符号,取值0~255
// C3:3个通道,RGB三通道
cv::Mat roiCopy = cv::Mat(mosaicRect.size(), CV_8UC3, scalar);
// 将这个小矩形的图片全部填充为这个纯色的图片
roiCopy.copyTo(roi);
}
}
return MatToUIImage(cvImage2);
}