Python 实现图片剪切
Python对图片处理的库是非常丰富的,所以有很多库能实现这一目标的方法如Pillow,OpenCV,moviepy等等,而这里给的gif处理方法主要使用的是Pillow,moviepy库的
安装Pillow,moviepy
终端上1 2
| pip install Pillow pip install moviepy
|
对于图片的剪切主要是通过坐标进行判断,需要提供一个矩形坐标,即四个坐标点进行定位,然后对原图进行剪切处理。
那么其他废话不多说,直接上代码。(学别人讲的话,直接上代码确实很爽)
除GIF的图片文件处理
1 2 3 4 5 6 7 8 9
| U_x = 144.73429951690818 U_y = 76.13526570048306 U_width = 640.0000000000001 U_height = 640.0000000000001 u_rotate = 90
img = Image.open('input.img') crop_im = img.crop((U_x, U_y, U_x + U_width, U_y + U_height)).resize((400, 400),Image.LANCZOS).rotate(u_rotate)
|
备注:LANCZOS实际上和ANTIALIAS指向的是同一种图像插值模式算法
GIF图片文件处理
#用Pillow处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
import time
from PIL import Image, ImageSequence
if __name__ == '__main__': first_time = time.time() start=time.perf_counter() U_x = 144.73429951690818 U_y = 76.13526570048306 U_width = 640.0000000000001 U_height = 640.0000000000001 u_rotate = 90 frames = []
with Image.open('input.gif') as im: idx = 0 for frame in ImageSequence.Iterator(im): frame = frame.crop((U_x, U_y, U_x + U_width, U_y + U_height)).resize((400, 400)).rotate(u_rotate) frame.info['duration'] = im.info['duration'] frames.append(frame) idx += 1 frames[0].save('out.gif', save_all=True, append_images=frames[1:], loop=0, duration=im.info['duration'], quality=80) print('时间戳说——总共用时:',time.time()-first_time,'秒') print('计算机说——总共用时:',time.perf_counter()-start,'秒')
|
#用moviepy进行图片剪切
备注:如果你要求对分辨率的在处理的话,那么需要Pillow的版本为9.5,10.0和其底层的resize中设置的图像插值模式冲突,是Image.ANTIALIAS,手动打上去还改不了,而且速度极慢,是用Pillow和结合Pillow库的两倍。而且在movie官方文档也不建议在大型框架和web上使用moviepy.editor子模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
from moviepy.editor import * import time
if __name__ == '__main__': first_time = time.time() start = time.perf_counter() x = 144.73429951690818 y = 76.13526570048306 width = 640.0000000000001 height = 640.0000000000001 t_rotate = 90 gif = VideoFileClip('head_cap.gif') nframes = gif.reader.nframes
durations = [0.1] * nframes
images = [gif.get_frame(t) for t in range(nframes)]
clip = ImageSequenceClip(images, durations=durations)
rotated = clip.crop(x1=x, y1=y, x2=x + width, y2=y + height).resize((400, 400)).rotate(t_rotate) rotated.write_gif('23.gif', fps=20)
print('时间戳说——总共用时:', time.time() - first_time, '秒') print('CPU说——总共用时:', time.perf_counter() - start, '秒')
|
#用Pillow对每一帧图片进行剪切,moviepy生成gif文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
import time
import numpy as np from PIL import Image from moviepy.editor import VideoFileClip
def pillow_movie_crop(img): ''' :param img: :return: 存在缺点帧数丢失,图片变大 ''' im = Image.fromarray(img) cropped = im.crop((x, y, x+width, y+height)).resize((400,400)).rotate(t_rotate) return np.array(cropped)
if __name__ == '__main__': first_time=time.time() x = 144.73429951690818 y = 76.13526570048306 width = 640.0000000000001 height = 640.0000000000001 t_rotate = 90 clip = VideoFileClip("head_cap.gif", has_mask=False) cropped_clip = clip.fl_image(pillow_movie_crop) cropped_clip.write_gif("21.gif", fuzz=10, opt='nq') print('总共用时:',time.time()-first_time,'秒')
|
所以其实使用Pillow库是一个优选
Django 文件上传
settings.py1 2 3
| MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = '/media/'
|
utils.py 自主封装的自定义文件字段,对上传文件进行限制1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class RestrictedFileField(FileField): """ max_upload_size: 2.5MB - 2621440 5MB - 5242880 10MB - 10485760 20MB - 20971520 50MB - 5242880 100MB 104857600 250MB - 214958080 500MB - 429916160 """
def __init__(self, *args, **kwargs): self.content_types = kwargs.pop("content_types", []) self.max_upload_size = kwargs.pop("max_upload_size", []) super().__init__(*args, **kwargs) def clean(self, *args, **kwargs): data = super().clean(*args, **kwargs)
file = data.file try: content_type = file.content_type if content_type in self.content_types: if file.size > self.max_upload_size: raise forms.ValidationError('文件大小要求为{}. 当前文件大小为 {}'.format(filesizeformat(self.max_upload_size), filesizeformat(file.size))) else: raise forms.ValidationError('当前文件格式不被运行,仅支持{}.'.format(self.content_types)) except AttributeError: pass return data
|
models.py1 2 3 4 5 6 7 8
| from utils import RestrictedFileField class UserInfo(models.Model) avatar = RestrictedFileField(verbose_name=u'头像', max_length=50, default='avatar/用户.png', upload_to='avatar/', content_types=['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/tiff'],max_upload_size=5242880) id = models.BigAutoField(verbose_name='UID', primary_key=True) ……
|
urls.py1 2 3 4 5 6 7 8 9
| from django.urls import path, include, re_path from django.views.static import serve from django.conf import settings from . import views
urlpatterns = [ re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}, name='media'), ……
|
views.py1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| from django import forms from app.error import errorResponse class UserInfoForm(forms.ModelForm): class Meta: model = UserInfo fields = '__all__' def edit_info(request): uid = request.session['uid'] userinfo = UserInfo.objects.filter(id=uid).get() if request.method == 'GET': form = UserInfoForm(instance=userinfo) return render(request, 'space_edit_info.html', { 'userinfo': userinfo, 'form': form, 'title': '个人中心-编辑个人信息' }) form = UserInfoForm(instance=userinfo, data=request.POST) if form.is_valid(): form.save() return redirect('/space/edit_info/') return errorResponse(request, errMsg=form['avatar'].errors[0] )
|
此处的errorResponse是自己封装的处理错误页函数,这样可以在前端显示自定义头像组件的错误
1 2 3 4 5 6 7
| def errorResponse(request, errMsg): ''' :param request: :param errMsg:返回错误信息到前端页面 :return: ''' return render(request, 'error.html', {'errMsg': errMsg})
|
Django 图片上传剪切综合案例实现
前端获取所需的坐标数据
使用基于JQuery的cropper和Bootstrap,可以更方便的实现获取用户对图片剪切的操作(缩放,旋转等)后的数据和坐标。
jQuery cropper是一款使用简单且功能强大的图片剪裁jquery插件。该插件支持图片放大,缩小,旋转,裁剪和预览等功能。
素材来源17素材,不用登录另存为网页就行。
注意演示里的最右上角x最好点一下,因为我们要的里面的<iframe>
标签的html文档,然后保存下来的就是我们需要的素材,然后自己处理一下。文件主要如下
sitelogo.js是利用cropper获取坐标等数据,然后携带数据向处理的url放送请求
目标的url又写在了签单的html文件里 from的action,修改这个即可。
这里我使用的是Django的url反向解析,更便利一些,有着不少好处。
而在sitelogo.js中,主要要去关注两部分
这部分是序列化坐标和旋转角度数据,这里的x,y是左上的坐标,根据height和width可以计算出选中的矩形整个坐标。
发送ajax请求
前端的文件主要知道这些就可以了,自己不喜欢这些样式,想再多添加些功能可以自己改。
而前端重中之重就是记得Django有一个csrf安全验证机制,如果没有在中间件关闭它,就需要在前端添提交表单添加一个{% csrf_token%}
或者在对应函数前面标记注释。再或者直接根据上面这张ajax提交代码的图片,在你自己写的js代码中添加在headers中.
@csrf_exempt
1 2 3
| from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
|
后端根据坐标对图片进行剪切
#models.py 继承上面
urls.py继承上面且扩充1 2
| path('upload_avatar/', views.upload_avatar, name='upload_avatar'), path('ajax_avatar_upload/', views.ajax_avatar_upload, name='ajax_avatar_upload'),
|
views.py1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| import re import os import json import uuid from django.shortcuts import render, redirect, get_object_or_404 from django.urls import reverse from django.views.decorators.csrf import csrf_exempt from django import forms from PIL import Image, ImageSequence from .models import UserInfo
class AvatarUploadForm(forms.Form): avatar_file = forms.ImageField() def upload_avatar(request): uid = request.session['uid'] userinfo = UserInfo.objects.filter(id=uid).get() return render(request, 'Document.html', { 'userinfo': userinfo })
def ajax_avatar_upload(request): uid = request.session['uid'] userinfo = get_object_or_404(UserInfo, userName=username)
if request.method == "POST": form = AvatarUploadForm(request.POST, request.FILES) if form.is_valid(): img = request.FILES['avatar_file'] data = request.POST['avatar_data']
if img.size > 5242880: return JsonResponse({"message": "上传图片大小应小于5MB, 请重新上传。", })
current_avatar = userinfo.avatar cropped_avatar = crop_image(current_avatar, img, data, userinfo.id) userinfo.avatar = cropped_avatar userinfo.save() data = {"result": userinfo.avatar.url, } return JsonResponse(data)
else: return JsonResponse({"message": "请重新上传。只能上传图片"})
return HttpResponseRedirect(reverse('upload_avatar'))
def crop_image(current_avatar, file, data, uid): '''
:param current_avatar: 当前头像 :param file: 上传文件 :param data: 坐标 :param uid: 用户id :return: 保存的图片路径名 ''' ext = file.name.split('.')[-1] file_name = '{}.{}'.format(uuid.uuid4().hex[:10], ext) cropped_avatar = os.path.join(str(uid), "avatar", file_name) file_path = os.path.join("media", str(uid), "avatar", file_name)
coords = json.loads(data) t_x = int(coords['x']) t_y = int(coords['y']) t_width = t_x + int(coords['width']) t_height = t_y + int(coords['height']) t_rotate = coords['rotate'] if abs(t_rotate)<=90: t_rotate=-t_rotate if file_name.endswith('.gif'): '''成功了就是保存的文件会变大,还有保存速度太慢了4~5s,前台应该做个动画''' frames = []
with Image.open(file) as im: idx = 0 for frame in ImageSequence.Iterator(im): frame = frame.crop((t_x, t_y, t_width, t_height)).resize((400, 400)).rotate(t_rotate) frame.info['duration'] = im.info['duration'] frames.append(frame) idx += 1 frames[0].save(file_path, save_all=True, append_images=frames[1:], loop=0, duration=im.info['duration'], quality=80) else:
img = Image.open(file) crop_im = img.crop((t_x, t_y, t_width, t_height)).resize((400, 400), Image.LANCZOS).rotate(t_rotate)
directory = os.path.dirname(file_path) if not os.path.exists(directory): os.makedirs(directory) crop_im.save(file_path)
if not current_avatar == os.path.join("media", "avatar", "用户.png"): current_avatar_path = os.path.join("media", str(uid), "avatar",os.path.basename(current_avatar.url)) os.remove(current_avatar_path)
return cropped_avatar
|
效果展示
#静图剪切
#动图剪切(这一步其实挺慢的,中间等待的部分剪去了)
总结:
当初自己整这个可是整了挺久的,很难找到直接拿来用的,很多都是前端VUE处理的,没有直接是Html或是Html+JQuery,而且那些头像处理没有对GiF处理的步骤,就观在那些大型网站来看,现在好像确实没有多少支持用gif动图为头像的。
而且这个前端处理的js文件坐标点好像有点偏移……
虽然历经不少,也找到了解决办法,但是还是那么难找,整了挺久的。找到了还是要自己理解和运用,就这样吧,希望我的文章能给你提供帮助。
希望能够我一个赞同/赞/收藏辣(‾◡◝)
大家有什么问题可以在评论区大胆留盐(不用加密(bushi
与CSDN同步发布,我应该在博客园也发一份~