一份粗略的学习笔记
第五章
5.1 图像变换的作用
- (通俗的讲)当然是有些东西在原来的图像中比较不明显所以才要变换,突出特征才好做特征提取或者处理啊。
- 图像的特征更为突出;原来无法直接观测的特征直接显现出来;需要提取图像中的特征,便于后续处理和图像理解
- 变换包括:平移,旋转,放缩,镜像,距离变换,Log-Polar变换(将直角坐标变换成极坐标,可用于全景展开)…
5.2 灰度直方图
- 直方图的概念,以及如果给定一个直方图怎样通过这个直方图判断图像质量(从这一点总可以知道该怎样修图了吧)
- 直方图均衡化(原理过多,不想阐述,具体看书,这一块完全可以写一篇了)
- 局部直方图均衡化
5.3 hough变换
- 原理:直线y = kx + b 对应着一组k,b(直角坐标系),同时对应着一个p和theta(极坐标系)
5.4 实战演练
# 图像的几何变换
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread("lena.png")
# cv2.imshow("lena.png",img)
gray= cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# 实现图像的翻转和改变大小
w,h =img.shape[0:2]
resized = cv2.resize(img,(int(w/4),int(h/2)))
flipped = cv2.flip(img,-1) # 后面的-1也可以取1,0,代表不同方向的翻转
# cv2.imshow("resized",resized)
# cv2.imshow("flipped",flipped)
# 距离变换
# 要先二值化,使用threshold函数
ret,thr =cv2.threshold(gray,100,255,cv2.THRESH_OTSU)
# print(ret)
# cv2.imshow("thr",thr) # 二值化后的图像
dist =cv2.distanceTransform(thr,cv2.DIST_L2,cv2.DIST_MASK_3)
dist_norm = cv2.convertScaleAbs(dist)
# cv2.imshow("dist",dist_norm)
# Log-Polar变换
center =(w/2,h/2)
maxRadius =0.7*min(center)
M= w/cv2.log(maxRadius) # 尺度
print(maxRadius,M[0])
log_polar = cv2.logPolar(img,center,M[0]*0.8,cv2.INTER_LINEAR + cv2.WARP_FILL_OUTLIERS)
# cv2.imshow("log_polar",log_polar)
# 灰度直方图和直方图均衡化
# plt.hist(gray.ravel(),256,[0,256])
# plt.show()
# equa = cv2.equalizeHist(gray)
# cv2.imshow("equa",equa)
# 实现hough变换
edges = cv2.Canny(thr,50,150) # 用canny算子进行边缘检测
cv2.imshow("edges",edges)
disp_edge =cv2.cvtColor(edges,cv2.COLOR_GRAY2BGR)
# cv2.imshow("disp_edge",disp_edge)
lines = cv2.HoughLinesP(edges, 1, 1*np.pi/180, 10)
for line in lines:
for x1,y1,x2,y2 in line:
cv2.line(disp_edge,(x1,y1),(x2,y2),(0,255,0),1)
pass
print("Line count:",len(lines))
cv2.imshow("hough",disp_edge)
cv2.waitKey()
cv2.destroyAllWindows()
第六章
6.1 图像分割
- 原因,意义:分割后可以得到自己感兴趣的区域从而再继续进行分析
- 方法:基于图像灰度阈值;边界;聚类;区域;图论及概率模型;深度学习
6.2 灰度阈值分割
- 如果图像中的目标区域和背景区域灰度存在明显的差异(如下图),我们可以根据其灰度直方图对其选择一个合适的阈值进行划分为前景和背景,之后赋予不同的颜色(一般是设置为黑白二值图像)
- 当然,在OpenCV里面有相应的函数会帮我们选择一个最合适的阈值进行分割
- 大津算法:最佳阈值确定的最佳二分类应使类内方差最小,等同于类间方差最大(这个说法和模式识别里面的很像,详见齐敏的《模式识别导论》,在聚类的时候评价聚类效果也可以通过类内方差和类间方差、类间距离和类内距离来衡量)
- 大津算法求解一般是使用遍历方式,思想直接,实现速度快
- 局限性:无法很好地处理细小的噪声和渐变图片
图中的标题有乱码是因为matplotlib不支持汉字,ubuntu 解决方法参见博客 https://blog.csdn.net/sf9898/article/details/104176727
6.3 实战演练
- 检测图中的米粒
- 步骤:获得图像 --> 预处理 --> 基于灰度的阈值分割 --> over
代码
import cv2
import matplotlib.pyplot as plt
import copy
img=cv2.imread("rice.png")
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# 大津算法灰度阈值化
# bw为阈值分割后的图像(二值图像),thr是使用大津算法得到的阈值
thr,bw = cv2.threshold(gray,0,0xff,cv2.THRESH_OTSU)
# print(thr)
# cv2.imshow("bw",bw)
# plt.hist(gray.ravel(),256,[0,256])
# plt.show()
# 形态学开操作
element = cv2.getStructuringElement(cv2.MORPH_CROSS,(3,3))
bw = cv2.morphologyEx(bw, cv2.MORPH_OPEN, element)
seg = copy.deepcopy(bw)
# 计算轮廓
# hier是分割完的结果,分割完之后所有的轮廓存储在cnts
cnts,hier = cv2.findContours(seg,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
count =0
# 遍历所有的轮廓
for i in range(len(cnts),0,-1):
c = cnts[i-1]
# 计算面积,如果面积小于10则认为是噪声
area = cv2.contourArea(c)
if area <10:
continue
count = count +1
print("blob",i,":",area)
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(img,(x,y),(x+w,h+y),(0,255,0),1)
# 标记序号
cv2.putText(img,str(count),(x,y),cv2.FONT_HERSHEY_PLAIN,0.5,(0,255,0))
print("米粒数量:",count)
cv2.imshow("original",img)
cv2.imshow("processed",bw)
cv2.waitKey()
cv2.destroyAllWindows()
6.4 阈值化分割扩展方法
- 局部阈值分割(可有效解决照明不均的问题)
- 多阈值分割(二值化分割方法的扩展,可解决多值目标 的分割问题)
- 评价标准(在通信领域中有提到一个概念:信噪比,这里用了类似的概念)
第七章
7.1 基于区域的分割(区域生长法)
-
原理:从种子点开始,按照一定准则(如相邻像素灰度相似性)向周围扩散,将邻域相似像素加入区域中
-
广度优先,深度优先(like DFS,BFS)(下面的步骤即是广度优先)
-
图像的分裂合并:图像是二维的因此只需要四叉树,而涉及到三维空间时,则应是八叉树。
这个四叉树和八叉树是怎么来的?先说二维平面,拿一个正方形,将其分为等大的正方形,正方形数量尽可能少(必须分),你会怎么分?再说三维空间,取一个正方体,同理,分为等大的正方体,正方体数量尽可能少,怎么分?对于四叉树和八叉树有兴趣的可以去找博客看看。大致思路是这样,四叉树和八叉树的作用在于将图像分裂为等大的区域进行分析。
- 区域生长法基于相邻像素间的相似性,由种子像素逐步生长得到
- 分裂-合并基于图像块内在的相似性,通过不断分裂得到区域外边界,通过合并将不同块连接
7.2 分水岭分割
- 分水岭算法基本思想来源于自低向高漫水和修水坝。分割过程通过漫水和膨胀,按照不同灰度级迭代进行。
- 过分割问题:由于噪声点或其他干扰因素的存在,使用分水岭算法常常存在过的分割的现象,这是因为有很多局部极小值点的存在
- 解决方法:在初始时给marker。使用基于标记(mark)图像的分水岭算法,在这个漫水过程中,水平面都是从定义的marker开始的,这样就可以避免一些很小的噪声极值区域的分割。
7.3 实战演练
- 代码
# 使用opencv 实现区域漫水填充(区域生长法)
import cv2
import copy
import numpy as np
if __name__ == "__main__":
import sys
try:
fn = sys.argv[1]
except:
fn = 'fruits.png'
print("__doc__")
img = cv2.imread(cv2.samples.findFile(fn))
if img is None:
print("Fail to load image files: ",fn)
sys.exit()
h,w = img.shape[:2]
mask = np.zeros((h+2,w+2),np.uint8)
# seedPoint 漫水填充算法的起始点
seed_pt = None
fixed_range = True
connectivity = 4
def update(dummy = None):
if seed_pt is None :
cv2.imshow("floodfill",img)
return
flooded = img.copy()
mask[:] = 0
# lo hi对应cv2.floodFill参数loDiff,upDiff
lo = cv2.getTrackbarPos("lo","floodfill")
hi = cv2.getTrackbarPos('hi','floodfill')
flags = connectivity
if fixed_range :
flags |= cv2.FLOODFILL_FIXED_RANGE
cv2.floodFill(flooded,mask,seed_pt,(255,255,255),(lo,)*3,(hi,)*3,flags)
cv2.circle(flooded,seed_pt,2,(0,0,255),-1)
cv2.imshow("floodfill",flooded)
def onmouse(event,x,y,flags,param):
global seed_pt
if flags & cv2.EVENT_FLAG_LBUTTON:
seed_pt = x,y
update()
update()
# 将鼠标左键和要执行的函数相关联
cv2.setMouseCallback('floodfill',onmouse)
cv2.createTrackbar('lo','floodfill',20,255,update)
cv2.createTrackbar('hi','floodfill',20,255,update)
while True:
ch = cv2.waitKey()
if ch == 27:
break
# 分水岭分割
import cv2
import numpy as np
# cv2.watershed(image,markers)
img = cv2.imread("coins.png")
# cv2.imshow("img",img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# cv2.imshow("gray",gray)
# 使用大津算法二值化
ret ,thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# print(ret)
cv2.imshow("thresh",thresh)
# noise removal
kernel = np.ones((3,3),np.uint8)
# 用开运算去除小的白色噪点,使用膨胀运算确保背景与原图一致
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,iterations=2)
# cv2.imshow("opening",opening)
# sure background area
sure_bg = cv2.dilate(opening,kernel,iterations=3)
# cv2.imshow("sure_bg",sure_bg)
# 用距离变换计算每个硬币中心,采用腐蚀变换可以实现类似的效果
dist_transform = cv2.distanceTransform(opening,cv2.DIST_L2,5)
# cv2.imshow("dist_transform",dist_transform)
# 使用阈值化可以得到分离的硬币
dist_ret,sure_fg = cv2.threshold(dist_transform,0.7*dist_transform.max(),255,0)
# Finding unknown region
sure_fg = np.uint8(sure_fg)
# cv2.imshow("sure_fg",sure_fg)
# 通过相减得到相连接硬币的不确定区域(有待分割计算)
unknown = cv2.subtract(sure_bg,sure_fg)
cv2.imshow("unknown",unknown)
# 用cv2.connectedComponents给每个连通区域做标记,标记从0开始,0为背景
ret,markers = cv2.connectedComponents(sure_fg)
markers = markers + 1
# mark the region of unknown with zero
markers[unknown==255] = 0
# 用 cv2.watershed做分水岭分割
markers = cv2.watershed(img,markers)
# 分割线用蓝色标记
img[markers == -1] = [255,0,0]
cv2.imshow("img",img)
cv2.waitKey()
cv2.destroyAllWindows()
第八章
8.1 图像表示与描述
- 用什么特征来描述目标和怎样获得(测量)这些特征
- 链码(差分链码)
8.2 复杂特征
- 最下包围矩形
- 离心率:最长弦A与垂直于最长弦的B的长度比(主次轴之比)
- 椭圆拟合
- 投影
- 多边形拟合
- 不变矩
8.3 实战演练
- 代码
# 第八章实战演练
import cv2
import copy
img = cv2.imread("shape.png")
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# 阈值化
ret,thresh = cv2.threshold(gray,1,255,cv2.THRESH_BINARY)
# cv2.imshow("img",img)
# cv2.imshow("thresh",thresh)
# 进一步得到图像轮廓
cnts,hier = cv2.findContours(thresh,cv2.RETR_LIST,cv2.CHAIN_APPROX_NONE)
count =0
# 用于显示的图像
disp_poly = img.copy()
disp_elli = img.copy()
for i in range(len(cnts)):
# 进行多边形逼近,得到多边形角点
c = cnts[i]
poly = cv2.approxPolyDP(c,5,True)
cv2.polylines(disp_poly,[poly],True,(255,255,255),2)
# 拟合椭圆
if len(c) > 5:
ellipse = cv2.fitEllipse(c)
cv2.ellipse(disp_elli,ellipse,(255,255,255),2)
# 计算Hu不变矩
area = cv2.contourArea(c)
length = cv2.arcLength(c,True)
# 计算普通矩
moments = cv2.moments(c)
hu = cv2.HuMoments(moments)
print(i+1,":","length=%.1f"%length,"area=",area,"m00=%.3f,m01=%.3f,m10=%.3f,m11=%.3f"%(hu[0],hu[1],hu[2],hu[3]))
# 得到对应区域包围框,并在左上角显示序号(轮廓包围矩形)
x,y,w,h = cv2.boundingRect(c)
cv2.putText(disp_poly,str(i+1),(x,y),cv2.FONT_HERSHEY_PLAIN,0.8,(0xff,0xff,0xff))
cv2.imshow("disp_poly",disp_poly) # 多边形
cv2.imshow("disp_elli",disp_elli) # 椭圆拟合
cv2.waitKey()
cv2.destroyAllWindows()