数字图像处理-实验3
实验3:几何变换与变形
实验3.1:电子哈哈镜
设计变换函数对实时视频进行变形,生成哈哈镜的效果。
采用双线性插值进行图像重采样
采用cv::VideoCapture读取摄像头视频,并进行实时处理和显示结果。
优化代码执行效率,改善实时性(不要忘了打开编译优化,vc请用release模式编译)。
录制一个视频和实验报告一起提交,不用太长,大小不要超过10M(可以采用cv::VideoWriter保存视频,太大可以用格式工厂等进行压缩)。
考虑要如何设计变换函数,由于要实现的是凸透镜的效果,因此需要让原图像的每个像素都向外扩张,并且越靠内的像素扩张的效果还要越明显。
查资料找到了一个凸透镜效果的变换公式:
其中 O 表示图像的中心点,R 表示图像中心到图像左上角的距离,r 表示图像中心到 (x,y) 坐标的距离,dx 和 dy 分别表示图像中心到 (x,y) 坐标的横、纵坐标之差。
又通过查阅资料得知,双线性插值是一种根据距离某个坐标最近的四个像素点计算该点的值的算法,根据其定义可写出实现双线性插值的函数:
cv::Vec3b bilinear_interpolation(const cv::Mat& image,double x,double y){
if(x<0) x=0;
if(x>image.rows-1) x=image.rows-1;
if(y<0) y=0;
if(x>image.cols-1) y=image.cols-1;
int x1=(int)x,x2=x1+1;
int y1=(int)y,y2=y1+1;
if(x2>=image.rows) x2=image.rows-1;
if(y2>=image.cols) y2=image.cols-1;
cv::Vec3b p1,p2,p3,p4; //四个临近点
p1=image.at<cv::Vec3b>(x1,y1);
p2=image.at<cv::Vec3b>(x2,y1);
p3=image.at<cv::Vec3b>(x1,y2);
p4=image.at<cv::Vec3b>(x2,y2);
cv::Vec3b p12=p1+(p2-p1)*(x-x1); //第一次差值
cv::Vec3b p34=p3+(p4-p3)*(x-x1); //第二次差值
return p12+(p34-p12)*(y-y1); //最终差值
}
对图像应用凸透镜效果的函数如下:
cv::Mat& transform(const cv::Mat &src){
static cv::Mat dst(src.rows,src.cols,src.type());
int Ox=src.rows*0.5,Oy=src.cols*0.5;
double R=std::sqrt(Ox*Ox+Oy*Oy);
for(int x=0;x<src.rows;++x){
for(int y=0;y<src.cols;++y){
double dx=x-Ox,dy=y-Oy;
double r=std::sqrt(dx*dx+dy*dy);
double nx=Ox+dx*(r/R),ny=Oy+dy*(r/R);
dst.at<cv::Vec3b>(x,y)=bilinear_interpolation(src,nx,ny);
}
}
return dst;
}
完整的代码如下:
查看代码
cv::Vec3b bilinear_interpolation(const cv::Mat& image,double x,double y){
if(x<0) x=0;
if(x>image.rows-1) x=image.rows-1;
if(y<0) y=0;
if(x>image.cols-1) y=image.cols-1;
int x1=(int)x,x2=x1+1;
int y1=(int)y,y2=y1+1;
if(x2>=image.rows) x2=image.rows-1;
if(y2>=image.cols) y2=image.cols-1;
cv::Vec3b p1,p2,p3,p4; //四个临近点
p1=image.at<cv::Vec3b>(x1,y1);
p2=image.at<cv::Vec3b>(x2,y1);
p3=image.at<cv::Vec3b>(x1,y2);
p4=image.at<cv::Vec3b>(x2,y2);
cv::Vec3b p12=p1+(p2-p1)*(x-x1); //第一次差值
cv::Vec3b p34=p3+(p4-p3)*(x-x1); //第二次差值
return p12+(p34-p12)*(y-y1); //最终差值
}
cv::Mat& transform(const cv::Mat &src){
static cv::Mat dst(src.rows,src.cols,src.type());
int Ox=src.rows*0.5,Oy=src.cols*0.5;
double R=std::sqrt(Ox*Ox+Oy*Oy);
for(int x=0;x<src.rows;++x){
for(int y=0;y<src.cols;++y){
double dx=x-Ox,dy=y-Oy;
double r=std::sqrt(dx*dx+dy*dy);
double nx=Ox+dx*(r/R),ny=Oy+dy*(r/R);
dst.at<cv::Vec3b>(x,y)=bilinear_interpolation(src,nx,ny);
}
}
return dst;
}
int main(int argc,char *argv[]){
cv::VideoCapture cap(0);
if(!cap.isOpened()){
std::cout<<"Can't Open Capture!"; return 0;
}
cv::Mat frame;
//输出视频的设置
cv::Size frameSize((int)cap.get(cv::CAP_PROP_FRAME_WIDTH),(int)cap.get(cv::CAP_PROP_FRAME_HEIGHT));
cv::VideoWriter writer("output_video.mp4",cv::VideoWriter::fourcc('M','P','4','V'),24,frameSize,true); //保存24帧的视频
if(!writer.isOpened()){
std::cerr<<"Can't Open File!"; return 0;
}
while(1){
cap>>frame;
if(frame.empty()) break;
auto res=transform(frame);
writer<<res;
imshow("output",res);
if(cv::waitKey(1)==27) break; //Press ESC to Exit
}
cap.release();
writer.release();
cv::destroyAllWindows();
return 0;
}
录制的演示视频(由于视频较小,这里直接压缩成 GIF 上传):
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现