基于python的数字图像处理--学习笔记(二)

  • 基于python的简单图像矩阵变换:
    • 向前映射和向后映射:
    • 图像旋转:
    • 彩色图像邻域平均操作:
    • 去除/弱化 图片的加性高斯白噪声
    • 灰度级变换

基于python的简单图像矩阵变换:

使用opencv-python读取图片文件,并使用numpy和math等库对图片进行对称反转、旋转、平移、剪切等操作。

当使用cv2.imread读入一个图片后,完全可以将读入的图片转换为nuparray矩阵,所有对图片灰度(色度)的变化,都可以表示为是对矩阵中像素位置对应的数据的变换操作。

也即,图像变换的本质是将像素点的坐标通过某一种函数关系,所有对图像的操作都是对像素矩阵的操作。在本次内容中,还并未涉及灰度值的变换,主要是图像像素坐标的迁移如平移或旋转。

在平移旋转中,变换后的图像和原图像的像素是一一对应的;而对于剪切等变换,会有原图像上多个像素对应变换后图像像素的情况。由此,即可引出,图像映射的前向和后向区分问题。


向前映射和向后映射:

假设变换前图像为I(x,y),变换后图像为I’(x’,y’),则变换前后的图像之间存在下列关系

  1. 由原图像像素表示出变换后的像素,以原图每个像素为基准计算被它影响的新图像素。即为前向映射
  2. 由变换后的图像像素还原出原图像像素,以新图每个像素为基准计算影响它的原图像素。即为后向映射
    在这种情况下,我们知道输出图像上整数点位置(x’,y’)在变换前位于输入图像上的位置(x,y),一般来说这是个非整数点位置,利用其周围整数点位置的输入图像像素值进行插值,就得到了该点的像素值。我们遍历输出图像,经过坐标变换、插值两步操作,我们就能将其像素值一个个地计算出来,因此向后映射又叫图像填充映射。

前向映射可能会导致多对一的情况,也会出现目标点没有像素,但是原图像有的问题。工程上多使用反向映射,避免上述问题,同时减少计算量。但反向映射需要提前知道反变换,在变换复杂的场合,其反变换会很难求得。

参考:图像变换——向前映射和向后映射

参考:基础图像操作(十三):像素重映射


图像旋转:

终于写到正题了,开始结合python实现对图像的简单变换。

前文说过,图像变换就是矩阵变换:图像旋转也即矩阵旋转。(x’,y’)表示一个点经过旋转后的新位置,(x,y)表示未旋转前的原始位置,θ为旋转角度,编程中以弧度为单位。

