开机启动脚本现在会尝试从github上更新当前分支的最新代码并编译运行。
This commit is contained in:
@@ -46,4 +46,4 @@ ELSE ()
|
|||||||
MESSAGE(STATUS "Unsupport platform: ${CMAKE_SYSTEM_NAME}")
|
MESSAGE(STATUS "Unsupport platform: ${CMAKE_SYSTEM_NAME}")
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
|
||||||
ADD_CUSTOM_TARGET(bind-monitor COMMAND "${PROJECT_SOURCE_DIR}/tools/bind-monitor.sh" "${PROJECT_SOURCE_DIR}" "${CMAKE_BINARY_DIR}")
|
ADD_CUSTOM_TARGET(create-startup COMMAND "${PROJECT_SOURCE_DIR}/tools/create-startup.sh" "${PROJECT_SOURCE_DIR}" "${CMAKE_BINARY_DIR}")
|
||||||
@@ -28,31 +28,90 @@ bool rectangleContainPoint(cv::RotatedRect rectangle, cv::Point2f point)
|
|||||||
double indicator = pointPolygonTest(contour,point,true);
|
double indicator = pointPolygonTest(contour,point,true);
|
||||||
return indicator >= 0;
|
return indicator >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Todo: 下面的函数可以有性能优化,暂时未做。
|
/// Todo: 下面的函数可以有性能优化,暂时未做。
|
||||||
static double nonZeroRateOfRotateRect(const cv::Mat &bin, const cv::RotatedRect &rorect){
|
static double nonZeroRateOfRotateRect(const cv::Mat &bin, const cv::RotatedRect &rotrect){
|
||||||
auto rect = rorect.boundingRect();
|
auto rect = rotrect.boundingRect();
|
||||||
if(rect.x < 0 || rect.y < 0 || rect.x+rect.width > bin.cols || rect.y+rect.height > bin.rows){
|
if(rect.x < 0 || rect.y < 0 || rect.x+rect.width > bin.cols || rect.y+rect.height > bin.rows){
|
||||||
// cout << "break" << endl;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
auto roi=bin(rect);
|
auto roi=bin(rect);
|
||||||
int cnt=0;
|
int cnt=0;
|
||||||
for(int r=0; r<roi.rows; r++){
|
for(int r=0; r<roi.rows; r++){
|
||||||
for(int c=0; c<roi.cols; c++){
|
for(int c=0; c<roi.cols; c++){
|
||||||
if(rectangleContainPoint(rorect, cv::Point(c+rect.x, r+rect.y))){
|
if(rectangleContainPoint(rotrect, cv::Point(c+rect.x, r+rect.y))){
|
||||||
if(roi.at<uint8_t>(r, c)){
|
if(roi.at<uint8_t>(r, c)){
|
||||||
cnt++;
|
cnt++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return double(cnt) / rorect.size.area();
|
return double(cnt) / rotrect.size.area();
|
||||||
|
}
|
||||||
|
|
||||||
|
int linePointX(const cv::Point2f &p1, const cv::Point2f &p2, int y){
|
||||||
|
return (p2.x-p1.x)/(p2.y-p1.y)*(y-p1.y)+p1.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
///Todo: 性能优化后的函数。(暂时还有点问题)
|
||||||
|
static double nonZeroRateOfRotateRect_opt(const cv::Mat &bin, const cv::RotatedRect &rotrect){
|
||||||
|
int cnt=0;
|
||||||
|
cv::Point2f corners[4];
|
||||||
|
rotrect.points(corners);
|
||||||
|
sort(corners, &corners[4], [](cv::Point2f p1, cv::Point2f p2){
|
||||||
|
return p1.y < p2.y;
|
||||||
|
});
|
||||||
|
// for(int r=corners[0].y; r<corners[3].y; r++){
|
||||||
|
// int val[]={
|
||||||
|
// linePointX(corners[0],corners[1], r),
|
||||||
|
// linePointX(corners[0],corners[2], r),
|
||||||
|
// linePointX(corners[1],corners[3], r),
|
||||||
|
// linePointX(corners[2],corners[3], r),
|
||||||
|
// };
|
||||||
|
// for(int c=val[1]; c<val[2]; c++){
|
||||||
|
// if(bin.at<uint8_t >(c, r)){
|
||||||
|
// cnt++;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
for(int r=corners[0].y; r<corners[1].y; r++){
|
||||||
|
auto start = min(linePointX(corners[0],corners[1], r), linePointX(corners[0],corners[2], r))-1;
|
||||||
|
auto end = max(linePointX(corners[0],corners[1], r), linePointX(corners[0],corners[2], r))+1;
|
||||||
|
if(start<0 || end>640) return 0;
|
||||||
|
for(int c=start; c<end; c++){
|
||||||
|
if(bin.at<uint8_t >(c, r)){
|
||||||
|
cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(int r=corners[1].y; r<corners[2].y; r++){
|
||||||
|
auto start = min(linePointX(corners[0],corners[2], r), linePointX(corners[1],corners[3], r))-1;
|
||||||
|
auto end = max(linePointX(corners[0],corners[2], r), linePointX(corners[1],corners[3], r))+1;
|
||||||
|
if(start<0 || end>640) return 0;
|
||||||
|
for(int c=start; c<end; c++){
|
||||||
|
if(bin.at<uint8_t >(r, c)){
|
||||||
|
cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(int r=corners[2].y; r<corners[3].y; r++){
|
||||||
|
auto start = min(linePointX(corners[1],corners[3], r), linePointX(corners[2],corners[3], r))-1;
|
||||||
|
auto end = max(linePointX(corners[1],corners[3], r), linePointX(corners[2],corners[3], r))+1;
|
||||||
|
if(start<0 || end>640) return 0;
|
||||||
|
for(int c=start; c<end; c++){
|
||||||
|
if(bin.at<uint8_t >(c, r)){
|
||||||
|
cnt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return double(cnt) / rotrect.size.area();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isValidLightBlob(const cv::Mat &bin, const cv::RotatedRect &rect){
|
static bool isValidLightBlob(const cv::Mat &bin, const cv::RotatedRect &rect){
|
||||||
return (lw_rate(rect) > 1.5) &&
|
return (lw_rate(rect) > 1.8) &&
|
||||||
// (rect.size.width*rect.size.height < 3000) &&
|
// (rect.size.width*rect.size.height < 3000) &&
|
||||||
(rect.size.width*rect.size.height > 1) &&
|
(rect.size.width*rect.size.height > 1) &&
|
||||||
|
// (nonZeroRateOfRotateRect_opt(bin, rect) > 0.8);
|
||||||
(nonZeroRateOfRotateRect(bin, rect) > 0.8);
|
(nonZeroRateOfRotateRect(bin, rect) > 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +129,7 @@ static bool findLightBlobs(const cv::Mat &src, LightBlobs &light_blobs) {
|
|||||||
}else if(src.type() == CV_8UC1){
|
}else if(src.type() == CV_8UC1){
|
||||||
src_gray = src.clone();
|
src_gray = src.clone();
|
||||||
}
|
}
|
||||||
|
LightBlobs all;
|
||||||
std::vector<std::vector<cv::Point> > light_contours;
|
std::vector<std::vector<cv::Point> > light_contours;
|
||||||
cv::findContours(src_gray, light_contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
|
cv::findContours(src_gray, light_contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
|
||||||
|
|
||||||
@@ -79,12 +138,13 @@ static bool findLightBlobs(const cv::Mat &src, LightBlobs &light_blobs) {
|
|||||||
if(isValidLightBlob(src_gray, rect)){
|
if(isValidLightBlob(src_gray, rect)){
|
||||||
light_blobs.emplace_back(rect);
|
light_blobs.emplace_back(rect);
|
||||||
}
|
}
|
||||||
|
all.emplace_back(rect);
|
||||||
}
|
}
|
||||||
|
showContours("all", src, all);
|
||||||
return light_blobs.size() >= 2;
|
return light_blobs.size() >= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool angelJudge(const LightBlob &light_blob_i, const LightBlob &light_blob_j) {
|
bool angelJudge(const LightBlob &light_blob_i, const LightBlob &light_blob_j) {
|
||||||
|
|
||||||
float angle_i = light_blob_i.rect.size.width > light_blob_i.rect.size.height ? light_blob_i.rect.angle :
|
float angle_i = light_blob_i.rect.size.width > light_blob_i.rect.size.height ? light_blob_i.rect.angle :
|
||||||
light_blob_i.rect.angle - 90;
|
light_blob_i.rect.angle - 90;
|
||||||
float angle_j = light_blob_j.rect.size.width > light_blob_j.rect.size.height ? light_blob_j.rect.angle :
|
float angle_j = light_blob_j.rect.size.width > light_blob_j.rect.size.height ? light_blob_j.rect.angle :
|
||||||
@@ -93,7 +153,6 @@ bool angelJudge(const LightBlob &light_blob_i, const LightBlob &light_blob_j) {
|
|||||||
}
|
}
|
||||||
bool heightJudge(const LightBlob &light_blob_i, const LightBlob &light_blob_j) {
|
bool heightJudge(const LightBlob &light_blob_i, const LightBlob &light_blob_j) {
|
||||||
cv::Point2f centers = light_blob_i.rect.center - light_blob_j.rect.center;
|
cv::Point2f centers = light_blob_i.rect.center - light_blob_j.rect.center;
|
||||||
|
|
||||||
return abs(centers.y)<30;
|
return abs(centers.y)<30;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,38 +160,18 @@ bool lengthJudge(const LightBlob &light_blob_i, const LightBlob &light_blob_j) {
|
|||||||
double side_length;
|
double side_length;
|
||||||
cv::Point2f centers = light_blob_i.rect.center - light_blob_j.rect.center;
|
cv::Point2f centers = light_blob_i.rect.center - light_blob_j.rect.center;
|
||||||
side_length = sqrt(centers.ddot(centers));
|
side_length = sqrt(centers.ddot(centers));
|
||||||
// std::cout << "side:" << side_length << " length:" << light_blob_i.length << std::endl;
|
|
||||||
return (side_length / light_blob_i.length < 6 && side_length / light_blob_i.length > 0.5);
|
return (side_length / light_blob_i.length < 6 && side_length / light_blob_i.length > 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool lengthRatioJudge(const LightBlob &light_blob_i, const LightBlob &light_blob_j) {
|
bool lengthRatioJudge(const LightBlob &light_blob_i, const LightBlob &light_blob_j) {
|
||||||
// std::cout << "i:" << light_blob_i.length << " j:" << light_blob_j.length << std::endl;
|
|
||||||
return (light_blob_i.length / light_blob_j.length < 2
|
return (light_blob_i.length / light_blob_j.length < 2
|
||||||
&& light_blob_i.length / light_blob_j.length > 0.5);
|
&& light_blob_i.length / light_blob_j.length > 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isCoupleLight(const LightBlob &light_blob_i, const LightBlob &light_blob_j, uint8_t enemy_color) {
|
bool isCoupleLight(const LightBlob &light_blob_i, const LightBlob &light_blob_j, uint8_t enemy_color) {
|
||||||
if(light_blob_i.BlobColor != enemy_color || light_blob_j.BlobColor != enemy_color){
|
return light_blob_i.BlobColor == enemy_color &&
|
||||||
return false;
|
light_blob_j.BlobColor == enemy_color &&
|
||||||
}
|
lengthRatioJudge(light_blob_i, light_blob_j) &&
|
||||||
if(!lengthRatioJudge(light_blob_i, light_blob_j)){
|
|
||||||
// std::cout << "lengthRatioJudge" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(!lengthJudge(light_blob_i, light_blob_j)){
|
|
||||||
// std::cout << "lengthJudge" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(!heightJudge(light_blob_i, light_blob_j)){
|
|
||||||
// std::cout << "heightJudge" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(!angelJudge(light_blob_i, light_blob_j)){
|
|
||||||
// std::cout << "angelJudge" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
return lengthRatioJudge(light_blob_i, light_blob_j) &&
|
|
||||||
lengthJudge(light_blob_i, light_blob_j) &&
|
lengthJudge(light_blob_i, light_blob_j) &&
|
||||||
heightJudge(light_blob_i, light_blob_j) &&
|
heightJudge(light_blob_i, light_blob_j) &&
|
||||||
angelJudge(light_blob_i, light_blob_j);
|
angelJudge(light_blob_i, light_blob_j);
|
||||||
@@ -156,8 +195,8 @@ static bool findArmorBoxes(LightBlobs &light_blobs, std::vector<cv::Rect2d> &arm
|
|||||||
double min_x, min_y, max_x, max_y;
|
double min_x, min_y, max_x, max_y;
|
||||||
min_x = fmin(rect_left.x, rect_right.x) - 4;
|
min_x = fmin(rect_left.x, rect_right.x) - 4;
|
||||||
max_x = fmax(rect_left.x + rect_left.width, rect_right.x + rect_right.width) + 4;
|
max_x = fmax(rect_left.x + rect_left.width, rect_right.x + rect_right.width) + 4;
|
||||||
min_y = fmin(rect_left.y, rect_right.y) - 4;
|
min_y = fmin(rect_left.y, rect_right.y) - 0.3*(rect_left.height+rect_right.height)/2.0;
|
||||||
max_y = fmax(rect_left.y + rect_left.height, rect_right.y + rect_right.height) + 4;
|
max_y = fmax(rect_left.y + rect_left.height, rect_right.y + rect_right.height) + 0.3*(rect_left.height+rect_right.height)/2.0;
|
||||||
if (min_x < 0 || max_x > 640 || min_y < 0 || max_y > 480) {
|
if (min_x < 0 || max_x > 640 || min_y < 0 || max_y > 480) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -216,14 +255,18 @@ int prior_red[] = {0, 2, 3, 4, 1, 5, 7, 8, 9, 6};
|
|||||||
int prior_blue[]= {5, 7, 8, 9, 6, 0, 2, 3, 4, 1};
|
int prior_blue[]= {5, 7, 8, 9, 6, 0, 2, 3, 4, 1};
|
||||||
|
|
||||||
bool ArmorFinder::stateSearchingTarget(cv::Mat &src) {
|
bool ArmorFinder::stateSearchingTarget(cv::Mat &src) {
|
||||||
cv::Mat split, src_bin;
|
cv::Mat split, src_bin/*, edge*/;
|
||||||
LightBlobs light_blobs, light_blobs_, light_blobs_real;
|
LightBlobs light_blobs, light_blobs_, light_blobs_real;
|
||||||
std::vector<cv::Rect2d> armor_boxes, boxes_number[10];
|
std::vector<cv::Rect2d> armor_boxes, boxes_number[10];
|
||||||
armor_box = cv::Rect2d(0,0,0,0);
|
armor_box = cv::Rect2d(0,0,0,0);
|
||||||
|
|
||||||
cv::cvtColor(src, src_gray, CV_BGR2GRAY);
|
cv::cvtColor(src, src_gray, CV_BGR2GRAY);
|
||||||
|
// cv::Canny(src_gray, edge, 100, 150);
|
||||||
|
// src_gray -= edge;
|
||||||
|
// cv::imshow("minus", src_gray);
|
||||||
// pipelineLightBlobPreprocess(src_gray);
|
// pipelineLightBlobPreprocess(src_gray);
|
||||||
cv::threshold(src_gray, src_bin, 170, 255, CV_THRESH_BINARY);
|
cv::threshold(src_gray, src_bin, 170, 255, CV_THRESH_BINARY);
|
||||||
|
imagePreProcess(src_bin);
|
||||||
// imshow("gray bin", src_bin);
|
// imshow("gray bin", src_bin);
|
||||||
if(!findLightBlobs(src_bin, light_blobs)){
|
if(!findLightBlobs(src_bin, light_blobs)){
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
22
main.cpp
22
main.cpp
@@ -32,7 +32,7 @@ mcu_data mcuData = {
|
|||||||
ARMOR_STATE,
|
ARMOR_STATE,
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
ENEMY_BLUE,
|
ENEMY_RED,
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
@@ -57,8 +57,8 @@ int main(int argc, char *argv[]) {
|
|||||||
WrapperHead *video_armor=nullptr;
|
WrapperHead *video_armor=nullptr;
|
||||||
WrapperHead *video_energy=nullptr;
|
WrapperHead *video_energy=nullptr;
|
||||||
if (from_camera) {
|
if (from_camera) {
|
||||||
video_armor = new CameraWrapper(0/*, "armor"*/);
|
video_armor = new CameraWrapper(0, "armor");
|
||||||
// video_energy = new CameraWrapper(1, "energy");
|
video_energy = new CameraWrapper(1, "energy");
|
||||||
} else {
|
} else {
|
||||||
// string armor_video, energy_video;
|
// string armor_video, energy_video;
|
||||||
// lastVideo(armor_video, PROJECT_DIR"/armor_video/");
|
// lastVideo(armor_video, PROJECT_DIR"/armor_video/");
|
||||||
@@ -66,7 +66,7 @@ int main(int argc, char *argv[]) {
|
|||||||
// lastVideo(energy_video, PROJECT_DIR"/energy_video/");
|
// lastVideo(energy_video, PROJECT_DIR"/energy_video/");
|
||||||
// video_energy = new VideoWrapper(energy_video);
|
// video_energy = new VideoWrapper(energy_video);
|
||||||
video_armor = new VideoWrapper("/home/sjturm/Desktop/valid_video/armor/65.avi");
|
video_armor = new VideoWrapper("/home/sjturm/Desktop/valid_video/armor/65.avi");
|
||||||
// video_energy = new VideoWrapper("/home/sjturm/Desktop/valid_video/energy/121.avi");
|
video_energy = new VideoWrapper("/home/sjturm/Desktop/valid_video/energy/121.avi");
|
||||||
}
|
}
|
||||||
if (video_armor->init()) {
|
if (video_armor->init()) {
|
||||||
LOGM("video_armor source initialization successfully.");
|
LOGM("video_armor source initialization successfully.");
|
||||||
@@ -75,13 +75,13 @@ int main(int argc, char *argv[]) {
|
|||||||
delete video_armor;
|
delete video_armor;
|
||||||
video_armor = nullptr;
|
video_armor = nullptr;
|
||||||
}
|
}
|
||||||
// if (video_energy->init()) {
|
if (video_energy->init()) {
|
||||||
// LOGM("video_energy source initialization successfully.");
|
LOGM("video_energy source initialization successfully.");
|
||||||
// } else {
|
} else {
|
||||||
// LOGW("video_energy source unavailable!");
|
LOGW("video_energy source unavailable!");
|
||||||
// delete video_energy;
|
delete video_energy;
|
||||||
// video_energy = nullptr;
|
video_energy = nullptr;
|
||||||
// }
|
}
|
||||||
|
|
||||||
Mat energy_src, armor_src;
|
Mat energy_src, armor_src;
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
|
|||||||
@@ -139,7 +139,7 @@
|
|||||||
GetLocalTime(&te); \
|
GetLocalTime(&te); \
|
||||||
LOGM(tag": %dms", (te.wSecond-ts.wSecond)*1000+(te.wMilliseconds-ts.wMilliseconds)); \
|
LOGM(tag": %dms", (te.wSecond-ts.wSecond)*1000+(te.wMilliseconds-ts.wMilliseconds)); \
|
||||||
}while (0)
|
}while (0)
|
||||||
#else
|
#elif defined(Linux)
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#define CNT_TIME(tag, codes, ...) do{ \
|
#define CNT_TIME(tag, codes, ...) do{ \
|
||||||
static timeval ts, te; \
|
static timeval ts, te; \
|
||||||
@@ -148,7 +148,10 @@
|
|||||||
gettimeofday(&te, NULL); \
|
gettimeofday(&te, NULL); \
|
||||||
LOGM(tag": %.1lfms", (te.tv_sec-ts.tv_sec)*1000.0+(te.tv_usec-ts.tv_usec)/1000.0); \
|
LOGM(tag": %.1lfms", (te.tv_sec-ts.tv_sec)*1000.0+(te.tv_usec-ts.tv_usec)/1000.0); \
|
||||||
}while (0)
|
}while (0)
|
||||||
#endif
|
#else
|
||||||
|
#warning "Unsupport plantform for CNT_TIME"
|
||||||
|
#define CNT_TIME(tag, codes, ...) codes
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
#define CNT_TIME(tag, codes, ...) codes
|
#define CNT_TIME(tag, codes, ...) codes
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -75,10 +75,10 @@ bool CameraWrapper::init() {
|
|||||||
LOGM("successfully loaded %s!", filepath);
|
LOGM("successfully loaded %s!", filepath);
|
||||||
#elif defined(Linux)
|
#elif defined(Linux)
|
||||||
CameraSetAeState(h_camera, false);
|
CameraSetAeState(h_camera, false);
|
||||||
CameraSetExposureTime(h_camera, 8*1000);
|
CameraSetExposureTime(h_camera, 10*1000);
|
||||||
CameraSetAnalogGain(h_camera, 16);
|
CameraSetAnalogGain(h_camera, 20);
|
||||||
if(mode == 0){
|
if(mode == 0){
|
||||||
CameraSetGain(h_camera, 100, 131, 113);
|
CameraSetGain(h_camera, 100, 130, 112);
|
||||||
CameraSetLutMode(h_camera, LUTMODE_PRESET);
|
CameraSetLutMode(h_camera, LUTMODE_PRESET);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
7
tools/bind-monitor.sh → tools/create-startup.sh
Normal file → Executable file
7
tools/bind-monitor.sh → tools/create-startup.sh
Normal file → Executable file
@@ -1,5 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
echo "#!/bin/bash" > $2/monitor-run
|
echo "#!/bin/bash" > $2/startup-run
|
||||||
echo "gnome-terminal -- bash -c \"echo sjturm | sudo -S $1/tools/monitor.sh \\\"$2/run --run-with-camera --save-video --wait-uart\\\"\"" >> $2/monitor-run
|
echo "$1/tools/auto-pull.sh" >> $2/startup-run
|
||||||
chmod +x $2/monitor-run
|
echo "gnome-terminal -- bash -c \"echo sjturm | sudo -S $1/tools/monitor.sh \\\"$2/run --run-with-camera --save-video --wait-uart\\\"\"" >> $2/startup-run
|
||||||
|
chmod +x $2/startup-run
|
||||||
Reference in New Issue
Block a user