【PHP】控制摄像头缩放监控画面大小,并保存可视画面为图片

一、前言

功能描述

调用摄像头并可以控制缩放摄像头监控画面的大小,把可视画面保存为图片。

我使用的是USB摄像头,其他摄像头此方法应该也通用。

使用技术

使用到的技术比较简单,前端使用WebcamJS插件调用摄像头,并摄像头监控画面,使用CSS的transform属性来放大或缩小画面的大小。后端使用PHP的Imagick类库来缩放图片,最终保存图片到本地。

二、最终效果

Capture:保存可视区域图片;放大:放大可视区域画面;缩小:缩小可视区域画面;还原:还原可视区域画面。

三、业务逻辑说明

前端

① 调用摄像头:使用WebcamJS库调用摄像头,显示监控画面(注意:需要在https下才能使用此插件);

② 缩放画面:先初始可视区域的宽高,设置scale缩放比例,再使用CSS的transform属性来缩放画面大小(注意:transform属性只是在视觉上对图片进行了缩放,其实和图像的真实大小并不一致,因为我这里为了保持放大后的图像尺寸大小和原始图像尺寸大小在视觉上一致,所以我把多余的画面区域使用overflow隐藏了);

③ 数据传输后端:把图片数据和缩放比例传输给PHP后端进行处理。此时传输的图像数据是base64编码,尺寸大小并不是视觉上所看到的画面大小,这个需要在后端进行处理。

后端

① 接收前端传过来的数据(base64图片编码、缩放比例);

② 把接收的base64编码保存为图片到本地;

③ 对图片进行处理:使用Imagick类库对图像进行处理,前面说了前端传过来的图像尺寸并不是可视区域的尺寸,所以为了保存一致,需要对接收的原始图像进行缩放处理。需要两个步骤,先是根据放大比例计算并保存放大后的图片(是原始大小的图片,不是视觉大小的图片),再对该图片进行裁剪,裁剪方法是计算裁剪区域的起始坐标,这个坐标是从放大后图像的中心位置开始计算的,最后根据这个坐标和裁剪尺寸进行裁剪并保存新的图片。这样就得到了一个从放大后的图像中心为圆心,向外扩散裁剪;

④ 保存裁剪后的图片,进行后续操作。

四、代码

前端

<!doctype html>

<html lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>WebcamJS Test Page - Large Capture</title>
	<style type="text/css">
		body { font-family: Helvetica, sans-serif; }
		h2, h3 { margin-top:0; }
		form { margin-top: 15px; }
		form > input { margin-right: 15px; }

		#my_camera {  
		  transition: transform 0.3s ease-in-out; /* 添加过渡效果,使变换更平滑 */  
		  transform-origin: center center; /* 变换原点设置为图片中心 */  
		  width: 100%; /* 初始尺寸设置为容器宽度 */  
  			height: auto; /* 保持图片的宽高比 */  
		}
		#imageContainer{
			 width: 640px; /* 设置你想要的容器宽度 */  
			  height: 480px; /* 设置你想要的容器高度 */  
			  overflow: hidden; /* 隐藏超出容器的部分 */  
			  position: relative; /* 如果需要相对于容器定位图片,可以添加这个属性 */
		}

	</style>
