5.12.2. 实例¶
############################################
# Loading Images
############################################
import cv2
import matplotlib.pyplot as plt
import numpy as np
# 使用urllib库下载图片并保存到本地
import urllib
url = 'https://img.zhaoweiguo.com/uPic/2023/12/kgMCBx.png'
with urllib.request.urlopen(url) as url_response:
s = url_response.read()
with open('image.png', 'wb') as file:
file.write(s)
# Loading Images
im = cv2.imread('./image.png')
print(im.shape)
plt.imshow(im)
# convert it to black-and-white
bw_im = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
print(bw_im.shape)
plt.imshow(bw_im)
############################################
# Image Processing
############################################
# enhance the image a little bit with the idea of thresholding
# 使用阈值的概念稍微增强图像
# 对灰度图像进行模糊处理。这里,模糊核的大小是3x3
im = cv2.blur(bw_im,(3,3))
# 对图像进行自适应阈值处理。
# 这里的阈值是按照图像区域的平均灰度值确定的。结果图像的二值化形式被取反,以便于后续处理。
im = cv2.adaptiveThreshold(im, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
cv2.THRESH_BINARY_INV, 5, 4)
# 对图像进行中值模糊。这可以去除一些噪声。
im = cv2.medianBlur(im, 3)
# 使用 Otsu 的阈值分割方法对图像进行阈值处理。该方法会自动计算全局阈值。
_,im = cv2.threshold(im, 0, 255, cv2.THRESH_OTSU)
# 使用 cv2.GaussianBlur 方法对图像进行高斯模糊。模糊核的大小是 3x3,标准差是 0
im = cv2.GaussianBlur(im, (3,3), 0)
# 再次使用 Otsu 的阈值分割方法对图像进行阈值处理
_,im = cv2.threshold(im, 0, 255, cv2.THRESH_OTSU)
plt.imshow(im)
# 将图像转换为一组单个点的坐标。可以使用特征提取技术(例如 SIFT、SURF 或 ORB)来做到这一点
orb = cv2.ORB_create(5000)
f, d = orb.detectAndCompute(im,None)
print(f"First 5 points: { [f[i].pt for i in range(5)]}")
# plot all points to make sure we got things right
def plot_dots(dots):
img = np.zeros((250,500))
for x in dots:
cv2.circle(img,(int(x[0]),int(x[1])),3,(255,0,0))
plt.imshow(img)
pts = [x.pt for x in f]
plot_dots(pts)
# To separate individual characters, we need to know the bounding box of the whole text.
# To find it out, we can just compute min and max coordinates:
# 定位有图像的边界,把没有图像的部分去掉
min_x, min_y, max_x, max_y = [int(f([z[i] for z in pts])) for f in (min,max) for i in (0,1)]
min_y+=13
plt.imshow(im[min_y:max_y,min_x:max_x])
off = 5
src_pts = np.array([(min_x-off,min_y-off),(min_x-off,max_y+off),
(max_x+off,min_y-off),(max_x+off,max_y+off)])
w = int(max_x-min_x+off*2)
h = int(max_y-min_y+off*2)
dst_pts = np.array([(0,0),(0,h),(w,0),(w,h)])
# 计算从源区域到目标区域的变换矩阵
ho, m = cv2.findHomography(src_pts,dst_pts)
# 将图像进行裁剪和矫正
# 1. im 是要裁剪的图像。
# 2. ho 是变换矩阵。
# 3. (w,h) 是裁剪区域的宽和高。
# 4. trim 是裁剪后的图像。
trim = cv2.warpPerspective(im, ho, (w,h))
plt.imshow(trim)
# 图像对齐良好后,将其切成碎片
def display_images(l,titles=None,fontsize=12):
n=len(l)
fig,ax = plt.subplots(1,n)
for i,im in enumerate(l):
ax[i].imshow(im)
ax[i].axis('off')
if titles is not None:
ax[i].set_title(titles[i],fontsize=fontsize)
fig.set_size_inches(fig.get_size_inches()*n)
plt.tight_layout()
plt.show()
char_h = 36
char_w = 24
def slice(img):
dy,dx = img.shape
y = 0
while y+char_h<dy:
x=0
while x+char_w<dx:
# Skip empty lines
# 检查子图像是否为空
# 求子图像中所有像素值的最大值。如果最大值大于 0,则表示子图像不为空
if np.max(img[y:y+char_h,x:x+char_w])>0:
# 生成子图像
yield img[y:y+char_h,x:x+char_w]
x+=char_w
y+=char_h
sliced = list(slice(trim))
display_images(sliced)
############################################
# Motion Detection using Frame Difference
# 使用帧差进行运动检测
############################################
# 意义
# 检测视频流上的运动是一项非常频繁的任务。例如,它允许我们在监控摄像头发生某些事情时收到警报。
# 如果我们想了解相机上发生了什么,我们可以使用神经网络来具体操作
# 但是当我们知道某些事情正在发生时,再使用神经网络会更节省资源。
# 原理
# 运动检测的主要思想很简单。如果相机是固定的,那么相机的帧应该彼此非常相似
# 由于帧表示为数组,因此只需在随后的两帧中减去这些数组,我们就会得到像素差异
# 对于静态帧来说,像素差异应该很低,一旦图像中有实质性的运动,像素差异就会变得更高。
import cv2
import urllib
# 视频的URL地址
video_url = 'https://video.zhaoweiguo.com/2023/12/motionvideo.mp4'
# 创建一个VideoCapture对象并打开视频文件
vid = cv2.VideoCapture(video_url)
## 上面一句是如下代码的简写
# import requests
# response = requests.get(video_url)
# with open('motionvideo.mp4', 'wb') as f:
# f.write(response.content)
# vid = cv2.VideoCapture('motionvideo.mp4')
c = 0
frames = []
while vid.isOpened():
ret, frame = vid.read()
if not ret:
break
frames.append(frame)
c+=1
vid.release()
print(f"Total frames: {c}")
# 输出: Total frames: 876
from matplotlib import pyplot as plt
# 图像对齐良好后,将其切成碎片
def display_images(l,titles=None,fontsize=12):
n=len(l)
fig,ax = plt.subplots(1,n)
for i,im in enumerate(l):
ax[i].imshow(im)
ax[i].axis('off')
if titles is not None:
ax[i].set_title(titles[i],fontsize=fontsize)
fig.set_size_inches(fig.get_size_inches()*n)
plt.tight_layout()
plt.show()
display_images(frames[::150]) # 每隔 150 个元素取一个元素
# len(frames[::150]) => 6
## 实时显示每一帧
#if not vid.isOpened():
# print('无法打开视频文件')
# exit()
#while True:
# ret, frame = vid.read()
# if not ret:
# break
# # 在窗口中显示视频帧
# cv2.imshow('Video', frame)
# # 等待按下'q'键退出循环
# if cv2.waitKey(1) & 0xFF == ord('q'):
# break
## 释放VideoCapture对象和窗口
#vid.release()
#cv2.destroyAllWindows()
# 颜色对于运动检测并不那么重要,因此我们将所有帧转换为灰度
# 然后,我们将计算帧差异,并绘制它们的范数,以直观地看到正在进行的活动量
import numpy as np
bwframes = [cv2.cvtColor(x,cv2.COLOR_BGR2GRAY) for x in frames]
diffs = [(p2-p1) for p1,p2 in zip(bwframes[:-1],bwframes[1:])]
diff_amps = np.array([np.linalg.norm(x) for x in diffs])
plt.plot(diff_amps)
display_images(bwframes[::150],titles=diff_amps[::150]) # 打印灰度版截图
display_images(diffs[::150],titles=diff_amps[::150]) # 打印帧差异截图
# 为了消除一些噪音,我们还将使用移动平均线功能让图形平滑一些
# np.convolve 计算两个序列的卷积
# 卷积是两个序列的乘积,然后对结果进行滑动平均
def moving_average(x, w):
return np.convolve(x, np.ones(w), 'valid') / w
threshold = 13000
plt.plot(moving_average(diff_amps,10))
plt.axhline(y=threshold, color='r', linestyle='-')
# 使用 np.where 找出变化量高于阈值的帧,并提取一个长度超过 30 帧的连续帧序列
active_frames = np.where(diff_amps>threshold)[0]
def subsequence(seq, min_length=30):
ss = []
for i, x in enumerate(seq[:-1]):
ss.append(x)
if x+1 != seq[i+1]:
if len(ss)>min_length:
return ss
ss.clear()
sub = subsequence(active_frames)
print(sub)
# 显示图像
plt.imshow(frames[(sub[0]+sub[-1])//2])
# OpenCV 出于历史原因在 BGR 颜色空间中加载图像,而 matplotlib 使用更传统的 RGB 颜色顺序。
# 大多数情况下,在加载图像后立即将图像转换为 RGB 是有意义的。
plt.imshow(cv2.cvtColor(frames[(sub[0]+sub[-1])//2],cv2.COLOR_BGR2RGB))
############################################
# Extract Motion using Optical Flow
# 使用光流提取运动
############################################
# 计算帧之间的密集光流
flows = [cv2.calcOpticalFlowFarneback(f1, f2, None, 0.5, 3, 15, 3, 5, 1.2, 0)
for f1,f2 in zip(bwframes[:-1],bwframes[1:])]
flows[0].shape # (180, 320, 2)
# 流具有帧的维度和 2 个通道,对应于光流矢量的 x 和 y 分量
# 在 2D 中显示光流有点挑战性
# 将光流转换为极坐标,那么我们将为每个像素获得两个分量:方向和强度
# 用像素强度来表示强度,用不同的颜色来表示方向
# 在 HSV(色相饱和度值)色彩空间中创建图像,其中色相将由方向定义,值 - 由强度定义,饱和度为 255
def flow_to_hsv(flow):
hsvImg = np.zeros((flow.shape[0],flow.shape[1],3),dtype=np.uint8)
mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1])
hsvImg[..., 0] = 0.5 * ang * 180 / np.pi
hsvImg[..., 1] = 255
hsvImg[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
return cv2.cvtColor(hsvImg, cv2.COLOR_HSV2BGR)
start = sub[0]
stop = sub[-1]
print(start,stop)
frms = [flow_to_hsv(x) for x in flows[start:stop]]
display_images(frms[::25])
# 在这些帧中,绿色对应于向左移动,而蓝色对应于向右移动
# 光流可以成为得出关于一般运动方向的结论的好工具。
# 例如,如果您看到帧中的所有像素都或多或少地向一个方向移动 - 您可以得出结论,相机正在移动,并尝试对此进行补偿。