当前位置: 首页 > news >正文

【OpenCV实现图像:使用OpenCV生成拼图效果】

文章目录

    • 概要
    • 通用配置
    • 不考虑间隔代码实现
    • 考虑间隔代码实现
    • 小结

概要

概要:

拼图效果是一种将图像切割为相邻正方形并重新排列的艺术效果。在生成拼图效果时,可以考虑不同的模式,包括是否考虑间隔和如何处理不能整除的部分。

不考虑间隔,忽略不能整除部分:
相邻正方形之间没有间隔,同时高度不能整除的部分直接被忽略。
示例图中展示了正方形紧密排列,没有任何间隔,不整除的部分被舍弃。
在这里插入图片描述

不考虑间隔,对不能整除部分进行空白填充:
相邻正方形之间没有间隔,同时对高度不能整除的部分进行白色填充。
示例图中呈现了正方形之间无缝连接,高度不能整除的部分填充为白色。
在这里插入图片描述

考虑间隔,忽略不能整除部分:
相邻正方形之间存在间隙,间隔距离为3像素,同时高度不能整除的部分直接被忽略。
示例图中显示了正方形之间的间隔,不整除的部分被舍弃。
在这里插入图片描述

考虑间隔,对不能整除部分进行空白填充:
相邻正方形之间存在间隙,间隔距离为3像素,同时对高度不能整除的部分进行白色填充。
示例图中演示了正方形之间的间隔,高度不能整除的部分填充为白色。
在这里插入图片描述

通用配置

为了统一上述代码,我们设置通用配置项如下:

config = {"cell_num":7,"whether_crop_image_height": True,"whether_with_gap": True,"gap_width":3
}

上述配置中,各项含义如下:

cell_num: 表示每行划分格子个数

whether_crop_image_height: 表示是否对高度不能整除部分进行空白填充

whether_with_gap: 表示相邻正方形之间是否存在间隔

gap_width: 表示相邻正方形间间隔的大小

不考虑间隔代码实现

1). 获取每行格子数目和输入图像信息

w_count = config['cell_num']
src_height = img.shape[0]
src_width = img.shape[1]

2).计算每个格子的长度以及结果图像的宽度

sub_length = int(src_width / w_count)
new_width = sub_length * w_count

3).根据是否需要对高度进行空白填充,计算结果图像的高度

if config['whether_crop_image_height']:h_count = int(src_height / sub_length)
else:h_count = math.ceil(src_height / sub_length)
new_height = sub_length * h_count

4).对结果图进行赋值

img_t = np.zeros(shape=(new_height,new_width,3),dtype=np.uint8) + 255
if config['whether_crop_image_height']:img_t = img[:new_height,:new_width,:]
else:img_t[:src_height, :new_width, :] = img[:src_height, :new_width, :]

5).画格子

 for x_i in range(1, w_count):cv2.line(img_t,(x_i*sub_length,0),(x_i*sub_length,new_height-1),color=(205,205,74),thickness=1)
for y_i in range(1, h_count):cv2.line(img_t,(0,y_i*sub_length),(new_width-1,y_i*sub_length), color=(205,205,74), thickness=1)

完整代码:

import cv2
import numpy as np
import math# 通用配置
config = {"cell_num": 7,"whether_crop_image_height": True,"whether_with_gap": False,"gap_width": 0
}# 读取图像
img = cv2.imread("img.png")# 获取每行格子数目和输入图像信息
w_count = config['cell_num']
src_height = img.shape[0]
src_width = img.shape[1]# 计算每个格子的长度以及结果图像的宽度
sub_length = int(src_width / w_count)
new_width = sub_length * w_count# 根据是否需要对高度进行空白填充,计算结果图像的高度
if config['whether_crop_image_height']:h_count = int(src_height / sub_length)
else:h_count = math.ceil(src_height / sub_length)
new_height = sub_length * h_count# 对结果图进行赋值
img_t = np.zeros(shape=(new_height, new_width, 3), dtype=np.uint8) + 255
if config['whether_crop_image_height']:img_t = img[:new_height, :new_width, :]
else:img_t[:src_height, :new_width, :] = img[:src_height, :new_width, :]# 画格子
for x_i in range(1, w_count):cv2.line(img_t, (x_i * sub_length, 0), (x_i * sub_length, new_height-1), color=(205, 205, 74), thickness=1)
for y_i in range(1, h_count):cv2.line(img_t, (0, y_i * sub_length), (new_width-1, y_i * sub_length), color=(205, 205, 74), thickness=1)# 展示生成的拼图效果
cv2.imshow('Mosaic Effect', img_t)
cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述
带填充的结果:

