技术标签: python 计算机视觉 人工智能 数字图像处理 opencv
本文是利用opencv python 的美颜(磨皮,大眼)实现。
磨皮使用的是导向滤波进行磨皮。关于导向滤波的介绍,可以看我的另一篇文章导向滤波与opencv python实现
可以先使用椭圆肤色模型对皮肤部分进行提取,得到掩膜数组,然后利用位运算将滤波后的图像与原图结合,这样就做到了对背景的保护。
def YCrCb_ellipse_model(img):
skinCrCbHist = np.zeros((256,256), dtype= np.uint8)
cv2.ellipse(skinCrCbHist, (113,155),(23,25), 43, 0, 360, (255,255,255), -1) #绘制椭圆弧线
YCrCb = cv2.cvtColor(img, cv2.COLOR_BGR2YCR_CB) #转换至YCrCb空间
(Y,Cr,Cb) = cv2.split(YCrCb) #拆分出Y,Cr,Cb值
skin = np.zeros(Cr.shape, dtype = np.uint8) #掩膜
(x,y) = Cr.shape
for i in range(0, x):
for j in range(0, y):
if skinCrCbHist [Cr[i][j], Cb[i][j]] > 0: #若不在椭圆区间中
skin[i][j] = 255
res = cv2.bitwise_and(img,img, mask = skin)
return skin,res
可以对掩膜数组进行一次开运算,消除细小区域。
def mopi(img):
skin,_ = ellipse_skin_model.YCrCb_ellipse_model(img)#获得皮肤的掩膜数组
#进行一次开运算
kernel = np.ones((3,3),dtype=np.uint8)
skin = cv2.erode(skin,kernel=kernel)
skin = cv2.dilate(skin,kernel=kernel)
img1 = guided_filter(img/255.0,img/255.0,10,eps=0.001)*255
img1 = np.array(img1,dtype=np.uint8)
img1 = cv2.bitwise_and(img1,img1,mask=skin)#将皮肤与背景分离
skin = cv2.bitwise_not(skin)
img1 = cv2.add(img1,cv2.bitwise_and(img,img,mask=skin))#磨皮后的结果与背景叠加
fig, ax = plt.subplots(1, 2)
# ax[0].imshow(skin)
# ax[1].imshow(img1[:, :, ::-1])
# plt.show()
return img1
得到的结果如下图
在美颜中,要想进行大眼,瘦脸等操作,需要首先提取出人脸的关键点才可以做坐标变换。
这里,使用了Google的mediapipe进行检测。一般来说应该使用dlib的,但我折腾一个小时没装上。。。
import cv2
import mediapipe as mp
mp_face_detection = mp.solutions.face_detection
mp_drawing = mp.solutions.drawing_utils
def get_face_key_point(img):
with mp_face_detection.FaceDetection(model_selection=1, min_detection_confidence=0.5) as face_detection:
results = face_detection.process(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
if not results.detections:
return None
annotated_image = img.copy()
r,w,c =img.shape
for detection in results.detections:
left_eye =mp_face_detection.get_key_point(detection, mp_face_detection.FaceKeyPoint.LEFT_EYE)
right_eye =mp_face_detection.get_key_point(detection, mp_face_detection.FaceKeyPoint.RIGHT_EYE)
left_eye_pos = [int(left_eye.x * w), int(left_eye.y * r)]
right_eye_pos = [int(right_eye.x * w), int(right_eye.y * r)]
return left_eye_pos,right_eye_pos
这里参考了论文Interactive Image Warping的局部缩放算法。
局部缩放公式:
由上公式可得,当 r r r等于 r m a x r_{max} rmax时,该式等于 r r r。
如果 a > 0 a>0 a>0,即r乘上一个0~1的数,即变换后的点相较于变换前的点相对于圆心外扩了。也就是局部放大。
如果 a < 0 a<0 a<0,即r乘上一个大于1的数,即变换后的点相较于变换前的点相对于圆心内缩了。也就是局部缩小。
根据上述公式,我们可以得到一个原图与输出图的像素坐标映射,通过双线性插值可以得到输出图的对应像素值。
局部放大函数:
def Local_scaling_warps(img,cx,cy,r_max,a):
img1 = np.copy(img)
for y in range(cy-r_max,cy+r_max+1):
d = int(math.sqrt(r_max**2-(y-cy)**2))
x0 = cx-d
x1 = cx+d
for x in range(x0,x1+1):
r = math.sqrt((x-cx)**2 + (y-cy)**2)
for c in range(3):
if r<=r_max:
vector_c = np.array([cx, cy])
vector_r =np.array([x,y])-vector_c
f_s = (1-((r/r_max-1)**2)*a)
vector_u = vector_c+f_s*vector_r#原坐标
img1[y][x][c] = bilinear_interpolation(img,vector_u,c)
return img1
双线性插值:
def bilinear_interpolation(img,vector_u,c):
ux,uy=vector_u
x1,x2 = int(ux),int(ux+1)
y1,y2 = int(uy),int(uy+1)
# f_x_y1 = (x2-ux)/(x2-x1)*img[x1][y1]+(ux-x1)/(x2-x1)*img[x2][y1]
# f_x_y2 = (x2 - ux) / (x2 - x1) * img[x1][y2] + (ux - x1) / (x2 - x1) * img[x2][y2]
f_x_y1 = (x2-ux)/(x2-x1)*img[y1][x1][c]+(ux-x1)/(x2-x1)*img[y1][x2][c]
f_x_y2 = (x2 - ux) / (x2 - x1) * img[y2][x1][c] + (ux - x1) / (x2 - x1) * img[y2][x2][c]
f_x_y = (y2-uy)/(y2-y1)*f_x_y1+(uy-y1)/(y2-y1)*f_x_y2
return int(f_x_y)
这里利用pyqt5简单设计了一个界面。
其中下方的两个滑块分别对应放缩的系数 a a a和放缩范围 r r r。
左边显示的是原图,右边是处理后的图像。可见小姐姐皮肤变好了,眼睛也变大了。
将参数稍微调下,会变得有点阴间哈哈哈。
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 19:05
# @Author : shuoshuo
# @File : main.py
# @Project : 美颜
import sys
import cv2
import numpy as np
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QImage
from PIL import Image,ImageQt
from qtpy import QtGui
import beautify
import face_detect
class My_win(QWidget):
def __init__(self,img):
super().__init__()
self.initUI(img)
def initUI(self,img):
self.r_max = 40
self.a = 0.5
self.left_eye_pos,self.right_eye_pos = face_detect.get_face_key_point(img)
self.img = img.copy()
self.mopi_btn = QPushButton("磨皮",self)
self.big_eye_btn = QPushButton("大眼",self)
self.save_btn = QPushButton("保存",self)
self.a_slider =QSlider(Qt.Horizontal)
self.a_slider.setRange(1,99)
self.a_slider.setSingleStep(5)
self.a_slider.setToolTip('大眼程度')
self.r_slider = QSlider(Qt.Horizontal)
self.r_slider.setRange(1,99)
self.r_slider.setSingleStep(5)
self.r_slider.setToolTip('大眼范围')
self.r_slider.valueChanged.connect(self.r_change)
self.a_slider.valueChanged.connect(self.a_change)
self.big_eye_btn.clicked.connect(self.big_eye)
self.mopi_btn.clicked.connect(self.mopi)
self.save_btn.clicked.connect(self.save)
self.original_pic = QLabel(self)
self.result_pic = QLabel(self)
self.button_box = QHBoxLayout()
# self.button_box.addStretch(1)
self.button_box.addWidget(self.mopi_btn)
self.button_box.addWidget(self.big_eye_btn)
self.button_box.addWidget(self.save_btn)
self.slider_box = QHBoxLayout()
self.slider_box.addWidget(self.a_slider)
self.slider_box.addWidget(self.r_slider)
self.hbox = QHBoxLayout()
self.hbox.addStretch(1)
self.hbox.addWidget(self.original_pic)
self.hbox.addWidget(self.result_pic)
self.vbox = QVBoxLayout()
self.vbox.addStretch(1)
self.vbox.addLayout(self.button_box)
self.vbox.addLayout(self.slider_box)
self.vbox.addLayout(self.hbox)
self.setLayout(self.vbox )
self.show_img(img,frame=self.original_pic)
self.show_img(self.img,frame=self.result_pic)
# self.setGeometry(100,100,800,600)
self.setWindowTitle("拉闸美颜")
self.show()
def show_img(self,img,frame):
r,w,c = img.shape
image = QtGui.QImage(img.data, w, r,w*3, QtGui.QImage.Format_RGB888).rgbSwapped()
frame.setPixmap(QtGui.QPixmap.fromImage(image))
# frame.setGeometry(pos[0],pos[1],r,w)
def big_eye(self):
self.result = beautify.big_eye(self.img,r_max=self.r_max,a=self.a,
left_eye_pos=self.left_eye_pos,right_eye_pos=self.right_eye_pos)
self.result = np.array(self.result, dtype=np.uint8)
self.show_img(self.result, frame=self.result_pic)
def mopi(self):
self.img = beautify.mopi(self.img)
# self.img = np.array(self.img*255,dtype=np.uint8)
self.show_img(self.img, frame=self.result_pic)
def a_change(self):
self.a = self.a_slider.value()/100
self.big_eye()
def r_change(self):
self.r_max = self.r_slider.value()
self.big_eye()
def save(self):
cv2.imwrite('output.jpg',self.result)
if __name__ == '__main__':
app = QApplication(sys.argv)
img = cv2.imread('img.png')
# img = cv2.resize(img,(400,300))
ex = My_win(img)
sys.exit(app.exec_())
# -*- coding: utf-8 -*-
# @Time : 2022/10/5 22:48
# @Author : shuoshuo
# @File : beautify.py
# @Project : 美颜
import math
import cv2
import numpy as np
import matplotlib.pyplot as plt
import face_detect
import ellipse_skin_model
def bilinear_interpolation(img,vector_u,c):
ux,uy=vector_u
x1,x2 = int(ux),int(ux+1)
y1,y2 = int(uy),int(uy+1)
# f_x_y1 = (x2-ux)/(x2-x1)*img[x1][y1]+(ux-x1)/(x2-x1)*img[x2][y1]
# f_x_y2 = (x2 - ux) / (x2 - x1) * img[x1][y2] + (ux - x1) / (x2 - x1) * img[x2][y2]
f_x_y1 = (x2-ux)/(x2-x1)*img[y1][x1][c]+(ux-x1)/(x2-x1)*img[y1][x2][c]
f_x_y2 = (x2 - ux) / (x2 - x1) * img[y2][x1][c] + (ux - x1) / (x2 - x1) * img[y2][x2][c]
f_x_y = (y2-uy)/(y2-y1)*f_x_y1+(uy-y1)/(y2-y1)*f_x_y2
return int(f_x_y)
def Local_scaling_warps(img,cx,cy,r_max,a):
img1 = np.copy(img)
for y in range(cy-r_max,cy+r_max+1):
d = int(math.sqrt(r_max**2-(y-cy)**2))
x0 = cx-d
x1 = cx+d
for x in range(x0,x1+1):
r = math.sqrt((x-cx)**2 + (y-cy)**2) #求出当前位置的半径
for c in range(3):
vector_c = np.array([cx, cy])
vector_r =np.array([x,y])-vector_c
f_s = (1-((r/r_max-1)**2)*a)
vector_u = vector_c+f_s*vector_r#原坐标
img1[y][x][c] = bilinear_interpolation(img,vector_u,c)
return img1
def big_eye(img,r_max,a,left_eye_pos=None,right_eye_pos=None):
img0 = img.copy()
if left_eye_pos==None or right_eye_pos==None:
left_eye_pos,right_eye_pos=face_detect.get_face_key_point(img)
img0 = cv2.circle(img0,left_eye_pos,radius=10,color=(0,0,255))
img0 = cv2.circle(img0,right_eye_pos,radius=10,color=(0,0,255))
img= Local_scaling_warps(img,left_eye_pos[0],left_eye_pos[1],r_max=r_max,a=a)
img = Local_scaling_warps(img,right_eye_pos[0],right_eye_pos[1],r_max=r_max,a=a)
return img
def guided_filter(I,p,win_size,eps):
assert I.any() <=1 and p.any()<=1
mean_I = cv2.blur(I,(win_size,win_size))
mean_p = cv2.blur(p,(win_size,win_size))
corr_I = cv2.blur(I*I,(win_size,win_size))
corr_Ip = cv2.blur(I*p,(win_size,win_size))
var_I = corr_I-mean_I*mean_I
cov_Ip = corr_Ip - mean_I*mean_p
a = cov_Ip/(var_I+eps)
b = mean_p-a*mean_I
mean_a = cv2.blur(a,(win_size,win_size))
mean_b = cv2.blur(b,(win_size,win_size))
q = mean_a*I + mean_b
return q
def mopi(img):
skin,_ = ellipse_skin_model.YCrCb_ellipse_model(img)#获得皮肤的掩膜数组
#进行一次开运算
kernel = np.ones((3,3),dtype=np.uint8)
skin = cv2.erode(skin,kernel=kernel)
skin = cv2.dilate(skin,kernel=kernel)
img1 = guided_filter(img/255.0,img/255.0,10,eps=0.001)*255
img1 = np.array(img1,dtype=np.uint8)
img1 = cv2.bitwise_and(img1,img1,mask=skin)#将皮肤与背景分离
skin = cv2.bitwise_not(skin)
img1 = cv2.add(img1,cv2.bitwise_and(img,img,mask=skin))#磨皮后的结果与背景叠加
# fig, ax = plt.subplots(1, 2)
# ax[0].imshow(skin)
# ax[1].imshow(img1[:, :, ::-1])
# plt.show()
return img1
if __name__ == '__main__':
img = cv2.imread('img.png')
img1 = mopi(img)
big_eye(img1,r_max=40,a=0.8)
# -*- coding: utf-8 -*-
# @Time : 2022/10/18 21:54
# @Author : shuoshuo
# @File : ellipse_skin_model.py
# @Project : 美颜
import numpy as np
import cv2
def YCrCb_ellipse_model(img):
skinCrCbHist = np.zeros((256,256), dtype= np.uint8)
cv2.ellipse(skinCrCbHist, (113,155),(23,25), 43, 0, 360, (255,255,255), -1) #绘制椭圆弧线
YCrCb = cv2.cvtColor(img, cv2.COLOR_BGR2YCR_CB) #转换至YCrCb空间
(Y,Cr,Cb) = cv2.split(YCrCb) #拆分出Y,Cr,Cb值
skin = np.zeros(Cr.shape, dtype = np.uint8) #掩膜
(x,y) = Cr.shape
for i in range(0, x):
for j in range(0, y):
if skinCrCbHist [Cr[i][j], Cb[i][j]] > 0: #若不在椭圆区间中
skin[i][j] = 255
res = cv2.bitwise_and(img,img, mask = skin)
return skin,res
if __name__ == '__main__':
pass
import cv2
import mediapipe as mp
mp_face_detection = mp.solutions.face_detection
mp_drawing = mp.solutions.drawing_utils
def get_face_key_point(img):
with mp_face_detection.FaceDetection(model_selection=1, min_detection_confidence=0.5) as face_detection:
results = face_detection.process(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
if not results.detections:
return None
annotated_image = img.copy()
r,w,c =img.shape
for detection in results.detections:
left_eye =mp_face_detection.get_key_point(detection, mp_face_detection.FaceKeyPoint.LEFT_EYE)
right_eye =mp_face_detection.get_key_point(detection, mp_face_detection.FaceKeyPoint.RIGHT_EYE)
left_eye_pos = [int(left_eye.x * w), int(left_eye.y * r)]
right_eye_pos = [int(right_eye.x * w), int(right_eye.y * r)]
return left_eye_pos,right_eye_pos
# -*- coding: utf-8 -*-
# @Time : 2022/10/14 19:05
# @Author : shuoshuo
# @File : main.py
# @Project : 美颜
import sys
import cv2
import numpy as np
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QImage
from PIL import Image,ImageQt
from qtpy import QtGui
import beautify
import face_detect
class My_win(QWidget):
def __init__(self,img):
super().__init__()
self.initUI(img)
def initUI(self,img):
self.r_max = 40
self.a = 0.5
self.left_eye_pos,self.right_eye_pos = face_detect.get_face_key_point(img)
self.img = img.copy()
self.mopi_btn = QPushButton("磨皮",self)
self.big_eye_btn = QPushButton("大眼",self)
self.save_btn = QPushButton("保存",self)
self.a_slider =QSlider(Qt.Horizontal)
self.a_slider.setRange(1,99)
self.a_slider.setSingleStep(5)
self.a_slider.setToolTip('大眼程度')
self.r_slider = QSlider(Qt.Horizontal)
self.r_slider.setRange(1,99)
self.r_slider.setSingleStep(5)
self.r_slider.setToolTip('大眼范围')
self.r_slider.valueChanged.connect(self.r_change)
self.a_slider.valueChanged.connect(self.a_change)
self.big_eye_btn.clicked.connect(self.big_eye)
self.mopi_btn.clicked.connect(self.mopi)
self.save_btn.clicked.connect(self.save)
self.original_pic = QLabel(self)
self.result_pic = QLabel(self)
self.button_box = QHBoxLayout()
# self.button_box.addStretch(1)
self.button_box.addWidget(self.mopi_btn)
self.button_box.addWidget(self.big_eye_btn)
self.button_box.addWidget(self.save_btn)
self.slider_box = QHBoxLayout()
self.slider_box.addWidget(self.a_slider)
self.slider_box.addWidget(self.r_slider)
self.hbox = QHBoxLayout()
self.hbox.addStretch(1)
self.hbox.addWidget(self.original_pic)
self.hbox.addWidget(self.result_pic)
self.vbox = QVBoxLayout()
self.vbox.addStretch(1)
self.vbox.addLayout(self.button_box)
self.vbox.addLayout(self.slider_box)
self.vbox.addLayout(self.hbox)
self.setLayout(self.vbox )
self.show_img(img,frame=self.original_pic)
self.show_img(self.img,frame=self.result_pic)
# self.setGeometry(100,100,800,600)
self.setWindowTitle("拉闸美颜")
self.show()
def show_img(self,img,frame):
r,w,c = img.shape
image = QtGui.QImage(img.data, w, r,w*3, QtGui.QImage.Format_RGB888).rgbSwapped()
frame.setPixmap(QtGui.QPixmap.fromImage(image))
# frame.setGeometry(pos[0],pos[1],r,w)
def big_eye(self):
self.result = beautify.big_eye(self.img,r_max=self.r_max,a=self.a,
left_eye_pos=self.left_eye_pos,right_eye_pos=self.right_eye_pos)
self.result = np.array(self.result, dtype=np.uint8)
self.show_img(self.result, frame=self.result_pic)
def mopi(self):
self.img = beautify.mopi(self.img)
# self.img = np.array(self.img*255,dtype=np.uint8)
self.show_img(self.img, frame=self.result_pic)
def a_change(self):
self.a = self.a_slider.value()/100
self.big_eye()
def r_change(self):
self.r_max = self.r_slider.value()
self.big_eye()
def save(self):
cv2.imwrite('output.jpg',self.result)
if __name__ == '__main__':
app = QApplication(sys.argv)
img = cv2.imread('img.png')
# img = cv2.resize(img,(400,300))
ex = My_win(img)
sys.exit(app.exec_())
文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr
文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc
文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8
文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束
文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求
文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname
文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include<stdio.h>#include<string.h>#include<stdlib.h>#include<malloc.h>#include<iostream>#include<stack>#include<queue>using namespace std;typed_二叉树的建立
文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码
文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词
文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限
文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定
文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland