这是填坑篇,之前写的图片旋转程序把图片变成了桌布,几个世纪后,在一个月黑风高的夜晚,我灵光乍现,何不试试双线性插值?

先上代码和效果图。

1 #!/usr/bin/env python3

2 #-*-coding:utf-8-*-

3 """

4 双线性插值参考资料: 双线性插值原理及Python实现 - Jinglever https://www.jianshu.com/p/29e5c84ea5395

6 如果出现错误:...If you are on Ubuntu or Debian, install libgtk2.0-dev and pkg-config7 执行 pip3 install opencv-contrib-python8 """

9 importnumpy as np10 #np.set_printoptions(suppress=True) # 关闭科学计数法

11 importcv212 importos13

14

15 #旋转矩阵R

16 ANGLE = 30 #(dim=°)

17 assert 0 < ANGLE < 90 #目前限制这个旋转范围,原因是y1, y2, y3, y4上下关系根据角度变化

18 alpha = ANGLE/360*2*np.pi19 R_rev = np.matrix([[np.cos(alpha), np.sin(alpha)], #逆向映射推导的旋转矩阵

20 [-np.sin(alpha), np.cos(alpha)]])21 print(R_rev)22

23 #重设图片大小

24 WIDTH, HEIGHT = 640, 480

25

26 img = cv2.imread("timg.jpg")27 img =cv2.resize(img, (WIDTH, HEIGHT))28 #img_gray = np.float32(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY))

29 img =np.float32(img)30 print(img.shape)31

32 #假设已经得到旋转后的图片,利用图片边框画出图片的矩形,在矩形内遍历坐标就是图片各个像素点的坐标

33 #注意旋转角度超过90度后边框线的上下关系会发生变化,待改进……

34 x = np.arange(np.abs(WIDTH*np.cos(alpha)) + np.abs(HEIGHT*np.sin(alpha)), dtype=np.int32)35 y1 = lambda x: (- x*np.tan(alpha)).astype(np.int32)36 y2 = lambda x: (y1(x) + HEIGHT/np.cos(alpha)).astype(np.int32)37 y3 = lambda x: (x/np.tan(alpha)).astype(np.int32)38 y4 = lambda x: (y3(x) - WIDTH/np.sin(alpha)).astype(np.int32)39 #用矩形下面2条线(的最大值)确定y坐标最小值,上面2条线(的最小值)确定y坐标最大值

40 y_min = np.max(np.concatenate((y1(x).reshape(1, -1), y4(x).reshape(1, -1))), axis=0)41 y_max = np.min(np.concatenate((y2(x).reshape(1, -1), y3(x).reshape(1, -1))), axis=0)42 #计算旋转后图片各像素点坐标

43 pre_index = [np.array((yi, xi)).reshape(-1, 1) for xi in x for yi in range(y_min[xi], y_max[xi]+1)]44

45 ori_index = np.array(list(map(R_rev.dot, pre_index))).reshape(-1, 2) #坐标变换到原图

46 hs_p, ws_p = np.hsplit(ori_index, 2) #分离y, x坐标

47

48 ws_p = np.clip(ws_p, 0, WIDTH-1) #限制坐标最值防止越界

49 hs_p = np.clip(hs_p, 0, HEIGHT-1)50

51 ws_0 = np.clip(np.floor(ws_p), 0, WIDTH - 2).astype(np.int) #找出每个投影点在原图的近邻点坐标

52 hs_0 = np.clip(np.floor(hs_p), 0, HEIGHT - 2).astype(np.int)53 ws_1 = ws_0 + 1

54 hs_1 = hs_0 + 1

55

56 f_00 = img[hs_0, ws_0, :].T #四个临近点的像素值

57 f_01 =img[hs_0, ws_1, :].T58 f_10 =img[hs_1, ws_0, :].T59 f_11 =img[hs_1, ws_1, :].T60

61 w_00 = ((hs_1 - hs_p) * (ws_1 - ws_p)).T #计算权重

62 w_01 = ((hs_1 - hs_p) * (ws_p -ws_0)).T63 w_10 = ((hs_p - hs_0) * (ws_1 -ws_p)).T64 w_11 = ((hs_p - hs_0) * (ws_p -ws_0)).T65