import cv2
import numpy as np
import math# 通用配置
config = {"cell_num": 7,"whether_crop_image_height": True,"whether_with_gap": False,"gap_width": 0
}# 读取图像
img = cv2.imread("img.png")# 获取每行格子数目和输入图像信息
w_count = config['cell_num']
src_height = img.shape[0]
src_width = img.shape[1]# 计算每个格子的长度、间隔长度以及结果图像的宽度
sub_length = int(src_width / w_count)
gap_length = int(config["gap_width"])
new_width = sub_length * w_count + gap_length * (w_count - 1)# 根据是否需要对高度进行空白填充,计算结果图像的高度
if config['whether_crop_image_height']:h_count = int((src_height + gap_length) / (sub_length + gap_length))
else:h_count = math.ceil((src_height + gap_length) / (sub_length + gap_length))
new_height = sub_length * h_count + gap_length * (h_count - 1)# 对结果图进行赋值
img_t = np.zeros(shape=(new_height, new_width, 3), dtype=np.uint8) + 255
if config['whether_crop_image_height']:for i in range(h_count):for j in range(w_count):begin_x = sub_length * jbegin_y = sub_length * isrc_x = gap_length * j + begin_xsrc_y = gap_length * i + begin_yimg_t[src_y:src_y + sub_length, src_x:src_x + sub_length, :] = img[begin_y:begin_y + sub_length, begin_x:begin_x + sub_length, :]
else:for i in range(h_count):for j in range(w_count):begin_x = sub_length * jbegin_y = sub_length * isrc_x = gap_length * j + begin_xsrc_y = gap_length * i + begin_yif i < h_count - 1:img_t[src_y:src_y + sub_length, src_x:src_x + sub_length, :] = img[begin_y:begin_y + sub_length, begin_x:begin_x + sub_length, :]else:diff_height = src_height - sub_length * (h_count - 1)img_t[src_y:src_y + diff_height, src_x:src_x + sub_length, :] = img[begin_y:begin_y + diff_height, begin_x:begin_x + sub_length, :]# 展示生成的拼图效果
cv2.imshow('Mosaic Effect with Fill', img_t)
cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

不考虑间隔,带填充的情况:
img_t = np.zeros(shape=(new_height, new_width, 3), dtype=np.uint8) + 255
if config['whether_crop_image_height']:img_t = img[:new_height, :new_width, :]
else:img_t[:src_height, :new_width, :] = img[:src_height, :new_width, :]
考虑间隔,带填充的情况:
img_t = np.zeros(shape=(new_height, new_width, 3), dtype=np.uint8) + 255
if config['whether_crop_image_height']:for i in range(h_count):for j in range(w_count):begin_x = sub_length * jbegin_y = sub_length * isrc_x = gap_length * j + begin_xsrc_y = gap_length * i + begin_yimg_t[src_y:src_y + sub_length, src_x:src_x + sub_length, :] = img[begin_y:begin_y + sub_length, begin_x:begin_x + sub_length, :]
else:for i in range(h_count):for j in range(w_count):begin_x = sub_length * jbegin_y = sub_length * isrc_x = gap_length * j + begin_xsrc_y = gap_length * i + begin_yif i < h_count - 1:img_t[src_y:src_y + sub_length, src_x:src_x + sub_length, :] = img[begin_y:begin_y + sub_length, begin_x:begin_x + sub_length, :]else:diff_height = src_height - sub_length * (h_count - 1)img_t[src_y:src_y + diff_height, src_x:src_x + sub_length, :] = img[begin_y:begin_y + diff_height, begin_x:begin_x + sub_length, :]

在带填充的情况下,会对不能整除的高度部分进行空白填充。带填充的拼图效果会更整齐,而不带填充的情况下可能会有一些截断。

考虑间隔代码实现

1). 获取每行格子数目和输入图像信息

w_count = config['cell_num']
src_height = img.shape[0]
src_width = img.shape[1]

2).计算每个格子的长度,间隔长度以及结果图像的宽度

sub_length = int(src_width / w_count)
gap_length = int(config["gap_width"])
new_width = sub_length * w_count + gap_length * (w_count -1)

3).根据是否需要对高度进行空白填充,计算结果图像的高度

if config['whether_crop_image_height']:h_count = int( (src_height + gap_length) / (sub_length+gap_length))
else:h_count = math.ceil((src_height + gap_length) / (sub_length+gap_length))
new_height = sub_length * h_count + gap_length * (h_count-1)

4).对结果图进行赋值