</head>
<body>

	<h1>WebcamJS Test Page - Large/Small Capture</h1>
	
	<div id="imageContainer" style="">
		<div id="my_camera"></div>
	</div>
	
	<!-- A button for taking snaps -->
	<form>
		<input type=button value="Capture" onClick="take_snapshot()">
		<input type=button value="放大" id="zoomIn" >
		<input type=button value="缩小" id="zoomOut">
		<input type=button value="还原" id="reset">
	</form>
	
	<script type="text/javascript" src="/static/index/js/webcam.min.js"></script>
	<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.5.1.min.js"></script>
  
	<script language="JavaScript">
		Webcam.set({
			width: 640,
			height: 480,
			image_format: 'jpeg',
			jpeg_quality: 90
		});
		Webcam.attach( '#my_camera' );
	</script>

	<script language="JavaScript">
		function take_snapshot() {
			// take snapshot and get image data
			Webcam.snap( function(data_uri) {

				console.log(scale)
				var imgBase64 = data_uri
				$.ajax({
					url:'/index/camera/photo',
					dataType:'JSON',
					type:'POST',
					data:{data:imgBase64,scale:scale},
					success:function (res) {
						console.log(res)
					}
				})
			});
		}
		
		var scale = 1; // 初始缩放比例  
		$('#zoomIn').click(function() {  
		    if (scale < 3) { // 限制最大缩放比例,这里设置为3 
		      scale += 0.1; // 每次点击放大0.1  
		      $('#my_camera').css('transform', 'scale(' + scale + ')');  
		    }  

		    // 计算放大后的尺寸
		    var originalWidth = $("#my_camera").width();
			var originalHeight = $("#my_camera").height();

			var zoomedWidth = originalWidth * scale;  
      		var zoomedHeight = originalHeight * scale; 

		    console.log(zoomedWidth)
		    console.log(zoomedHeight)

		  });  
		  
		  $('#zoomOut').click(function() {  
		    if (scale > 1) { // 限制最小缩放比例,这里设置为1  
		      scale -= 0.1; // 每次点击缩小0.1  
		      $('#my_camera').css('transform', 'scale(' + scale + ')'); 
		    }  
		  });  

		  $('#reset').click(function() {  
		  	scale = 1
	      	$('#my_camera').css('transform', 'scale(' + scale + ')'); 
		  });  
		

	</script>
	
</body>
</html>

后端

// 处理图像
public function photo()
{
	
      $image= input('post.data');		//接收base64数据
      $scale= input('post.scale');	//接收缩放比例

       //图片存放的路径
      $path = "uploads".DS."chufang".DS.date('Y-m-d', time()) . DS;

      // 检查该目录是否存在,不存在就创建
      if (!file_exists($path)) {
        mkdir($path, 0700, true); //创建目录
        chmod($path, 0700); //赋予权限
      }

      $uid = session('uid');
      
      //确保图片名唯一,防止重名产生覆盖
      $imageName = 'wx_' .$uid.'_'. time() . rand(1000, 9000) . '.jpg';
      
      //判断是否有逗号 如果有就截取后半部分
      if (strstr($image,",")){
        $image = explode(',',$image);
        $image = $image[1];
      }
    
      //图片完整路径
      $imageSrc= ROOT_PATH .'public' .DS. $path . $imageName;

      // 生成文件夹和图片
      $r = file_put_contents($imageSrc, base64_decode($image));

      // 原始图像路径
      $sourceImagePath= $imageSrc;
      // 目标图像路径
      $targetImagePath = ROOT_PATH .'public' .DS. $path .'target.jpg';  

      // 实例化(原始图像路径)
	$image = new \Imagick($sourceImagePath);

	// 获取原始图像的尺寸  
	$originalWidth = $image->getImageWidth();  
	$originalHeight = $image->getImageHeight();  
	
	// 计算放大后图像的尺寸(这仅用于计算裁剪区域,最终图像尺寸保持不变)  
	$scaledWidth = $originalWidth * $scale;
	$scaledHeight = $originalHeight * $scale;

	// 保存放大后的图像  
	$image->resizeImage($scaledWidth, $scaledHeight, \Imagick::FILTER_LANCZOS, 1);  
		
	// 设定裁剪区域的尺寸(这里设置与原始图像一样大的尺寸)  
	$cropWidth = $originalWidth * 1; 	// 如果比原始宽度大50%,可*1.5  
	$cropHeight = $originalHeight * 1; // 如果比原始高度大50%,可*1.5 

	// 计算裁剪区域的起始坐标(从放大后图像的中心开始)  
	$startX = ($scaledWidth - $originalWidth) / 2;  
	$startY = ($scaledHeight - $cropHeight) / 2;  
	  
	// 确保起始坐标是整数(因为像素坐标必须是整数)  
	$startX = (int)round($startX);  
	$startY = (int)round($startY);  

	// 使用 Imagick 的 cropImage 方法裁剪图像  
	$image->cropImage($cropWidth, $cropHeight, $startX, $startY);  
	  
	// 保存新图像
	$image->writeImage($targetImagePath);  
	  
	// 清除资源  
	$image->clear();  
	$image->destroy();  

}

 五、资源下载