注意:计算多个点的旋转,需要将每个点位置分别代入公式计算。

  1. 由以上方法,可写出旋转代码。但运行后会发现有诸多问题,这里先按下不表。

    import cv2 as cv
    import numpy as np
    import mathdef rotate(img, angle):height, width, _ = img.shaperes = np.zeros((height, width, 3), np.uint8) #生成三通道原图片大小的变换后图片模板anglePi = angle * np.pi / 180.0for i in range(height):#yfor j in range(width):#x# y = round(j * np.cos(anglePi) - i * np.sin(anglePi))   前向映射# x = round(j * np.sin(anglePi) + i * np.cos(anglePi))# 后向映射,下面为逆矩阵srcY = (j * np.cos(anglePi) + i * np.sin(anglePi))srcX = (-j * np.sin(anglePi) + i * np.cos(anglePi))# 后向映射+双线性插值x = math.floor(srcX)y = math.floor(srcY)u = srcX - xv = srcY - yif 0 <= x <= 255 and 0 <= y <= 255:res[i, j] = (1 - u) * (1 - v) * img[x, y] + u * (1 - v) * img[x + 1, y] + (1 - u) * v * img[x, y + 1] + u * v * img[x + 1, y + 1]# res[i, j] = img[x, y]# res[x, y] = img[i, j]print(res.shape)return resif __name__ == '__main__':img = cv.imread("pic1.jpg")cv.imshow("rotated img", rotate(img, 20))print("success")cv.waitKey(0)cv.destroyAllWindows()

    res[i, j] = (1 - u) * (1 - v) * img[x, y] + u * (1 - v) * img[x + 1, y] + (1 - u) * v * img[x, y + 1] + u * v * img[x + 1, y + 1]这句代码用来实现双线性内插,变换后图像像素通过反变换发现位于原图像像素空隙处,故使用双线性内插法拟合此处像素值并赋给变换图像。如下图所示:已知Q12,Q22,Q11,Q21,要插值的点为P点,首先在x轴方向上,对R1和R2两个点进行插值,即蓝色R1的值根据Q11和Q21的值可求得为:



    在代码中由于u = srcX - xv = srcY - y,且 x = math.floor(srcX) y = math.floor(srcY)
    x和y是srcx、srcy的向下取整,像素位置间隔为1,则u和v都是小于1的数,相当于已经进行过归一化,故分母省略。

  2. 问题显现

    运行上述代码,图像确实可以旋转,但并不完美,出现了几处问题。

    显而易见的,旋转图片并非以图片中心做旋转,而是围绕左上角,其根源在于默认矩阵左上角为矩阵的原点,也即图像的左上角像素为图片的原点,若想以图片中心做旋转,应先对图片做搬移操作,先把旋转点平移到原点;而且图片在以原图片大小显示时并不能将旋转后图片完整呈现,以朴素的理解图像在旋转之后的显示矩形框也应该比原来的大,故应按照旋转后的需求扩大图片矩形框。

    优化后的步骤:

    1).先把旋转点平移到原点

    2).然后进行以上旋转操作

    3).按1的逆操作平移回去

    就可以得到绕任意点旋转点变换矩阵:

    4).扩大画布

    画布大小不变的情况下,会有一部分图像超出,显示不全,所以我们需要将画布扩大为:

    新的高由图片中两段蓝色线组合

    new_H=int(w∗fabs(sin(radians(angle)))+h∗fabs(cos(radians(angle))))

    新的宽由图片中两段红色线组合

    new_W=int(h∗fabs(sin(radians(angle)))+w∗fabs(cos(radians(angle))))


    新的画布扩大是基于原图左上角点扩大,显示的还是蓝色区域,同样丢失了信息。

    5).我们还需要将红色区域进行平移操作显示到蓝色区域


    所以,在变换矩阵M上,我们可以调整平移参数:

    M[0,2]+=(new_W−w)/2

    M[1,2]+=(new_H−h)/2

由以上,改进后的旋转代码:

# -*-coding:utf-8-*-import cv2
from math import *
import numpy as np
from scipy.spatial.distance import pdist# x=np.random.random(100)
# y=np.random.random(100)
#
# #方法一:根据公式求解,2维
# d1=np.dot(x,y)/(np.linalg.norm(x)*np.linalg.norm(y))
#
# # print d1
#
# #方法二:根据scipy库求解,n维
# X=np.vstack([x,y])
# d2=1-pdist(X,\'cosine\')# print d2img = cv2.imread("pic1.jpg")
height, width = img.shape[:2]# (1)如何计算这个旋转角度
# degree = np.dot(x,y)/(np.linalg.norm(x)*np.linalg.norm(y))
degree = -30# (2)旋转后的尺寸
# @radians(),角度转换为弧度
heightNew = int(width * fabs(sin(radians(degree))) + height * fabs(cos(radians(degree))))
widthNew = int(height * fabs(sin(radians(degree))) + width * fabs(cos(radians(degree))))# (3)求旋转矩阵,以图片中心点为旋转中心 隐含M矩阵
matRotation = cv2.getRotationMatrix2D((width / 2, height / 2), degree, 1)matRotation[0, 2] += (widthNew - width) / 2  # ?????重点在这步,目前不懂为什么加这步
matRotation[1, 2] += (heightNew - height) / 2  # ?????重点在这步# (4)最后得到的图像,边界是黑色
imgRotation = cv2.warpAffine(img, matRotation, (widthNew, heightNew), borderValue=(0, 0, 0))# 我把像素值 > 0 的区域提取出来
# 作二值化,将阈值设置为50,阈值类型为cv2.THRESH_BINARY,则灰度在大于50的像素其值将设置为255,其它像素设置为0
# retval, dst = cv2.threshold(imgRotation, 50, 255, cv2.THRESH_BINARY)
# cv2.imwrite('1.jpeg',img, [int( cv2.IMWRITE_JPEG_QUALITY), 95])
# cv2.imwrite('2.png',img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
# cv2.imshow("img", img)
cv2.imshow("imgRotation", imgRotation)
# cv2.imwrite("imgRotation_1.jpg",imgRotation)
cv2.waitKey(0)

运行代码,图片以中心做旋转,且显示完整正常。


彩色图像邻域平均操作:

写不动了,其实就是矩阵平均,用3×3邻域做平均就是将原图片像素除边缘像素点之外的所有像素,以他和他八邻域像素值的平均值做代替,结果是会使图像平滑,边界不清晰,也就是模糊。

本来代码很简单,就是简单的遍历矩阵,但是在操作过程中一直不能正常显示,而是很抽象的彩色图像

import cv2 as cv
import numpy as npdef avg(img):height, width,_ = img.shaperes = np.zeros((height, width, 3), np.uint8)for i in range(height):for j in range(width):if i - 1 >= 0 and j - 1 >= 0 and i + 1 <= height-2 and j + 1 <= width-2:res[i, j] = (img[i - 1, j - 1] + img[i - 1, j] + img[i - 1, j + 1] + img[i, j - 1] + img[i, j] + img[i, j + 1] + img[i + 1, j - 1] + img[i + 1, j] + img[i + 1, j + 1]) / 9return resif __name__ == '__main__':img = cv.imread("nimg.ws.126.jpg")show_img = np.concatenate((img.astype(np.uint8),avg(img)),axis=0)#.reshape(-1,width,3)print(show_img.shape)cv.imshow("avg_ed img", show_img)print("success")cv.waitKey(0)cv.destroyAllWindows()


平滑后的图像甚至有不可名状那味了。。。

开始检查代码,代码逻辑肯定是没有问题的,使用print打印出均衡化后的像素矩阵,会发现像素的值都很小,从下半张图像上也能看出来,是什么导致像素值变小了呢?有时还会蹦出一个警告RuntimeWarning: overflow encountered in ubyte_scalars,就从这个警告入手。

通过查询这个警告,发现是像素加减运算溢出异常,出现的原因是因为图片的像素一般是八位即最大值是256,最小值是0,如果超出了这个范围就会出现警告,不会报错使得程序停止下来,但是会使得计算出来的结果有误。

cv.read读入图片后返回的是nparray数组,里面的元素类型应该是np.uint8,在累加过程中溢出,python不会对其自动修改内存范围,而是重新从0开始计数:

假设一个图片像素点的灰度值为136,另一个像素点的灰度值为180,两个灰度值相加出现的结果按道理来说是:316

但是得出来的结果是:60,出现这种情况的原因就是因为316溢出了0-255的范围,导致其重新从0开始计数

解决办法:

1、在计算过程中先对每个累加的像素值除以9,而不是累加后 再÷9,以确保过程中不发生溢出。

2、 在计算过程中对每个需要加的像素值进行数据类型转换,使用.astype(np.uint32) 使累加允许的最大值变大,防止溢出。

3、在cv.read读入图片文件时就对整个矩阵修改其元素类型,并在显示图像前修改回np.uint8。

import cv2 as cv
import numpy as npdef avg(img):height, width ,_= img.shaperes = np.zeros((height, width, 3), np.uint8)#print(res)print(res[2][80])for i in range(height):for j in range(width):if i - 1 >= 0 and j - 1 >= 0 and i + 2 <= height and j + 2 <= width:a=0# 3×3的邻域操作for near_x in range(3):for near_y in range(3):#res[i, j] = res[i, j]+img[i - near_x-1, j - near_y-1] / 9#a=a+img[i - near_x-1, j - near_y-1].astype(np.uint32)a=a+img[i - near_x-1, j - near_y-1]res[i, j]=(a/9).astype(np.int8)return resif __name__ == '__main__':img = cv.imread("nimg.ws.126.jpg").astype(np.int32)height, width, _ = img.shape# cv.imshow("avg_ed img", avg(img))show_img = np.concatenate((img.astype(np.uint8),avg(img)),axis=0)#.reshape(-1,width,3)cv.imshow("avg_ed img", show_img)print("success")cv.waitKey(0)cv.destroyAllWindows()


处理后图片正常显示,也比原图像模糊了一些。

去除/弱化 图片的加性高斯白噪声

使用多张图片叠加取平均值方法弱化高斯噪声,达到去除噪声效果。

import random
import cv2 as cv
import numpy as npdef noise(img):img = img.astype(np.uint8)height, width, mode = img.shapefor i in range(height):for j in range(width):for k in range(mode):img[i, j, k] += random.gauss(0, 1)return imgdef avg_remove_noise(img, count):tar = np.zeros_like(img).astype(np.float32)for _ in range(count):tar += np.float32(noise(img))tar /= count# tar = np.clip(tar, 0, 255).astype(np.uint8)tar = tar.astype(np.uint8)return tarif __name__ == '__main__':img = cv.imread("nimg.ws.126.jpg")# cv.imshow("avg_ed 2", avg_remove_noise(img, 2))show_img = np.concatenate((img, noise(img)), axis=0)show_img = np.concatenate((show_img, avg_remove_noise(img, 50)), axis=0)cv.imshow("avg_ed 50",show_img)# cv.imshow("avg_ed 100", avg_remove_noise(img, 100))# cv.imshow("noise_img", noise(img))# diff(noise(img), avg_remove_noise(img, 10))print("success")cv.waitKey(0)cv.destroyAllWindows()

最上面为原图像,中间的是加入了(0,1)的高斯白噪声(均值为0),最下面为通过多张图片取均值的方法消除噪声的结果,可以明显看到噪声被弱化。

缺点就是计算量大耗时长,猜测应该是50遍高斯白噪声随机过程时间比较长,单纯的叠加取均值应该不会这么长时间吧。

灰度级变换

将255灰度级的灰度图片更改为任意灰度级

import cv2 as cv
import numpy as npdef change_grey_level(img, level):img -= np.min(img)ma = np.max(img)height, width = img.shaperes = np.zeros((height, width), np.uint8)for i in range(height):for j in range(width):for lev in range(level):flg=255/levelif img[i,j]>=flg*lev and img[i,j]<flg*(lev+1):res[i, j] = lev * 255 / (level - 1)  break# for lev in range(level):#     flg=(lev+1)*255/level#     if img[i,j]<=flg:#         continue#     res[i, j]=(lev+1)*255/(level-1)# res[i, j] = level * (img[i, j] / ma)return np.clip(res, 0, 255)if __name__ == '__main__':img = cv.imread("nimg.ws.126.jpg", 0)print(np.min(img))print(np.max(img))cv.imshow("img", img)print(change_grey_level(img, 3))cv.imshow("changed img", change_grey_level(img, 3))print("success")cv.waitKey(0)cv.destroyAllWindows()

中间的res[i, j] = lev * 255 / (level - 1) 最开始每太想明白,最下层一定灰度是0,所以0-255重新分配n个level,最小的 level一定是0,那么其他的 level平分255,所以阶跃是255/(level-1.)

基于python的数字图像处理--学习笔记(二)相关推荐

  1. 基于python的数字图像处理--学习笔记(三)

    基于python的数字图像处理--学习笔记(三) 前言 一.灰度拉伸 二.幂律(伽马)变换 三.对数变换 前言 进入冈萨雷斯的第三章内容,并用python实现功能.我更改了代码源,之前找到太烂了,代码 ...

  2. 数字图像处理学习笔记 二 数字图像基础

    目录 一 .人类视觉成像 二.数字图像处理的基本概念和操作 三.数学工具介绍 一 .人类视觉成像 1.眼睛上两类光感受器:锥状体和杆状体 锥状体: 数量在600-700万之间,主要位于视网膜的中间部分 ...

  3. python自动化办公脚本下载-基于python实现自动化办公学习笔记二

    word文件 (1)读word文件 import win32com import win32com.client def readWordFile(path): # 调用系统word功能,可以处理do ...

  4. 数字图像处理学习笔记(四)——数字图像的内插、度量、表示与质量

    数字图像处理(Digital Image Processing)是通过计算机对图像进行去除噪声.增强.复原.分割.提取特征等处理的方法和技术.本专栏将以学习笔记形式对数字图像处理的重点基础知识进行总结 ...

  5. 数字图像处理学习笔记(二):SIFT(尺度不变特征变换)算法

    数字图像处理学习笔记(二):SIFT(尺度不变特征变换)算法 一.概述: 提到特征点算法,首先就是大名鼎鼎的SIFT算法了.SIFT的全称是Scale Invariant Feature Transf ...

  6. 数字图像处理学习笔记 六 彩色图像处理

    目录 (一)彩色模型介绍 1.1 RGB模型 1.2 CMY.CMYK模型 1.3 HSI彩色模型 1.4 HSV模型 1.5 YCbCr 彩色空间 (二)伪彩色图像处理 (三)全彩色图像处理及彩色变 ...

  7. 数字图像处理学习笔记(一):特征检测和匹配概述

    数字图像处理学习笔记(一):特征检测和匹配概述 参考博客: 特征点的匹配 SIFT特征详解 数字图像处理学习笔记(二):SIFT(尺度不变特征变换)算法 1.特征点概述 如何高效且准确的匹配出两个不同 ...

  8. 数字图像处理学习笔记(八)——图像增强处理方法之点处理

    数字图像处理(Digital Image Processing)是通过计算机对图像进行去除噪声.增强.复原.分割.提取特征等处理的方法和技术.本专栏将以学习笔记形式对数字图像处理的重点基础知识进行总结 ...

  9. 数字图像处理学习笔记(十)——空间滤波

    数字图像处理(Digital Image Processing)是通过计算机对图像进行去除噪声.增强.复原.分割.提取特征等处理的方法和技术.本专栏将以学习笔记形式对数字图像处理的重点基础知识进行总结 ...

  10. 数字图像处理学习笔记(三):ORB算法(尺度不变特征变换)Oriented FAST and Rotated BRIEF

    数字图像处理学习笔记(三):ORB算法(尺度不变特征变换)Oriented FAST and Rotated BRIEF 一.概述 参考:特征点匹配+特征检测方法汇总 ORB的全称是Oriented ...

最新文章

  1. 阿里巴巴加大IT人才引进 只为捍卫云计算市场地位
  2. 解决Subquery returns more than 1 rowsql查询错误
  3. SDUT_2116 数据结构实验之链表一:顺序建立链表
  4. ARP***绑定 linux/windows 下解决方案
  5. ListView图片不显示 Application.EnableVisualStyle bug
  6. VTK:PolyData之PointNormals
  7. 修改Hybris Administration console管理员默认登录密码
  8. C语言函数不能返回局部变量的地址
  9. 命令行分析java线程CPU占用
  10. Spring-data-jpa中用@ColumnTransformer注解加密,中文乱码问题(数据库正常,在java代码和页面中乱码)
  11. 另类保存微信公众平台历史消息的方法 - 星标消息
  12. python3安装后怎么开启_python3.10 如何下载安装?
  13. 深入分析linux内核源码
  14. 将通达信的背景设置成白色
  15. php怎么画五星红旗,php基于GD库画五星红旗的方法,phpgd库五星红旗
  16. 程序员确实更容易秃....
  17. go 学习笔记之咬文嚼字带你弄清楚 defer 延迟函数
  18. 橘子与萝卜同食容易导致甲状腺肿(图)
  19. win7锁屏背景壁纸修改
  20. wifi底层学习之路:三,Linux内核子系统mac80211

热门文章

  1. 泰勒公式(泰勒展开式)通俗+本质详解
  2. 动画专业考一级计算机选什么语种,想成为一名出色的动画设计师吗?去美国纽约视觉艺术学院读计算机动画与视觉效果本科专业准没错!...
  3. Tensorflow 版本切换与 slim 问题
  4. Spring Boot 2 基础篇学习笔记
  5. 【linux】正点原子linux教程学习
  6. 狂神说——CSS3最新教程快速入门通俗易懂
  7. Linux三个网络监视器之《三》——vnstat
  8. Java后端防止频繁请求、重复提交
  9. ttk python_python GUI ttk库 -- Apple的学习笔记
  10. win10计算机升级系统,微软Win10升级助手