img_t = np.zeros(shape=(new_height,new_width,3),dtype=np.uint8) + 255
if config['whether_crop_image_height']:for i in range(h_count):  for j in range(w_count): begin_x = sub_length * jbegin_y = sub_length * isrc_x = gap_length * j + begin_xsrc_y = gap_length * i + begin_yimg_t[src_y:src_y+sub_length,src_x:src_x + sub_length,:] = img[begin_y:begin_y + sub_length,begin_x:begin_x + sub_length,  :]
else:for i in range(h_count):  for j in range(w_count): begin_x = sub_length * jbegin_y = sub_length * isrc_x = gap_length * j + begin_xsrc_y = gap_length * i + begin_yif i<h_count-1:img_t[src_y:src_y+sub_length,src_x:src_x + sub_length,:] = img[begin_y:begin_y + sub_length,begin_x:begin_x + sub_length,  :]else:diff_height = src_height - sub_length * (h_count-1)img_t[src_y:src_y + diff_height, src_x:src_x + sub_length, :] = img[begin_y:begin_y + diff_height,begin_x:begin_x + sub_length, :]

全部代码

import cv2
import numpy as np
import math# 通用配置
config = {"cell_num": 7,"whether_crop_image_height": True,"whether_with_gap": True,"gap_width": 3
}# 读取图像
img = cv2.imread("img.png")# 获取每行格子数目和输入图像信息
w_count = config['cell_num']
src_height = img.shape[0]
src_width = img.shape[1]# 计算每个格子的长度、间隔长度以及结果图像的宽度
sub_length = int(src_width / w_count)
gap_length = int(config["gap_width"])
new_width = sub_length * w_count + gap_length * (w_count - 1)# 根据是否需要对高度进行空白填充,计算结果图像的高度
if config['whether_crop_image_height']:h_count = int((src_height + gap_length) / (sub_length + gap_length))
else:h_count = math.ceil((src_height + gap_length) / (sub_length + gap_length))
new_height = sub_length * h_count + gap_length * (h_count - 1)# 对结果图进行赋值
img_t = np.zeros(shape=(new_height, new_width, 3), dtype=np.uint8) + 255
if config['whether_crop_image_height']:for i in range(h_count):for j in range(w_count):begin_x = sub_length * jbegin_y = sub_length * isrc_x = gap_length * j + begin_xsrc_y = gap_length * i + begin_yimg_t[src_y:src_y + sub_length, src_x:src_x + sub_length, :] = img[begin_y:begin_y + sub_length, begin_x:begin_x + sub_length, :]
else:for i in range(h_count):for j in range(w_count):begin_x = sub_length * jbegin_y = sub_length * isrc_x = gap_length * j + begin_xsrc_y = gap_length * i + begin_yif i < h_count - 1:img_t[src_y:src_y + sub_length, src_x:src_x + sub_length, :] = img[begin_y:begin_y + sub_length, begin_x:begin_x + sub_length, :]else:diff_height = src_height - sub_length * (h_count - 1)img_t[src_y:src_y + diff_height, src_x:src_x + sub_length, :] = img[begin_y:begin_y + diff_height, begin_x:begin_x + sub_length, :]# 绘制间隔
if config['whether_with_gap']:for i in range(h_count):for j in range(w_count - 1):begin_x = sub_length * (j + 1) + gap_length * jbegin_y = sub_length * i + gap_length * icv2.line(img_t, (begin_x, begin_y), (begin_x, begin_y + sub_length), color=(205, 205, 74), thickness=1)# 展示生成的带填充和间隔的拼图效果
cv2.imshow('Mosaic Effect with Fill and Gap', img_t)
cv2.waitKey(0)
cv2.destroyAllWindows()

在这里插入图片描述

小结

通过使用OpenCV库和Python编程语言,实现了图像拼图效果的生成。

拼图效果生成: 根据用户的需求,实现了两种不同的拼图效果生成方式。
一种是不考虑间隔,可以选择是否对高度不能整除的部分进行空白填充;另一种是考虑间隔,可以选择相邻正方形之间是否存在间隔,以及间隔的大小,同样可以选择是否对高度不能整除的部分进行空白填充。
通用配置: 引入了通用配置项,用户可以通过修改配置来调整拼图的格子数、是否裁剪高度、是否考虑间隔以及间隔的宽度等参数。
代码优化: 封装了一些功能函数,使代码更加模块化和可读性更强。

相关文章:

【OpenCV实现图像:使用OpenCV生成拼图效果】

文章目录 概要通用配置不考虑间隔代码实现考虑间隔代码实现小结 概要 概要&#xff1a; 拼图效果是一种将图像切割为相邻正方形并重新排列的艺术效果。在生成拼图效果时&#xff0c;可以考虑不同的模式&#xff0c;包括是否考虑间隔和如何处理不能整除的部分。 不考虑间隔&a…...

【AOSP】生成签名文件release key,通过Android源码对apk进行签名

简介 现在apk都需要签名&#xff0c;Flutter做的项目官方规定编译apk必须签名。 签名的好处&#xff1a; 应用来源验证&#xff1a; 应用签名允许Android系统验证应用的来源。每个应用都使用开发者的私钥进行签名&#xff0c;而应用的签名信息包含在应用的APK文件中。当用户尝…...

深度学习之基于Tensorflow银行卡号码识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介银行卡号码识别的步骤TensorFlow的优势 二、功能三、系统四. 总结 一项目简介 # 深度学习基于TensorFlow的银行卡号码识别介绍 深度学习在图像识别领域取得…...

第95步 深度学习图像目标检测:Faster R-CNN建模

基于WIN10的64位系统演示 一、写在前面 本期开始&#xff0c;我们学习深度学习图像目标检测系列。 深度学习图像目标检测是计算机视觉领域的一个重要子领域&#xff0c;它的核心目标是利用深度学习模型来识别并定位图像中的特定目标。这些目标可以是物体、人、动物或其他可识…...

设计模式—里氏替换原则

1.概念 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说&#xff0c;任何基类可以出现的地方&#xff0c;子类一定可以出现。 LSP是继承复用的基石&#xff0c;只有当衍生类可以替换掉基类&#xff0c;软件单位的功能不受到影…...

PyTorch包

进入PyTorch的官网&#xff1a; pytorch GitHub 点击GitHub&#xff1a; 进入PyTorch的主目录&#xff1a; 进入Vision reference&#xff1a; detection&#xff1a; 这就是我们在训练过程中会使用到的文件了&#xff1a;...

22、什么是中间件和权限拦截中间件实操

新建中间件 middleware\auth.js // 定义权限判断中间件&#xff0c;中间件的第一个参数是context export default ({store, redirect}) > {console.log("中间件被调用")// if (!store || !store.state.userinfo) {// redirect("/")// } }页面使用…...

vue.config.js

proxy代理 proxy选项用于配置开发服务器的代理。下面是proxy的全部属性&#xff1a; 1. target (String | Object | Function): 指定要代理的目标主机的URL。可以是一个字符串&#xff0c;也可以是一个对象或函数&#xff0c;用于动态返回目标URL。 2. forward (Boolean): 控…...

80C51单片机----数据传送类指令

目录 一.一般传送指令&#xff0c;即mov指令 1.16位传送&#xff08;仅1条&#xff09; 2.8位传送 &#xff08;1&#xff09;目的字节为A&#xff08;累加器&#xff09; &#xff08;2&#xff09;目的字节为Rn(工作寄存器) &#xff08;3&#xff09;目的字节为direct…...

【Golang】使用泛型对数组进行去重

背景&#xff1a; 要求写一个方法&#xff0c;返回去重后的数组。数组的类型可能是int64&#xff0c;也可能是string&#xff0c;或是其他类型。 如果区分类型的话&#xff0c;每增加一个新的类型都需要重新写一个方法。 示例代码&#xff1a; //对int64数组进行去重 func DeD…...

Ps:画笔工具的基本操作

画笔工具 Brush Tool是 Ps 中最常用的工具&#xff0c;广泛地用于绘画与修饰工作。 虽然多数操作可在画笔工具的工具选项栏中选择执行&#xff0c;但是如果能记住相应的快捷键可大大提高工作效率。 熟练掌握画笔工具的操作对于使用其他工具也非常有益&#xff0c;因为 Ps 中许多…...

【Apache Doris】一键实现万表MySQL整库同步 | 快速体验

【Apache Doris】一键实现万表MySQL整库同步 | 快速体验&#xff09; 一、 环境信息1.1 硬件信息1.2 软件信息 二、 流程介绍三、 前提概要3.1 安装部署3.2 JAR包准备3.2.1 数据源3.2.2 目标源 3.3 脚本模版 四、快速体验五、常见问题5.1 Mysql通信异常5.2 MySQL无Key同步异常5…...

35.逻辑运算符

目录 一.什么是逻辑运算符 二.C语言中的逻辑运算符 三.逻辑表达式 三.视频教程 一.什么是逻辑运算符 同时对俩个或者俩个以上的表达式进行判断的运算符叫做逻辑运算符。 举例&#xff1a;比如去网吧上网&#xff0c;只有年满十八周岁并且带身份证才可以上网。在C语言中如果…...