WebcamJS: jQuery移动端调用摄像头拍照插件WebcamJS

Imagick:PHP安装Imagick扩展库,需要安装两个,分别是imagick扩展、imagemagick软件,操作步骤可参考:PHP 7 安装 Imagick 扩展 以及一些使用注意事项 - 简书

完整代码下载:https://download.csdn.net/download/qq_25285531/89487658

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/760764.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java进阶学习|Day3.Java集合类(容器),Stream的使用,哈希初接触

java集合类&#xff08;容器&#xff09; Java中的集合类主要由Collection和Map这两个接口派生而出&#xff0c;其中Collection接口又派生出三个子接口&#xff0c;分别是Set、List、Queue。所有的Java集合类&#xff0c;都是Set、List、Queue、Map这四个接口的实现类&#xf…

VuePress日常使用

什么是主题&#xff1f;读者对主题这个单词应该不陌生&#xff0c;例如大部分 APP 都支持白天主题和夜晚主题等&#xff0c;使用者可以很轻松的切换主题等等。VuePress 中也有类似的概念。 ‍ VuePress 的主题 在 VuePress 中&#xff0c;主题不仅仅是外观上的不同&#xff…

#17前端后花园周刊-ECMAScript 2024、JS新set方法、TS5.5、理解React Compiler

⚡️行业动态 Ecma International 批准 ECMAScript 2024&#xff1a;有何新内容&#xff1f; Ecma 大会批准了最新的 ECMAScript / JavaScript 语言规范&#xff0c;正式使其成为标准。与 ECMAScript 2023 一样&#xff0c;这只是向前迈出的一小步&#xff0c;但 Axel 博士研…

详细配置SQL Server的链接服务器(图文操作Mysql数据库)

目录 前言1. MySQL ODBC 驱动2. 配置 SQL Server 链接服务器3. 彩蛋前言 此处配置以及安装没有什么理论知识 所以直奔主题,跟着以下步骤配置安装即可 需求:准备在10.197.0.110中链接外部的10.197.0.96的mysql数据源 已默认在10.197.0.96中安装了MySQL数据库并且知道其连接信…

Gradio 4.37.1官方教程三:Chatbot

文章目录 一、使用ChatInterface创建聊天机器人1.1 定义聊天函数1.2 流式聊天机器人&#xff08;Streaming chatbots&#xff09;1.3 自定义聊天机器人1.4 添加多模态功能1.5 通过additional_inputs添加额外组件1.6 直接添加 Gradio 组件1.7 通过 API 使用聊天机器人1.7.1 调用…

苹果可能与谷歌合作推AI订阅服务;全国首个司法审判大模型在深圳上线

&#x1f989; AI新闻 &#x1f680; 苹果可能与谷歌合作推AI订阅服务 摘要&#xff1a;苹果宣布将与OpenAI合作推出Apple Intelligence&#xff0c;并有望在今年秋季与谷歌达成合作&#xff0c;接入Gemini。Meta的Llama因质量不佳被拒。苹果计划推出订阅模式的智能功能服务&…

C语言--vs使用调试技巧

1.什么是bug? 1.产品说明书中规定要做的事情&#xff0c;而软件没有实现。 2.产品说明书中规定不要做的事情&#xff0c;而软件确实现了。 3.产品说明书中没有提到过的事情&#xff0c;而软件确实现了。 4.产品说明书中没有提到但是必须要做的事情&#xff0c;软件确没有实…

vue 组件el-tree添加结构指示线条

效果展示: 注意&#xff1a;组件中需要添加:indent"0" 进行子级缩进处理&#xff0c;否则会出现子级缩进逐级递增 :expand-on-click-node"false" 设置点击箭头图标才会展开或者收起 代码&#xff1a; <el-tree class"tree filter-tree" :da…

数据恢复篇:如何在电脑上恢复已删除和丢失的音乐文件

尽管流媒体网络非常流行&#xff0c;但许多人仍然选择将音乐下载并保存在 PC 本地。这会使文件面临丢失或意外删除的风险。 幸运的是&#xff0c;您可以使用数据恢复软件恢复已删除的音乐和其他文件类型。这篇文章讨论了这些解决方案以及如何使用奇客数据恢复检索丢失的音乐文…

