问题描述

最近在做 OCR 图像识别。大致流程是先拿到预览区图片的 base64 字符串,根据接口要求压缩 base64 字符串大小,再调用 OCR 相关接口获取识别结果。然而,通过文件上传 input 域、FileReader 读到的 base64 字符串直接放入 img 标签后,预览出的图片往往会出现 原本为纵向拍摄的图片默认按横向图片展示,从而导致后续 OCR 识别报错。

方向正确:

方向错误:

究其原因,可能是 canvas.toDataURL(type, quality) 生成的 base64 字符串没有图片朝向相关的标识,赋给 img.src 后,img 标签 默认将较长的一边作为宽度、较短的一边作为高度 来显示图片。要解决这个问题,需要在图片加载完毕后,手动调节图片的朝向(右转或左转 90°)。

网上关于图片转向的文章大多通过构造新的 canvas 画布,设置具体的转向角度后重绘图片,最后在写回原图片。结合项目实际需求,只需要简单左右旋转 90° 即可。这可以通过 ImageData 对象的像素变换轻松实现。

基本原理1——像素矩阵变换

ImageData 是图片经数据化处理后的对象,其中包含三个属性:

  • width:图片的总宽度像素值(整数)
  • height:图片的总高度像素值(整数)
  • data:八位无符号整型固定数组、一个特殊的类型数组。该数组每 4 个元素的值,依次描述了对应像素点的 R、G、B、A 的取值,值域均为 [0, 255]。

因此一个 4 × 3 像素的原始图片,可以看作如下形式的像素矩阵 A
A = [ a 11 a 12 a 13 a 14 a 21 a 22 a 23 a 24 a 31 a 32 a 33 a 34 ] (1) A = \left[ \begin{matrix} a_{11} & a_{12} & a_{13} & a_{14} \\ a_{21} & a_{22} & a_{23} & a_{24} \\ a_{31} & a_{32} & a_{33} & a_{34} \end{matrix} \right] \tag{1} A=⎣⎡​a11​a21​a31​​a12​a22​a32​​a13​a23​a33​​a14​a24​a34​​⎦⎤​(1)
图片向右旋转 90°,实质就是设法将 A 变为 A’ ——
A ′ = [ a 31 a 21 a 11 a 32 a 22 a 12 a 33 a 23 a 13 a 34 a 24 a 14 ] (2) A'= \left[ \begin{matrix} a_{31} & a_{21} & a_{11}\\ a_{32} & a_{22} & a_{12}\\ a_{33} & a_{23} & a_{13}\\ a_{34} & a_{24} & a_{14} \end{matrix} \right] \tag{2} A′=⎣⎢⎢⎡​a31​a32​a33​a34​​a21​a22​a23​a24​​a11​a12​a13​a14​​⎦⎥⎥⎤​(2)

这可以通过原矩阵一次 转置、与多次初等 变换(逆序排列各列)得到:

A T = [ a 11 a 12 a 13 a 14 a 21 a 22 a 23 a 24 a 31 a 32 a 33 a 34 ] T = [ a 11 a 21 a 31 a 12 a 22 a 32 a 13 a 23 a 33 a 14 a 24 a 34 ] = > [ a 31 a 21 a 11 a 32 a 22 a 12 a 33 a 23 a 13 a 34 a 24 a 14 ] = A ′ (3) A^T=\left[ \begin{matrix} a_{11} & a_{12} & a_{13} & a_{14} \\ a_{21} & a_{22} & a_{23} & a_{24} \\ a_{31} & a_{32} & a_{33} & a_{34} \end{matrix} \right]^T= \left[ \begin{matrix} a_{11} & a_{21} & a_{31}\\ a_{12} & a_{22} & a_{32}\\ a_{13} & a_{23} & a_{33}\\ a_{14} & a_{24} & a_{34} \end{matrix} \right] => \left[ \begin{matrix} a_{31} & a_{21} & a_{11}\\ a_{32} & a_{22} & a_{12}\\ a_{33} & a_{23} & a_{13}\\ a_{34} & a_{24} & a_{14} \end{matrix} \right] = A' \tag{3} AT=⎣⎡​a11​a21​a31​​a12​a22​a32​​a13​a23​a33​​a14​a24​a34​​⎦⎤​T=⎣⎢⎢⎡​a11​a12​a13​a14​​a21​a22​a23​a24​​a31​a32​a33​a34​​⎦⎥⎥⎤​=>⎣⎢⎢⎡​a31​a32​a33​a34​​a21​a22​a23​a24​​a11​a12​a13​a14​​⎦⎥⎥⎤​=A′(3)