66 pixels = (f_00 * w_00).T + (f_01 * w_01).T + (f_10 * w_10).T + (f_11 * w_11).T #计算目标像素值

67

68 y_new, x_new = np.hsplit(np.array(pre_index).reshape(-1, 2), 2) ## 分离y, x坐标

69 y_new = y_new - np.min(y_new) #y坐标平移,防止图片旋转后被窗口切分

70

71 h, w = np.max(y_new), np.max(x_new) #旋转后画布大小

72 #像素映射 原始→新图

73 new_img = np.zeros((h+1, w+1, img.shape[2])) #(H, W, C)

74 new_img[y_new, x_new, :] = pixels #填充像素

75

76 cv2.imwrite('./AffinedImg.jpg', new_img, [int(cv2.IMWRITE_JPEG_QUALITY),95])77 #显示图片

78 cv2.imshow('img', np.array(new_img, dtype=np.uint8))79 cv2.waitKey(0)80 cv2.destroyAllWindows()

原图见入坑篇。

下面是运行结果,这次我换成了彩色的:

双线性插值常用于图像的比例缩放,基本原理很容易搜索到,这里就不多说了,重点讲一下怎么把它应用到图像旋转上来。

假设输入图片是 input image,输出图片是 output image,首先回顾一下双线性插值的思路:坐标的变换是反着来的,从 output image 到 input image。即 output image 对应的整数坐标,缩放变换到 input image 后,变成浮点数坐标,然后取它4个角上的点,计算浮点数坐标的颜色,填充到 output image 对应的坐标那里。

再说回图像旋转,之前出现黑点就是因为图像的变换是从 input image 到 output image,即每个 input image 的像素坐标用旋转矩阵算到 output image 上,然后把浮点数直接量化成整数,这样就引入了量化误差,个别输出的坐标就错位了,导致有黑点(本来在黑点位置的像素因为坐标错位到别的地方去了,黑点那里就没有颜色数据了)。

应用双线性插值的解决思路:先得到 output image 对应的整数坐标,变换到 input image 后,变成浮点数坐标,然后取它4个角上的点,计算浮点数坐标的颜色,填充到 output image 对应的坐标那里。(跟上面那句一样)

那么,实现过程就分为以下几步:

1. 获取 output image 对应各个像素点坐标。

1) 假设已经得到 output image,这张图片是旋转一定角度的,俗话说就是斜着的,但是坐标系是正着的,怎么得到像素坐标?

2. 坐标映射:使用反着转的旋转矩阵(R_rev)把 output image 的坐标转到 input image 上,这个结果算出来是浮点数。

3. 双线性插值:取浮点数坐标4个角上的点,计算浮点数坐标的颜色,然后填充回 output image。大功告成!

首先回答问题 1) :

我使用了一个很简单的方法,就是靠图像边框作为边界,框出图像的矩形区域,遍历里面的所有点。

$y_{1}=-x\cdot \tan \left ( \alpha  \right )$

$y_{2}=-x\cdot \tan \left ( \alpha  \right ) + \frac {HEIGHT}{\cos \left ( \alpha  \right )}$

$y_{3}=\frac{x}{\tan \left ( \alpha  \right )}$

$y_{4}=\frac{x}{\tan \left ( \alpha  \right )}-\frac{WIDTH}{\sin \left ( \alpha  \right )}$

上图绘制了y1, y2, y3, y4四条直线,注意图片显示的坐标,y轴正方向朝下。如图所示,y1, y2, y3, y4是图片的边框线,标号是我自己随便标的,如果旋转角度在90度内,边框线的上下关系不变(y2, y3在上,y1, y4在下,注意y轴正方向朝下)。这也就是现在这个程序只能实现90度以内旋转的原因,如果要继续旋转,例如旋转120度时,就变成 y1, y3 在上,y2, y4 在下,需要修改程序。

然后是遍历图片坐标:

如图所示,从点 (0, 0) 开始,按照箭头方向逐列遍历图片坐标,保存到 pre_index 中。 对应代码:(理解注释里的上下关系的时候,仍然要记得y轴正方向朝下!)