ASP.NET Core 启用CORS

浏览器的安全阻止一个域的本地页面请求另外不同域的本地页面&#xff0c;这个限制叫同源策略&#xff0c;这个安全特性用来阻止恶意站点从别的网站读取数据 例如假如我有一个页面叫A.html https://foo.example/A.html 现在页面A.html有一个ajax代码尝试读取B.html的HTML的源…...

io.lettuce.core.RedisCommandExecutionException

io.lettuce.core.RedisCommandExecutionException: ERR invalid password ERR invalid password-CSDN博客 io.lettuce.core.RedisCommandExecutionException /** Copyright 2011-2022 the original author or authors.** Licensed under the Apache License, Version 2.0 (the…...

vue3 导出数据为 excel 文件

文章目录 安装插件封装组件 -- Export2Excel.js多表封装界面使用 -- 数据处理成二维数组更多 菜鸟最近做了一个需求&#xff0c;就是需要上传表单并识别&#xff0c;然后识别出来的内容要可以修改&#xff0c;然后想的就是识别内容变成 form 表单&#xff0c;所以并没有使用 Sp…...

PyQt6简介

锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计12条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话版…...

某60区块链安全之未初始化的存储指针实战二学习记录

系列文章目录 文章目录 系列文章目录未初始化的存储指针实战二实验目的实验环境实验工具实验原理实验内容实验过程EXP利用 未初始化的存储指针实战二 实验目的 学会使用python3的web3模块 学会分析以太坊智能合约未初始化的存储指针漏洞 找到合约漏洞进行分析并形成利用 实验…...

软件工程第十二周

软件作坊、软件危机、软件过程控制、重型控制、敏捷、DevOps 这些术语概括了软件开发历史和实践中的几个重要概念和阶段。让我们逐一解析它们&#xff1a; 软件作坊&#xff08;Software Craftsmanship&#xff09;&#xff1a;这是软件开发的早期模式&#xff0c;强调个人技能…...

electron 问题记录

23年11月24 electron项目npm install 卡在一个地方不动 原因&#xff1a;主要是 install electron 会卡住 解决方法&#xff1a; # 先解决install electron卡死 npm install -g cnpm --registryhttps://registry.npmmirror.com cnpm install electron# 然后下载其他依赖 np…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建

华为云FlexusDeepSeek征文&#xff5c;DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色&#xff0c;华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型&#xff0c;能助力我们轻松驾驭 DeepSeek-V3/R1&#xff0c;本文中将分享如何…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

springboot 日志类切面,接口成功记录日志,失败不记录

springboot 日志类切面&#xff0c;接口成功记录日志&#xff0c;失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...

【1】跨越技术栈鸿沟:字节跳动开源TRAE AI编程IDE的实战体验

2024年初&#xff0c;人工智能编程工具领域发生了一次静默的变革。当字节跳动宣布退出其TRAE项目&#xff08;一款融合大型语言模型能力的云端AI编程IDE&#xff09;时&#xff0c;技术社区曾短暂叹息。然而这一退场并非终点——通过开源社区的接力&#xff0c;TRAE在WayToAGI等…...

Modbus转Ethernet IP深度解析:磨粉设备效率跃升的底层技术密码

在建材矿粉磨系统中&#xff0c;开疆智能Modbus转Ethernet IP网关KJ-EIP-101的应用案例是一个重要的技术革新。这个转换过程涉及到两种主要的通信协议&#xff1a;Modbus和Ethernet IP。Modbus是一种串行通信协议&#xff0c;广泛应用于工业控制系统中。它简单、易于部署和维护…...

鸿蒙APP测试实战:从HDC命令到专项测试

普通APP的测试与鸿蒙APP的测试有一些共同的特征&#xff0c;但是也有一些区别&#xff0c;其中共同特征是&#xff0c;它们都可以通过cmd的命令提示符工具来进行app的性能测试。 其中区别主要是&#xff0c;对于稳定性测试的命令的区别&#xff0c;性能指标获取方式的命令的区…...

PHP 表单 - 验证邮件和URL

PHP 表单 - 验证邮件和URL 引言 在Web开发中&#xff0c;表单是用户与网站交互的重要途径。一个功能完善的表单不仅可以收集用户数据&#xff0c;还能提高用户体验。在表单设计中&#xff0c;验证邮件地址和URL是常见的需求。本文将详细介绍如何在PHP中实现邮件和URL的验证&a…...