同理,图片向左旋转 90°,实际上就是得到矩阵 A’'

A ′ ′ = [ a 14 a 24 a 34 a 13 a 23 a 33 a 12 a 22 a 32 a 11 a 21 a 31 ] (4) A''= \left[ \begin{matrix} a_{14} & a_{24} & a_{34}\\ a_{13} & a_{23} & a_{33}\\ a_{12} & a_{22} & a_{32}\\ a_{11} & a_{21} & a_{31} \end{matrix} \right] \tag{4} A′′=⎣⎢⎢⎡​a14​a13​a12​a11​​a24​a23​a22​a21​​a34​a33​a32​a31​​⎦⎥⎥⎤​(4)
这可以通过原矩阵一次 转置、与多次初等 变换(逆序排列各行)得到——

A T = [ a 11 a 12 a 13 a 14 a 21 a 22 a 23 a 24 a 31 a 32 a 33 a 34 ] T = [ a 11 a 21 a 31 a 12 a 22 a 32 a 13 a 23 a 33 a 14 a 24 a 34 ] = > [ a 14 a 24 a 34 a 13 a 23 a 33 a 12 a 22 a 32 a 11 a 21 a 31 ] = A ′ ′ (5) A^T = \left[ \begin{matrix} a_{11} & a_{12} & a_{13} & a_{14} \\ a_{21} & a_{22} & a_{23} & a_{24} \\ a_{31} & a_{32} & a_{33} & a_{34} \end{matrix} \right]^T= \left[ \begin{matrix} a_{11} & a_{21} & a_{31}\\ a_{12} & a_{22} & a_{32}\\ a_{13} & a_{23} & a_{33}\\ a_{14} & a_{24} & a_{34} \end{matrix} \right]=> \left[ \begin{matrix} a_{14} & a_{24} & a_{34}\\ a_{13} & a_{23} & a_{33}\\ a_{12} & a_{22} & a_{32}\\ a_{11} & a_{21} & a_{31} \end{matrix} \right] = A'' \tag{5} AT=⎣⎡​a11​a21​a31​​a12​a22​a32​​a13​a23​a33​​a14​a24​a34​​⎦⎤​T=⎣⎢⎢⎡​a11​a12​a13​a14​​a21​a22​a23​a24​​a31​a32​a33​a34​​⎦⎥⎥⎤​=>⎣⎢⎢⎡​a14​a13​a12​a11​​a24​a23​a22​a21​​a34​a33​a32​a31​​⎦⎥⎥⎤​=A′′(5)

基本原理2——像素数组与矩阵的对应关系

由于 ImageData.data 对应一个数组,对于 4 × 3 的图片而言,ImageData.data 就是一个具有 48 个元素的数组 D,不妨每个元素的值就是其下标值,则:
D = [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7...44 , 45 , 46 , 47 ] (6) D = \left[0, 1, 2, 3, 4, 5, 6, 7... 44, 45, 46, 47\right]\tag{6} D=[0,1,2,3,4,5,6,7...44,45,46,47](6)
其中:

元组 (0, 1, 2, 3) 表示第 1(= 0 / 4 + 1) 个像素的颜色为 rgba(0, 1, 2, 3/255)
元组 (4, 5, 6, 7) 表示第 2(= 4 / 4 + 1) 个像素的颜色为 rgba(4, 5, 6, 7/255)
元组 (8, 9, 10, 11) 表示第 3(= 8 / 4 + 1) 个像素的颜色为 rgba(8, 9, 10, 11/255)

元组 (i, i+1, i+2, i+3) 表示第 (i / 4 + 1) 个像素的颜色为 rgba(i, i+1, i+2, (i+3)/255)

元组 (44, 45, 46, 47) 表示第 12(= 44 / 4 + 1) 个像素的颜色为 rgba(44, 45, 46, 47/255)

可见从 0 开始遍历 D 数组,每次递增 4 个单位,即可依次得到各个像素的红色值 R,再依次加1、加2、加3,即得到对应的绿色值 G、蓝色值 B、等效 α 通道值 A。

反之,如果知道图片的像素尺寸为 4 × 3,则可以通过下图找到数组 D 的各个元素:

可见各像素点是按照 从左至右、从上至下 的顺序排列的。设图片总宽度像素为 W,总高度像素为 H,任一像素点 P 的坐标为 (x, y)P 的红色值在数组 D 的下标为 R(x, y),则:
R ( x , y ) = ( x + W ⋅ y ) × 4 (7) R(x, y) = (x + W · y) × 4 \tag{7} R(x,y)=(x+W⋅y)×4(7)
验证:(x 与 y 均从 0 开始计数)

R(2, 1) = (2 + 1 × 4) × 4 = 24
R(1, 2) = (1 + 2 × 4) × 4 = 36
R(3, 1) = (3 + 1 × 4) × 4 = 28

拿到了 R(x, y),不难求出该像素的纵向中心对称像素 Rh(x, y)、横向中心对称像素 Rw(x, y)、以及主对角线对称像素 Rd(x, y)
R h ( x , y ) = [ x + W ⋅ ( H − 1 − y ) ] × 4 (8-1) Rh(x, y) = [x + W · (H - 1 - y)] × 4 \tag{8-1} Rh(x,y)=[x+W⋅(H−1−y)]×4(8-1)

R w ( x , y ) = [ ( W − 1 − x ) + W ⋅ y ] × 4 (8-2) Rw(x, y) = [(W - 1 - x) + W · y] × 4 \tag{8-2} Rw(x,y)=[(W−1−x)+W⋅y]×4(8-2)

R d ( x , y ) = ( y + H ⋅ x ) × 4 (8-3) Rd(x, y) = (y + H · x) × 4 \tag{8-3} Rd(x,y)=(y+H⋅x)×4(8-3)

其中,式(8-3)用于 转置 运算;式(8-1)、式(8-2)分别用于 初等行变换初等列变换

具体实现

基本思路:

  1. 通过 canvas 获取目标图片的 ImageData 对象;
  2. 转置原图片数组,得到数组 AT
  3. AT 执行一组初等行变换,使各行逆序排列,得到左旋 90° 效果;
  4. AT 执行一组初等列变换,使各列逆序排列,得到右旋 90° 效果;
  5. 将新的像素数组写回图片源标签。

HTML

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Rotate by ImageData</title><style>.image{ margin-top: 5px; }</style>
</head>
<body><div class="btns"><input type="button" value="左转 90°" id="turnLeft" /><input type="button" value="右转 90°" id="turnRight" /></div><div class="image"><img id="fruit" src="fruit.jpg" class="image" alt="fruit" title="fruit" /></div><script src="data:imageRotate.js"></script>
</body></html>

imageRotate.js

document.querySelector('#turnLeft' ).addEventListener('click', e => rotateImage('l'))
document.querySelector('#turnRight').addEventListener('click', e => rotateImage('r'))function rotateImage(direction = 'l') {// 1. Prepare ImageDatalet img = document.querySelector('#fruit')const { width: W, height: H } = imglet cvs = document.createElement('canvas')cvs.width = Wcvs.height = Hlet ctx = cvs.getContext('2d')ctx.drawImage(img, 0, 0)let imgDt0 = ctx.getImageData(0, 0, W, H)let imgDt1 = new ImageData(H, W)let imgDt2 = new ImageData(H, W)let dt0 = imgDt0.datalet dt1 = imgDt1.datalet dt2 = imgDt2.data// 2. Transposelet r = r1 = 0  // index of red pixel in old and new ImageData, respectivelyfor (let y = 0, lenH = H; y < lenH; y++) {for (let x = 0, lenW = W; x < lenW; x++) {r  = (x + lenW * y) * 4r1 = (y + lenH * x) * 4dt1[r1 + 0] = dt0[r + 0]dt1[r1 + 1] = dt0[r + 1]dt1[r1 + 2] = dt0[r + 2]dt1[r1 + 3] = dt0[r + 3]}}// 3. Reverse width / heightfor (let y = 0, lenH = W; y < lenH; y++) {for (let x = 0, lenW = H; x < lenW; x++) {r  = (x + lenW * y) * 4r1 = direction === 'l'? (x + lenW * (lenH - 1 - y)) * 4: ((lenW - 1 - x) + lenW * y) * 4dt2[r1 + 0] = dt1[r + 0]dt2[r1 + 1] = dt1[r + 1]dt2[r1 + 2] = dt1[r + 2]dt2[r1 + 3] = dt1[r + 3]}}// 4. Redraw imagecvs.width = Hcvs.height = Wctx.clearRect(0, 0, W, H)ctx.putImageData(imgDt2, 0, 0, 0, 0, H, W)img.src = cvs.toDataURL('image/jpeg', 1)
}

运行结果:

原始图片:

左转 90°:

右转 90°:

示例文件

链接: https://pan.baidu.com/s/1w1_5qh3Tg95VUUjLmhrvTA
提取码: v7f6

利用 ImageData 实现图片左右旋转 90°相关推荐

  1. ios 拍照上传到服务器_ios端浏览器拍照上传到服务器,图片被旋转90度 php 解决方案...

    1.可以通过前端进行解决,本案例通过后端解决的 判断请求的浏览器的ua,如果是ios浏览器则进行90度旋转 重点来了: 必须确保检测的图片是ios设备上传的完整图片,不要在前端压缩过的,因为压缩后的图 ...

  2. 上传图片的时候,ios手机的图片会旋转90°

    1.问题:在html5中利用canvas对上传图片压缩的时候,ios手机竖着拍照时,图片会旋转90°,其他情况正常. 2.解决方法:获取拍照角度,对Ios竖着拍的照片进行角度处理 3.利用exif.j ...

  3. python图片旋转脚本_Python+OpenCV 实现图片无损旋转90°且无黑边

    0. 引言 有如上一张图片,在以往的图像旋转处理中,往往得到如图所示的图片. 然而,在进行一些其他图像处理或者图像展示时,黑边带来了一些不便.本文解决图片旋转后出现黑边的问题,实现了图片尺寸不变的旋转 ...

  4. 解决H5 IOS手机图片上传时图片会旋转90°问题

    解决H5 IOS手机图片上传时图片会旋转90°问题 Vant 官方给出的解答需要自己解决,没有处理. 解决办法主要使用了 compressorjs 插件库 一.Vant UI库Uploader 组件图 ...

  5. 解决ios横屏拍照图片自动旋转90度问题

    解决ios横屏拍照图片自动旋转90度问题 参考文章: (1)解决ios横屏拍照图片自动旋转90度问题 (2)https://www.cnblogs.com/lanshengzhong/p/900856 ...

  6. iOS开发- 相机(摄像头)获取到的图片自动旋转90度解决办法

    http://www.tuicool.com/articles/IfEZre 今天写demo的时候发现, 如果把通过相机获取到的图片,直接进行操作, 比如裁剪, 缩放, 则会把原图片向又旋转90度. ...

  7. android 竖屏拍照旋转90度,三星等机型上拍照后图片被旋转90度的解决方案

    考虑到Android7.0以后拍照修改了调用和返回方式,找到了一个看起来还不错的第三方库,实际可能并非如此. -TakePhoto 在三星Note3和S6上测试,发现竖屏拍照后返回的照片是横屏的,在其 ...

  8. html中如何使图片自动旋转90度,css怎么让图片旋转90度?

    css怎么让图片旋转90度?下面本篇文章给大家介绍一下使用CSS让图片旋转90度的方法.有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助. css怎么让图片旋转90度? 在CSS中,可以 ...

  9. html中如何使图片自动旋转90度,css实现图片旋转90度的方法

    css实现图片旋转90度的方法 发布时间:2020-08-31 11:44:39 来源:亿速云 阅读:550 作者:小新 小编给大家分享一下css实现图片旋转90度的方法,相信大部分人都还不怎么了解, ...

最新文章

  1. react项目---基本语法字符串数组(6)
  2. PHP 学习笔记 01
  3. Android之系统自带的文字外观设置
  4. Ubuntu下pip安装、升级、卸载
  5. 关于子对话框的创建与销毁
  6. gan简介_GAN简介
  7. Educational Codeforces Round 111 (Rated for Div. 2)
  8. Java EE 7中的WebSocket客户端API
  9. 恋爱Linux(Fedora20)2——安装Java运行环境(JDK)
  10. 亿嘉和机器人上市了吗_亿嘉和上半年收入持续增长,拟7亿元定增加码主业研发...
  11. Kubernetes学习总结(8)—— Kubernetes Pod 资源管理 和 Pod 服务质量
  12. Spyder IDE中使用git
  13. always on sql 收缩日志_sql server日志文件过大无法收缩的问题
  14. 以Crypto++实现RSA加解密二进制数据
  15. python下载网页内容_使用selenium下载整个html页面内容
  16. 如何查询redhat的版本信息
  17. JavaScript常见的网页特效(元素样式相关属性)
  18. Microsoft.NET离线运行库合集
  19. 使用 jsonp解决跨域问题,在vue中成功调用心知天气api
  20. SPJ数据库—初识sql语句(02)(注释版)

热门文章

  1. 与Session的亲密接触彻底掌握Java中Session Token Cookie
  2. Compute Shader 语法及函数 Reference for HLSL
  3. 身份证号验证正则表达式
  4. 【面试题】谈谈你对vite的了解
  5. 行尾不一致,是否将行尾进行标准化
  6. SSL/TSL握手解析
  7. 【AD封装】KF2EDGK系列5.08接线端子(带3D)
  8. PHP文件锁同步实例
  9. 拷贝,浅拷贝与深拷贝三者的区别
  10. 使用POI操作Excell文件的基础用法