【SpringCloud】Eureka源码解析 下

eurkea是一个服务发现与注册组件&#xff0c;它包含客户端和服务端&#xff0c;服务端负责管理服务的注册信息&#xff0c;客户端用于简化与服务端的交互。上一章分析了eureka的服务注册&#xff0c;这一章来分析eureka的心跳机制 参考源码&#xff1a;<spring-cloud.versi…

科普文:八大排序算法(JAVA实现)+ 自制动画 (袁厨的算法小屋)

我将我仓库里的排序算法给大家汇总整理了一下&#xff0c;写的非常非常细&#xff0c;还对每个算法制作了动画&#xff0c;一定能够对大家有所帮助&#xff0c;欢迎大家阅读。另外我也对 leetcode 上面可以用排序算法秒杀的算法题进行了总结&#xff0c;会在后面的文章中进行发…

OpenCV 调用自定义训练的 YOLO-V8 Onnx 模型

一、YOLO-V8 转 Onnx 在本专栏的前面几篇文章中&#xff0c;我们使用 ultralytics 公司开源发布的 YOLO-V8 模型&#xff0c;分别 Fine-Tuning 实验了 目标检测、关键点检测、分类 任务&#xff0c;实验后发现效果都非常的不错&#xff0c;但是前面的演示都是基于 ultralytics…

计算机组成原理——寄存器

文章目录 1. 寄存器 2. 带寄存器的加法器 3. 时钟信号与计算速度 1. 寄存器 上一篇D触发器可以在时钟上沿存储1位数据。如果想存储多个位&#xff08;bit&#xff09;的数据&#xff0c;就需要用多个D触发器并联实现&#xff0c;这种电路称之为寄存器。 寄存器是计算机中央…

MATLAB使用系统辨识工具箱建立PID水温的传递函数系数

概述 利用PID控制水温&#xff0c;由于实际在工程项目中&#xff0c;手动调节PID参数比较耗费时间&#xff0c;所以可以先利用MATLAB中的Simulink软件建立模型&#xff0c;先在仿真软件上调节大概的PID参数&#xff0c;再利用此PID参数为基础在实际的工程项目中手动调节PID参数…

spring boot(学习笔记第十一课)

spring boot(学习笔记第十一课) Session共享&#xff0c;JPA实现自动RESTful 学习内容&#xff1a; Session共享JPA实现自动RESTful 1. Session共享 Session共享面临问题 spring boot默认将session保存在web server的内存里面&#xff0c;会产生什么问题呢。 如上图所示&#…

《昇思25天学习打卡营第15天 | 昇思MindSpore基于MindSpore的红酒分类实验》

15天 本节学了通过MindSpore的完成红酒分类。 1.K近邻算法&#xff08;K-Nearest-Neighbor, KNN&#xff09;是一种用于分类和回归的非参数统计方法&#xff0c;是机器学习最基础的算法之一。 1.1分类问题 1.2回归问题 1.3距离的定义 2.数据处理 2.1 数据准备 2.2 数据读取与处…

isupper()方法——判断字符串是否全由大写字母组成

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 isupper()方法用于判断字符串中所有的字母是否都是大写。isupper()方法的语法格式如下&#xff1a; str.isupper() 如果字符串中包含至少…

OpenGL3.3_C++_Windows(24)

渲染平行光阴影 阴影作用&#xff1a; 有了阴影的渲染&#xff0c;更容易地区分出物体之间的位置关系&#xff0c;如何判断片段是否在阴影中&#xff1f; 普通思路&#xff1a; 以光的位置为视角进行渲染&#xff0c;我们绘制一条从光源出发的射线&#xff0c;测试更新射线经过…

数据结构-分析期末选择题考点(广义表)

莫道桑榆晚 为霞尚满天 数据结构-图期末选择题 数据结构-串、数组选择题 数据结构-排序选择题 数据结构-线性表、栈、队列、二叉树合集 契子✨ 广义表&#xff1a; <1>考点一&#xff1a;基本概念 广义表的基础概念 &#xff08;1&#xff09;什么是广义表 广义表&#…