#假设已经得到旋转后的图片,利用图片边框画出图片的矩形,在矩形内遍历坐标就是图片各个像素点的坐标#注意旋转角度超过90度后边框线的上下关系会发生变化,待改进……

x = np.arange(np.abs(WIDTH*np.cos(alpha)) + np.abs(HEIGHT*np.sin(alpha)), dtype=np.int32)

y1= lambda x: (- x*np.tan(alpha)).astype(np.int32)

y2= lambda x: (y1(x) + HEIGHT/np.cos(alpha)).astype(np.int32)

y3= lambda x: (x/np.tan(alpha)).astype(np.int32)

y4= lambda x: (y3(x) - WIDTH/np.sin(alpha)).astype(np.int32)#用矩形下面2条线(的最大值)确定y坐标最小值,上面2条线(的最小值)确定y坐标最大值

y_min = np.max(np.concatenate((y1(x).reshape(1, -1), y4(x).reshape(1, -1))), axis=0)

y_max= np.min(np.concatenate((y2(x).reshape(1, -1), y3(x).reshape(1, -1))), axis=0)#计算旋转后图片各像素点坐标

pre_index = [np.array((yi, xi)).reshape(-1, 1) for xi in x for yi in range(y_min[xi], y_max[xi]+1)]

到这里第1步就完成了。

然后是第2步,坐标映射。

#R = np.matrix([[np.cos(alpha), -np.sin(alpha)],#[np.sin(alpha), np.cos(alpha)]])

R_rev = np.matrix([[np.cos(alpha), np.sin(alpha)], #逆向映射推导的旋转矩阵

[-np.sin(alpha), np.cos(alpha)]])

按照推导正向旋转矩阵的方法反推逆向旋转矩阵,就可以得到上面的结果。如果仍然难以理解,就当做反转(alpha = -alpha)

ori_index = np.array(list(map(R_rev.dot, pre_index))).reshape(-1, 2) #坐标变换到原图

ori_index 里的坐标全部是根据 pre_index 计算来的,并不是从原图上面取点。这里计算出来的 ori_index 数据类型是浮点数。

然后是第3步,双线性插值和像素填充。

从 ori_index 开始直到计算出来 pixels 就是双线性插值的过程了,实现原理可以参考一下参考资料。

之后是像素填充:

y_new, x_new = np.hsplit(np.array(pre_index).reshape(-1, 2), 2) ## 分离y, x坐标

y_new = y_new - np.min(y_new) #y坐标平移,防止图片旋转后被窗口切分

h, w= np.max(y_new), np.max(x_new) #旋转后画布大小#像素映射 原始→新图

new_img = np.zeros((h+1, w+1, img.shape[2])) #(H, W, C)

new_img[y_new, x_new, :] = pixels #填充像素

需要注意的是,旋转后的图片有一部分的坐标值是负值,实际显示的时候如果输入负坐标,图片会被分开显示,所以把旋转后的图片朝y轴正方向平移,移到所有点坐标值都大于0的地方。

现在 pixels 里已经计算出来旋转后图片所有点的像素值,像素点数据的排列方向和 pre_index 是相同的,所以直接把对应的点赋值就可以了。

最后的图片就是 new_img,插值效果还是很不错的。;-)

参考资料:

java旋转图片后边上变黑_图像旋转后出现黑点 - (二) - 填坑相关推荐

  1. java旋转图片后边上变黑_Java旋转图像将背景的一部分变成黑色

    因此,我下载了"原始"图像(不是正方形),对其进行了修改,使其变为正方形,运行您的代码,得到了java.awt.image.ImagingOpException:无法转换src图像 ...

  2. java 解决图片压缩背景色变黑的问题

    项目场景: 之前公司有个需求是将用户上传的图片自动压缩,作为一个菜鸟,要想苟活于公司之下,大多时候都是面向百度开发,熟练地运用 CV 大法,不求成为公司的大佬,只想安安心心的过好每一天. 翻阅了很多博 ...

  3. Java实现图片文件上传

    Java实现图片文件上传 Java实现后台图片上传,将上传图片的接口进行分层,便于维护接口. 接口部分(interface) 将接口分为单图片上传以及多图片上传 public interface Fi ...

  4. 90度旋转 flip opencv_基于Hu距的图像旋转矫正之OpenCV实现

    目录 1.常见图像旋转矫正方法 1.1 基于图像边缘轮廓的旋转矫正 1.2 基于傅里叶变换以及霍夫直线检测的旋转矫正 2.基于Hu距图像旋转矫正 2.1 Hu旋转不变性 2.2 实现步骤 2.2.1 ...

  5. 写给大忙人看的 - Java中图片压缩上传至MinIO服务器(4)

    之前文章已经介绍了 MinIO 的环境搭建,已经对文件的上传下载方法,本篇文章一起与大家来学习图片压缩上传的方法 1.背景 最近客户总抱怨 APP 中图片显示较慢, 升级服务器带宽又没有多的预算.查看 ...

  6. 用matlab实现任意点图片的旋转_图像旋转MATLAB实现代码

    图像旋转 MATLAB 实现 function [I,I1,I2] = irotating( i, x0) [m, n] = size(i); %get the size of the image m ...

  7. android相册拍照剪切上传封装,安卓,图片裁剪上传真机测试好使,打包后显示:无法保存裁剪的图像...

    var IMAGE_UNSPECIFIED = "image/*"; var PHOTOZOOM = 2; // 获取完图片返回key var PHOTOLAT = 1; // 剪 ...

  8. php 旋转图片 并保存,如何在PHP中旋转并保存图像

    在PHP中旋转并保存图像的方法:首先使用函数[imagerotate()]用给定角度旋转图像:然后使用函数[imagejpeg()]输出图象到浏览器或文件,代码为[imagejpeg ( resour ...

  9. thumbnails 变黑_解决java压缩图片透明背景变黑色的问题

    代码如下: public class Picture { // TODO Auto-generated constructor stub public static void resizePNG(St ...

  10. java将图片放进mysql中_在java代码中怎么从服务器上把图片拿来放到数据库里

    展开全部 看你用的是么数据库,一般是读取后转e68a84e8a2ad3231313335323631343130323136353331333431346430成二进制blob格式存入数据库的BLOB ...

最新文章

  1. 【6月活动】投稿换T恤!只要你愿意分享!这件酷炫的T恤就是你的~!!
  2. web项目性能优化--网络、js、渲染
  3. codevs 4560 NOIP2015 D2T2 子串
  4. 几本对于笔试和面试有用的书
  5. [未解决]jQuery中autocomplete的source格式问题
  6. Java编译和执行模式包括两种,Java程序的编译和执行模式包括2点,是【 】和半解释。...
  7. Cinder 组件详解 - 每天5分钟玩转 OpenStack(47)
  8. 判断线段和直线相交 POJ 3304
  9. Extjs4循序渐进(一)——开始Ext
  10. Nginx配置文件(作为Web服务器)
  11. 48个英语音标表:20个元音+28个辅音(转载)
  12. 电脑端实现微信双开(登录两个微信)
  13. 软件测试基本技术-XMind思维导图(全)
  14. 如何将原始SNP信息转化为0,1,2的矩阵形式
  15. 针对某个WEB渗透的整套修复建议(更新部分)
  16. 安装RedHat Linux 7.4
  17. 2011年计算机三级,2011年计算机三级网络技术辅导:网络技术上机分析
  18. 基于Multisim的buck降压斩波电路仿真
  19. 【USACO3.1.4】形成的区域 二维线段树/离散化/矩形切割/浮漂法 【线段树方法以后写】
  20. H5实现复制淘口令功能

热门文章

  1. Unity资源分享网站——记录
  2. 前端项目搭建基本流程
  3. Unable to load library 'xxx': Native library (linux-x86-64/xxx.so) not found in resourc 问题解决
  4. 怎么把图片内存变小尺寸保持不变呢。
  5. Android studio实现语音转文字功能
  6. JAVA音视频解决方案----JTT1078-2016文档梳理与一些难点梳理
  7. 马尔可夫(Markov)不等式
  8. HbuilderX配置微信开发者工具
  9. 赛事相关 | 腾讯觅影×腾讯云TI平台,锁了
  10. git官网下载比较慢的解决方法