diff --git a/anno.txt b/anno.txt new file mode 100644 index 0000000..8fd3b37 --- /dev/null +++ b/anno.txt @@ -0,0 +1,24 @@ +/home/cole/item-sort/positives/IMG_10208.jpg 1 4 3 80 110 +/home/cole/item-sort/positives/IMG_1155.jpg 1 1 1 30 32 +/home/cole/item-sort/positives/IMG_1184.jpg 1 1 1 34 30 +/home/cole/item-sort/positives/IMG_1287.jpg 1 2 2 35 29 +/home/cole/item-sort/positives/IMG_13351.jpg 1 2 2 165 75 +/home/cole/item-sort/positives/IMG_13857.jpg 1 2 5 90 142 +/home/cole/item-sort/positives/IMG_1472.jpg 1 1 1 43 29 +/home/cole/item-sort/positives/IMG_1584.jpg 1 1 1 45 31 +/home/cole/item-sort/positives/IMG_1683.jpg 1 1 0 48 32 +/home/cole/item-sort/positives/IMG_1764.jpg 1 2 1 38 39 +/home/cole/item-sort/positives/IMG_1776.jpg 1 1 0 45 36 +/home/cole/item-sort/positives/IMG_19530.jpg 1 4 4 120 149 +/home/cole/item-sort/positives/IMG_2200.jpg 1 1 0 46 42 +/home/cole/item-sort/positives/IMG_2772.jpg 1 0 0 83 32 +/home/cole/item-sort/positives/IMG_2784.jpg 1 1 1 46 55 +/home/cole/item-sort/positives/IMG_2800.jpg 1 1 1 53 48 +/home/cole/item-sort/positives/IMG_3285.jpg 1 1 1 71 43 +/home/cole/item-sort/positives/IMG_3604.jpg 1 1 0 104 33 +/home/cole/item-sort/positives/IMG_4550.jpg 1 1 1 127 32 + +/home/cole/item-sort/positives/IMG_4879.jpg 1 2 2 37 114 +/home/cole/item-sort/positives/IMG_7412.jpg 1 2 2 105 65 +/home/cole/item-sort/positives/IMG_7654.jpg 1 4 2 80 84 +/home/cole/item-sort/positives/IMG_7740.jpg 1 1 2 126 57 diff --git a/bg.txt b/bg.txt new file mode 100644 index 0000000..092373f --- /dev/null +++ b/bg.txt @@ -0,0 +1,38 @@ +negatives/IMG_10285.jpg +negatives/IMG_1190.jpg +negatives/IMG_1225.jpg +negatives/IMG_1443.jpg +negatives/IMG_1482.jpg +negatives/IMG_17202.jpg +negatives/IMG_18939.jpg +negatives/IMG_19860.jpg +negatives/IMG_2116.jpg +negatives/IMG_2408.jpg +negatives/IMG_2475.jpg +negatives/IMG_2548.jpg +negatives/IMG_2597.jpg +negatives/IMG_2700.jpg +negatives/IMG_27048.jpg +negatives/IMG_2805.jpg +negatives/IMG_28583.jpg +negatives/IMG_30940.jpg +negatives/IMG_35235.jpg +negatives/IMG_3599.jpg +negatives/IMG_36040.jpg +negatives/IMG_36400.jpg +negatives/IMG_3666.jpg +negatives/IMG_3840.jpg +negatives/IMG_3844.jpg +negatives/IMG_40176.jpg +negatives/IMG_42840.jpg +negatives/IMG_5041.jpg +negatives/IMG_5184.jpg +negatives/IMG_5402.jpg +negatives/IMG_5700.jpg +negatives/IMG_5820.jpg +negatives/IMG_6014.jpg +negatives/IMG_6016.jpg +negatives/IMG_6240.jpg +negatives/IMG_6435.jpg +negatives/IMG_7482.jpg +negatives/IMG_9800.jpg diff --git a/main.py b/main.py index 91d0bc6..532581a 100644 --- a/main.py +++ b/main.py @@ -66,6 +66,8 @@ def swap(a, b): ap = argparse.ArgumentParser() ap.add_argument("-i", "--image", required=True, help="path to the input image") +ap.add_argument("-c", "--cascade", required=True, + help="path to the cascade") ap.add_argument("-w", "--width", type=float, required=True, help="width of the left-most object in the image (in inches)") ap.add_argument("-n", "--number", type=int, required=False, @@ -112,7 +114,18 @@ cnts = imutils.grab_contours(cnts) pixelsPerMetric = None num = 0 - +cascade = cv2.CascadeClassifier() +if not cascade.load(cv2.samples.findFile(args2.cascade)): + print('--(!)Error loading face cascade') + exit(0) +screws = cascade.detectMultiScale(image) +frame = image.copy() +for (x,y,w,h) in screws: + center = (x + w//2, y + h//2) + frame = cv2.ellipse(frame, center, (w//2, h//2), 0, 0, 360, (255, 0, 255), 4) +if args2.show: + cv2.imshow("Item Sorter", frame) + cv2.waitKey(0) # Calibration loop for c in cnts: # if the contour is not sufficiently large, ignore it diff --git a/object_images/IMG_10285.jpg b/negatives/IMG_10285.jpg similarity index 100% rename from object_images/IMG_10285.jpg rename to negatives/IMG_10285.jpg diff --git a/object_images/IMG_1190.jpg b/negatives/IMG_1190.jpg similarity index 100% rename from object_images/IMG_1190.jpg rename to negatives/IMG_1190.jpg diff --git a/object_images/IMG_1225.jpg b/negatives/IMG_1225.jpg similarity index 100% rename from object_images/IMG_1225.jpg rename to negatives/IMG_1225.jpg diff --git a/object_images/IMG_1443.jpg b/negatives/IMG_1443.jpg similarity index 100% rename from object_images/IMG_1443.jpg rename to negatives/IMG_1443.jpg diff --git a/object_images/IMG_1482.jpg b/negatives/IMG_1482.jpg similarity index 100% rename from object_images/IMG_1482.jpg rename to negatives/IMG_1482.jpg diff --git a/object_images/IMG_17202.jpg b/negatives/IMG_17202.jpg similarity index 100% rename from object_images/IMG_17202.jpg rename to negatives/IMG_17202.jpg diff --git a/object_images/IMG_18939.jpg b/negatives/IMG_18939.jpg similarity index 100% rename from object_images/IMG_18939.jpg rename to negatives/IMG_18939.jpg diff --git a/object_images/IMG_19860.jpg b/negatives/IMG_19860.jpg similarity index 100% rename from object_images/IMG_19860.jpg rename to negatives/IMG_19860.jpg diff --git a/object_images/IMG_2116.jpg b/negatives/IMG_2116.jpg similarity index 100% rename from object_images/IMG_2116.jpg rename to negatives/IMG_2116.jpg diff --git a/object_images/IMG_2408.jpg b/negatives/IMG_2408.jpg similarity index 100% rename from object_images/IMG_2408.jpg rename to negatives/IMG_2408.jpg diff --git a/object_images/IMG_2475.jpg b/negatives/IMG_2475.jpg similarity index 100% rename from object_images/IMG_2475.jpg rename to negatives/IMG_2475.jpg diff --git a/object_images/IMG_2548.jpg b/negatives/IMG_2548.jpg similarity index 100% rename from object_images/IMG_2548.jpg rename to negatives/IMG_2548.jpg diff --git a/object_images/IMG_2597.jpg b/negatives/IMG_2597.jpg similarity index 100% rename from object_images/IMG_2597.jpg rename to negatives/IMG_2597.jpg diff --git a/object_images/IMG_2700.jpg b/negatives/IMG_2700.jpg similarity index 100% rename from object_images/IMG_2700.jpg rename to negatives/IMG_2700.jpg diff --git a/object_images/IMG_27048.jpg b/negatives/IMG_27048.jpg similarity index 100% rename from object_images/IMG_27048.jpg rename to negatives/IMG_27048.jpg diff --git a/object_images/IMG_2805.jpg b/negatives/IMG_2805.jpg similarity index 100% rename from object_images/IMG_2805.jpg rename to negatives/IMG_2805.jpg diff --git a/object_images/IMG_28583.jpg b/negatives/IMG_28583.jpg similarity index 100% rename from object_images/IMG_28583.jpg rename to negatives/IMG_28583.jpg diff --git a/object_images/IMG_30940.jpg b/negatives/IMG_30940.jpg similarity index 100% rename from object_images/IMG_30940.jpg rename to negatives/IMG_30940.jpg diff --git a/object_images/IMG_35235.jpg b/negatives/IMG_35235.jpg similarity index 100% rename from object_images/IMG_35235.jpg rename to negatives/IMG_35235.jpg diff --git a/object_images/IMG_3599.jpg b/negatives/IMG_3599.jpg similarity index 100% rename from object_images/IMG_3599.jpg rename to negatives/IMG_3599.jpg diff --git a/object_images/IMG_36040.jpg b/negatives/IMG_36040.jpg similarity index 100% rename from object_images/IMG_36040.jpg rename to negatives/IMG_36040.jpg diff --git a/object_images/IMG_36400.jpg b/negatives/IMG_36400.jpg similarity index 100% rename from object_images/IMG_36400.jpg rename to negatives/IMG_36400.jpg diff --git a/object_images/IMG_3666.jpg b/negatives/IMG_3666.jpg similarity index 100% rename from object_images/IMG_3666.jpg rename to negatives/IMG_3666.jpg diff --git a/object_images/IMG_3840.jpg b/negatives/IMG_3840.jpg similarity index 100% rename from object_images/IMG_3840.jpg rename to negatives/IMG_3840.jpg diff --git a/object_images/IMG_3844.jpg b/negatives/IMG_3844.jpg similarity index 100% rename from object_images/IMG_3844.jpg rename to negatives/IMG_3844.jpg diff --git a/object_images/IMG_40176.jpg b/negatives/IMG_40176.jpg similarity index 100% rename from object_images/IMG_40176.jpg rename to negatives/IMG_40176.jpg diff --git a/object_images/IMG_42840.jpg b/negatives/IMG_42840.jpg similarity index 100% rename from object_images/IMG_42840.jpg rename to negatives/IMG_42840.jpg diff --git a/object_images/IMG_5041.jpg b/negatives/IMG_5041.jpg similarity index 100% rename from object_images/IMG_5041.jpg rename to negatives/IMG_5041.jpg diff --git a/object_images/IMG_5184.jpg b/negatives/IMG_5184.jpg similarity index 100% rename from object_images/IMG_5184.jpg rename to negatives/IMG_5184.jpg diff --git a/object_images/IMG_5402.jpg b/negatives/IMG_5402.jpg similarity index 100% rename from object_images/IMG_5402.jpg rename to negatives/IMG_5402.jpg diff --git a/object_images/IMG_5700.jpg b/negatives/IMG_5700.jpg similarity index 100% rename from object_images/IMG_5700.jpg rename to negatives/IMG_5700.jpg diff --git a/object_images/IMG_5820.jpg b/negatives/IMG_5820.jpg similarity index 100% rename from object_images/IMG_5820.jpg rename to negatives/IMG_5820.jpg diff --git a/object_images/IMG_6014.jpg b/negatives/IMG_6014.jpg similarity index 100% rename from object_images/IMG_6014.jpg rename to negatives/IMG_6014.jpg diff --git a/object_images/IMG_6016.jpg b/negatives/IMG_6016.jpg similarity index 100% rename from object_images/IMG_6016.jpg rename to negatives/IMG_6016.jpg diff --git a/object_images/IMG_6240.jpg b/negatives/IMG_6240.jpg similarity index 100% rename from object_images/IMG_6240.jpg rename to negatives/IMG_6240.jpg diff --git a/object_images/IMG_6435.jpg b/negatives/IMG_6435.jpg similarity index 100% rename from object_images/IMG_6435.jpg rename to negatives/IMG_6435.jpg diff --git a/object_images/IMG_7482.jpg b/negatives/IMG_7482.jpg similarity index 100% rename from object_images/IMG_7482.jpg rename to negatives/IMG_7482.jpg diff --git a/object_images/IMG_9800.jpg b/negatives/IMG_9800.jpg similarity index 100% rename from object_images/IMG_9800.jpg rename to negatives/IMG_9800.jpg diff --git a/object_images/IMG_10208.jpg b/objects/IMG_10208.jpg similarity index 100% rename from object_images/IMG_10208.jpg rename to objects/IMG_10208.jpg diff --git a/object_images/IMG_10282.jpg b/objects/IMG_10282.jpg similarity index 100% rename from object_images/IMG_10282.jpg rename to objects/IMG_10282.jpg diff --git a/objects/IMG_10285.jpg b/objects/IMG_10285.jpg new file mode 100644 index 0000000..26101d5 Binary files /dev/null and b/objects/IMG_10285.jpg differ diff --git a/object_images/IMG_1155.jpg b/objects/IMG_1155.jpg similarity index 100% rename from object_images/IMG_1155.jpg rename to objects/IMG_1155.jpg diff --git a/object_images/IMG_1184.jpg b/objects/IMG_1184.jpg similarity index 100% rename from object_images/IMG_1184.jpg rename to objects/IMG_1184.jpg diff --git a/objects/IMG_1190.jpg b/objects/IMG_1190.jpg new file mode 100644 index 0000000..8d9b98e Binary files /dev/null and b/objects/IMG_1190.jpg differ diff --git a/objects/IMG_1225.jpg b/objects/IMG_1225.jpg new file mode 100644 index 0000000..aad6c84 Binary files /dev/null and b/objects/IMG_1225.jpg differ diff --git a/object_images/IMG_1287.jpg b/objects/IMG_1287.jpg similarity index 100% rename from object_images/IMG_1287.jpg rename to objects/IMG_1287.jpg diff --git a/object_images/IMG_13351.jpg b/objects/IMG_13351.jpg similarity index 100% rename from object_images/IMG_13351.jpg rename to objects/IMG_13351.jpg diff --git a/object_images/IMG_13857.jpg b/objects/IMG_13857.jpg similarity index 100% rename from object_images/IMG_13857.jpg rename to objects/IMG_13857.jpg diff --git a/objects/IMG_1443.jpg b/objects/IMG_1443.jpg new file mode 100644 index 0000000..04d2730 Binary files /dev/null and b/objects/IMG_1443.jpg differ diff --git a/object_images/IMG_1472.jpg b/objects/IMG_1472.jpg similarity index 100% rename from object_images/IMG_1472.jpg rename to objects/IMG_1472.jpg diff --git a/objects/IMG_1482.jpg b/objects/IMG_1482.jpg new file mode 100644 index 0000000..9b07950 Binary files /dev/null and b/objects/IMG_1482.jpg differ diff --git a/object_images/IMG_1584.jpg b/objects/IMG_1584.jpg similarity index 100% rename from object_images/IMG_1584.jpg rename to objects/IMG_1584.jpg diff --git a/object_images/IMG_1683.jpg b/objects/IMG_1683.jpg similarity index 100% rename from object_images/IMG_1683.jpg rename to objects/IMG_1683.jpg diff --git a/objects/IMG_17202.jpg b/objects/IMG_17202.jpg new file mode 100644 index 0000000..9728a59 Binary files /dev/null and b/objects/IMG_17202.jpg differ diff --git a/object_images/IMG_17290.jpg b/objects/IMG_17290.jpg similarity index 100% rename from object_images/IMG_17290.jpg rename to objects/IMG_17290.jpg diff --git a/object_images/IMG_1764.jpg b/objects/IMG_1764.jpg similarity index 100% rename from object_images/IMG_1764.jpg rename to objects/IMG_1764.jpg diff --git a/object_images/IMG_1776.jpg b/objects/IMG_1776.jpg similarity index 100% rename from object_images/IMG_1776.jpg rename to objects/IMG_1776.jpg diff --git a/object_images/IMG_17856.jpg b/objects/IMG_17856.jpg similarity index 100% rename from object_images/IMG_17856.jpg rename to objects/IMG_17856.jpg diff --git a/object_images/IMG_18156.jpg b/objects/IMG_18156.jpg similarity index 100% rename from object_images/IMG_18156.jpg rename to objects/IMG_18156.jpg diff --git a/object_images/IMG_18228.jpg b/objects/IMG_18228.jpg similarity index 100% rename from object_images/IMG_18228.jpg rename to objects/IMG_18228.jpg diff --git a/object_images/IMG_18360.jpg b/objects/IMG_18360.jpg similarity index 100% rename from object_images/IMG_18360.jpg rename to objects/IMG_18360.jpg diff --git a/objects/IMG_18939.jpg b/objects/IMG_18939.jpg new file mode 100644 index 0000000..370aaa0 Binary files /dev/null and b/objects/IMG_18939.jpg differ diff --git a/object_images/IMG_19530.jpg b/objects/IMG_19530.jpg similarity index 100% rename from object_images/IMG_19530.jpg rename to objects/IMG_19530.jpg diff --git a/objects/IMG_19860.jpg b/objects/IMG_19860.jpg new file mode 100644 index 0000000..31e15e3 Binary files /dev/null and b/objects/IMG_19860.jpg differ diff --git a/objects/IMG_2116.jpg b/objects/IMG_2116.jpg new file mode 100644 index 0000000..d08c697 Binary files /dev/null and b/objects/IMG_2116.jpg differ diff --git a/object_images/IMG_2200.jpg b/objects/IMG_2200.jpg similarity index 100% rename from object_images/IMG_2200.jpg rename to objects/IMG_2200.jpg diff --git a/objects/IMG_2408.jpg b/objects/IMG_2408.jpg new file mode 100644 index 0000000..2ccffcc Binary files /dev/null and b/objects/IMG_2408.jpg differ diff --git a/objects/IMG_2475.jpg b/objects/IMG_2475.jpg new file mode 100644 index 0000000..865645c Binary files /dev/null and b/objects/IMG_2475.jpg differ diff --git a/objects/IMG_2548.jpg b/objects/IMG_2548.jpg new file mode 100644 index 0000000..3f06bff Binary files /dev/null and b/objects/IMG_2548.jpg differ diff --git a/objects/IMG_2597.jpg b/objects/IMG_2597.jpg new file mode 100644 index 0000000..23979cc Binary files /dev/null and b/objects/IMG_2597.jpg differ diff --git a/objects/IMG_2700.jpg b/objects/IMG_2700.jpg new file mode 100644 index 0000000..6147eb6 Binary files /dev/null and b/objects/IMG_2700.jpg differ diff --git a/objects/IMG_27048.jpg b/objects/IMG_27048.jpg new file mode 100644 index 0000000..aaae660 Binary files /dev/null and b/objects/IMG_27048.jpg differ diff --git a/object_images/IMG_2772.jpg b/objects/IMG_2772.jpg similarity index 100% rename from object_images/IMG_2772.jpg rename to objects/IMG_2772.jpg diff --git a/object_images/IMG_2784.jpg b/objects/IMG_2784.jpg similarity index 100% rename from object_images/IMG_2784.jpg rename to objects/IMG_2784.jpg diff --git a/object_images/IMG_2800.jpg b/objects/IMG_2800.jpg similarity index 100% rename from object_images/IMG_2800.jpg rename to objects/IMG_2800.jpg diff --git a/objects/IMG_2805.jpg b/objects/IMG_2805.jpg new file mode 100644 index 0000000..a414255 Binary files /dev/null and b/objects/IMG_2805.jpg differ diff --git a/objects/IMG_28583.jpg b/objects/IMG_28583.jpg new file mode 100644 index 0000000..f8ebcda Binary files /dev/null and b/objects/IMG_28583.jpg differ diff --git a/objects/IMG_30940.jpg b/objects/IMG_30940.jpg new file mode 100644 index 0000000..4643e37 Binary files /dev/null and b/objects/IMG_30940.jpg differ diff --git a/object_images/IMG_3285.jpg b/objects/IMG_3285.jpg similarity index 100% rename from object_images/IMG_3285.jpg rename to objects/IMG_3285.jpg diff --git a/objects/IMG_35235.jpg b/objects/IMG_35235.jpg new file mode 100644 index 0000000..ccf1f29 Binary files /dev/null and b/objects/IMG_35235.jpg differ diff --git a/objects/IMG_3599.jpg b/objects/IMG_3599.jpg new file mode 100644 index 0000000..f45a1c0 Binary files /dev/null and b/objects/IMG_3599.jpg differ diff --git a/object_images/IMG_3604.jpg b/objects/IMG_3604.jpg similarity index 100% rename from object_images/IMG_3604.jpg rename to objects/IMG_3604.jpg diff --git a/objects/IMG_36040.jpg b/objects/IMG_36040.jpg new file mode 100644 index 0000000..1bbed65 Binary files /dev/null and b/objects/IMG_36040.jpg differ diff --git a/objects/IMG_36400.jpg b/objects/IMG_36400.jpg new file mode 100644 index 0000000..60cc39f Binary files /dev/null and b/objects/IMG_36400.jpg differ diff --git a/objects/IMG_3666.jpg b/objects/IMG_3666.jpg new file mode 100644 index 0000000..a6b136e Binary files /dev/null and b/objects/IMG_3666.jpg differ diff --git a/objects/IMG_3840.jpg b/objects/IMG_3840.jpg new file mode 100644 index 0000000..d63c839 Binary files /dev/null and b/objects/IMG_3840.jpg differ diff --git a/objects/IMG_3844.jpg b/objects/IMG_3844.jpg new file mode 100644 index 0000000..1baa94b Binary files /dev/null and b/objects/IMG_3844.jpg differ diff --git a/objects/IMG_40176.jpg b/objects/IMG_40176.jpg new file mode 100644 index 0000000..a46ce4e Binary files /dev/null and b/objects/IMG_40176.jpg differ diff --git a/objects/IMG_42840.jpg b/objects/IMG_42840.jpg new file mode 100644 index 0000000..76ca72c Binary files /dev/null and b/objects/IMG_42840.jpg differ diff --git a/object_images/IMG_4550.jpg b/objects/IMG_4550.jpg similarity index 100% rename from object_images/IMG_4550.jpg rename to objects/IMG_4550.jpg diff --git a/object_images/IMG_4674.jpg b/objects/IMG_4674.jpg similarity index 100% rename from object_images/IMG_4674.jpg rename to objects/IMG_4674.jpg diff --git a/object_images/IMG_4788.jpg b/objects/IMG_4788.jpg similarity index 100% rename from object_images/IMG_4788.jpg rename to objects/IMG_4788.jpg diff --git a/object_images/IMG_4879.jpg b/objects/IMG_4879.jpg similarity index 100% rename from object_images/IMG_4879.jpg rename to objects/IMG_4879.jpg diff --git a/object_images/IMG_49848.jpg b/objects/IMG_49848.jpg similarity index 100% rename from object_images/IMG_49848.jpg rename to objects/IMG_49848.jpg diff --git a/objects/IMG_5041.jpg b/objects/IMG_5041.jpg new file mode 100644 index 0000000..15680da Binary files /dev/null and b/objects/IMG_5041.jpg differ diff --git a/objects/IMG_5184.jpg b/objects/IMG_5184.jpg new file mode 100644 index 0000000..d574260 Binary files /dev/null and b/objects/IMG_5184.jpg differ diff --git a/object_images/IMG_52736.jpg b/objects/IMG_52736.jpg similarity index 100% rename from object_images/IMG_52736.jpg rename to objects/IMG_52736.jpg diff --git a/objects/IMG_5402.jpg b/objects/IMG_5402.jpg new file mode 100644 index 0000000..78d2a1b Binary files /dev/null and b/objects/IMG_5402.jpg differ diff --git a/objects/IMG_5700.jpg b/objects/IMG_5700.jpg new file mode 100644 index 0000000..d20d193 Binary files /dev/null and b/objects/IMG_5700.jpg differ diff --git a/objects/IMG_5820.jpg b/objects/IMG_5820.jpg new file mode 100644 index 0000000..a582995 Binary files /dev/null and b/objects/IMG_5820.jpg differ diff --git a/objects/IMG_6014.jpg b/objects/IMG_6014.jpg new file mode 100644 index 0000000..ab9f395 Binary files /dev/null and b/objects/IMG_6014.jpg differ diff --git a/objects/IMG_6016.jpg b/objects/IMG_6016.jpg new file mode 100644 index 0000000..3c609bd Binary files /dev/null and b/objects/IMG_6016.jpg differ diff --git a/objects/IMG_6240.jpg b/objects/IMG_6240.jpg new file mode 100644 index 0000000..a962e99 Binary files /dev/null and b/objects/IMG_6240.jpg differ diff --git a/objects/IMG_6435.jpg b/objects/IMG_6435.jpg new file mode 100644 index 0000000..6e9d8a3 Binary files /dev/null and b/objects/IMG_6435.jpg differ diff --git a/object_images/IMG_6716.jpg b/objects/IMG_6716.jpg similarity index 100% rename from object_images/IMG_6716.jpg rename to objects/IMG_6716.jpg diff --git a/object_images/IMG_6808.jpg b/objects/IMG_6808.jpg similarity index 100% rename from object_images/IMG_6808.jpg rename to objects/IMG_6808.jpg diff --git a/object_images/IMG_6975.jpg b/objects/IMG_6975.jpg similarity index 100% rename from object_images/IMG_6975.jpg rename to objects/IMG_6975.jpg diff --git a/object_images/IMG_7125.jpg b/objects/IMG_7125.jpg similarity index 100% rename from object_images/IMG_7125.jpg rename to objects/IMG_7125.jpg diff --git a/object_images/IMG_7315.jpg b/objects/IMG_7315.jpg similarity index 100% rename from object_images/IMG_7315.jpg rename to objects/IMG_7315.jpg diff --git a/object_images/IMG_7412.jpg b/objects/IMG_7412.jpg similarity index 100% rename from object_images/IMG_7412.jpg rename to objects/IMG_7412.jpg diff --git a/objects/IMG_7482.jpg b/objects/IMG_7482.jpg new file mode 100644 index 0000000..f87d727 Binary files /dev/null and b/objects/IMG_7482.jpg differ diff --git a/object_images/IMG_7654.jpg b/objects/IMG_7654.jpg similarity index 100% rename from object_images/IMG_7654.jpg rename to objects/IMG_7654.jpg diff --git a/object_images/IMG_7740.jpg b/objects/IMG_7740.jpg similarity index 100% rename from object_images/IMG_7740.jpg rename to objects/IMG_7740.jpg diff --git a/objects/IMG_9800.jpg b/objects/IMG_9800.jpg new file mode 100644 index 0000000..800a9fa Binary files /dev/null and b/objects/IMG_9800.jpg differ diff --git a/opencv-apps/CMakeLists.txt b/opencv-apps/CMakeLists.txt new file mode 100644 index 0000000..40a36b6 --- /dev/null +++ b/opencv-apps/CMakeLists.txt @@ -0,0 +1,57 @@ +add_definitions(-D__OPENCV_BUILD=1) +add_definitions(-D__OPENCV_APPS=1) + +# Unified function for creating OpenCV applications: +# ocv_add_application(tgt [MODULES [ ...]] SRCS [ ...]) +function(ocv_add_application the_target) + cmake_parse_arguments(APP "" "" "MODULES;SRCS" ${ARGN}) + ocv_check_dependencies(${APP_MODULES}) + if(NOT OCV_DEPENDENCIES_FOUND) + return() + endif() + + project(${the_target}) + ocv_target_include_modules_recurse(${the_target} ${APP_MODULES}) + ocv_target_include_directories(${the_target} PRIVATE "${OpenCV_SOURCE_DIR}/include/opencv") + ocv_add_executable(${the_target} ${APP_SRCS}) + ocv_target_link_libraries(${the_target} ${APP_MODULES}) + set_target_properties(${the_target} PROPERTIES + DEBUG_POSTFIX "${OPENCV_DEBUG_POSTFIX}" + ARCHIVE_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_PATH} + RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH} + OUTPUT_NAME "${the_target}") + + if(ENABLE_SOLUTION_FOLDERS) + set_target_properties(${the_target} PROPERTIES FOLDER "applications") + endif() + + if(INSTALL_CREATE_DISTRIB) + if(BUILD_SHARED_LIBS) + install(TARGETS ${the_target} RUNTIME DESTINATION ${OPENCV_BIN_INSTALL_PATH} CONFIGURATIONS Release COMPONENT dev) + endif() + else() + install(TARGETS ${the_target} RUNTIME DESTINATION ${OPENCV_BIN_INSTALL_PATH} COMPONENT dev) + endif() +endfunction() + +link_libraries(${OPENCV_LINKER_LIBS}) + +macro(ocv_add_app directory) + if(DEFINED BUILD_APPS_LIST) + list(FIND BUILD_APPS_LIST ${directory} _index) + if (${_index} GREATER -1) + add_subdirectory(${directory}) + else() + message(STATUS "Skip OpenCV app: ${directory}") + endif() + else() + add_subdirectory(${directory}) + endif() +endmacro() + +#ocv_add_app(traincascade) +#ocv_add_app(createsamples) +ocv_add_app(annotation) +ocv_add_app(visualisation) +ocv_add_app(interactive-calibration) +ocv_add_app(version) diff --git a/opencv-apps/annotation/CMakeLists.txt b/opencv-apps/annotation/CMakeLists.txt new file mode 100644 index 0000000..a30846d --- /dev/null +++ b/opencv-apps/annotation/CMakeLists.txt @@ -0,0 +1,3 @@ +ocv_add_application(opencv_annotation + MODULES opencv_core opencv_highgui opencv_imgproc opencv_imgcodecs opencv_videoio + SRCS opencv_annotation.cpp) diff --git a/opencv-apps/annotation/opencv_annotation.cpp b/opencv-apps/annotation/opencv_annotation.cpp new file mode 100644 index 0000000..13149b6 --- /dev/null +++ b/opencv-apps/annotation/opencv_annotation.cpp @@ -0,0 +1,317 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +// Copyright (C) 2009, Willow Garage Inc., all rights reserved. +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//////////////////////////////////////////////////////////////////////////////////////// + +/***************************************************************************************************** +USAGE: +./opencv_annotation -images -annotations + +Created by: Puttemans Steven - February 2015 +Adapted by: Puttemans Steven - April 2016 - Vectorize the process to enable better processing + + early leave and store by pressing an ESC key + + enable delete `d` button, to remove last annotation +*****************************************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace std; +using namespace cv; + +// Function prototypes +void on_mouse(int, int, int, int, void*); +vector get_annotations(Mat); + +// Public parameters +Mat image; +int roi_x0 = 0, roi_y0 = 0, roi_x1 = 0, roi_y1 = 0, num_of_rec = 0; +bool start_draw = false, stop = false; + +// Window name for visualisation purposes +const string window_name = "OpenCV Based Annotation Tool"; + +// FUNCTION : Mouse response for selecting objects in images +// If left button is clicked, start drawing a rectangle as long as mouse moves +// Stop drawing once a new left click is detected by the on_mouse function +void on_mouse(int event, int x, int y, int , void * ) +{ + // Action when left button is clicked + if(event == EVENT_LBUTTONDOWN) + { + if(!start_draw) + { + roi_x0 = x; + roi_y0 = y; + start_draw = true; + } else { + roi_x1 = x; + roi_y1 = y; + start_draw = false; + } + } + + // Action when mouse is moving and drawing is enabled + if((event == EVENT_MOUSEMOVE) && start_draw) + { + // Redraw bounding box for annotation + Mat current_view; + image.copyTo(current_view); + rectangle(current_view, Point(roi_x0,roi_y0), Point(x,y), Scalar(0,0,255)); + imshow(window_name, current_view); + } +} + +// FUNCTION : returns a vector of Rect objects given an image containing positive object instances +vector get_annotations(Mat input_image) +{ + vector current_annotations; + + // Make it possible to exit the annotation process + stop = false; + + // Init window interface and couple mouse actions + namedWindow(window_name, WINDOW_AUTOSIZE); + setMouseCallback(window_name, on_mouse); + + image = input_image; + imshow(window_name, image); + int key_pressed = 0; + + do + { + // Get a temporary image clone + Mat temp_image = input_image.clone(); + Rect currentRect(0, 0, 0, 0); + + // Keys for processing + // You need to select one for confirming a selection and one to continue to the next image + // Based on the universal ASCII code of the keystroke: http://www.asciitable.com/ + // c = 99 add rectangle to current image + // n = 110 save added rectangles and show next image + // d = 100 delete the last annotation made + // = 27 exit program + key_pressed = 0xFF & waitKey(0); + switch( key_pressed ) + { + case 27: + stop = true; + break; + case 99: + // Draw initiated from top left corner + if(roi_x0roi_x1 && roi_y0>roi_y1) + { + currentRect.x = roi_x1; + currentRect.y = roi_y1; + currentRect.width = roi_x0-roi_x1; + currentRect.height = roi_y0-roi_y1; + } + // Draw initiated from top right corner + if(roi_x0>roi_x1 && roi_y0roi_y1) + { + currentRect.x = roi_x0; + currentRect.y = roi_y1; + currentRect.width = roi_x1-roi_x0; + currentRect.height = roi_y0-roi_y1; + } + // Draw the rectangle on the canvas + // Add the rectangle to the vector of annotations + current_annotations.push_back(currentRect); + break; + case 100: + // Remove the last annotation + if(current_annotations.size() > 0){ + current_annotations.pop_back(); + } + break; + default: + // Default case --> do nothing at all + // Other keystrokes can simply be ignored + break; + } + + // Check if escape has been pressed + if(stop) + { + break; + } + + // Draw all the current rectangles onto the top image and make sure that the global image is linked + for(int i=0; i < (int)current_annotations.size(); i++){ + rectangle(temp_image, current_annotations[i], Scalar(0,255,0), 1); + } + image = temp_image; + + // Force an explicit redraw of the canvas --> necessary to visualize delete correctly + imshow(window_name, image); + } + // Continue as long as the next image key has not been pressed + while(key_pressed != 110); + + // Close down the window + destroyWindow(window_name); + + // Return the data + return current_annotations; +} + +int main( int argc, const char** argv ) +{ + // Use the cmdlineparser to process input arguments + CommandLineParser parser(argc, argv, + "{ help h usage ? | | show this message }" + "{ images i | | (required) path to image folder [example - /data/testimages/] }" + "{ annotations a | | (required) path to annotations txt file [example - /data/annotations.txt] }" + "{ maxWindowHeight m | -1 | (optional) images larger in height than this value will be scaled down }" + "{ resizeFactor r | 2 | (optional) factor for scaling down [default = half the size] }" + ); + // Read in the input arguments + if (parser.has("help")){ + parser.printMessage(); + cerr << "TIP: Use absolute paths to avoid any problems with the software!" << endl; + return 0; + } + string image_folder(parser.get("images")); + string annotations_file(parser.get("annotations")); + if (image_folder.empty() || annotations_file.empty()){ + parser.printMessage(); + cerr << "TIP: Use absolute paths to avoid any problems with the software!" << endl; + return -1; + } + + int resizeFactor = parser.get("resizeFactor"); + int const maxWindowHeight = parser.get("maxWindowHeight") > 0 ? parser.get("maxWindowHeight") : -1; + + // Start by processing the data + // Return the image filenames inside the image folder + map< String, vector > annotations; + vector filenames; + String folder(image_folder); + glob(folder, filenames); + + // Add key tips on how to use the software when running it + cout << "* mark rectangles with the left mouse button," << endl; + cout << "* press 'c' to accept a selection," << endl; + cout << "* press 'd' to delete the latest selection," << endl; + cout << "* press 'n' to proceed with next image," << endl; + cout << "* press 'esc' to stop." << endl; + + // Loop through each image stored in the images folder + // Create and temporarily store the annotations + // At the end write everything to the annotations file + for (size_t i = 0; i < filenames.size(); i++){ + // Read in an image + Mat current_image = imread(filenames[i]); + bool const resize_bool = (maxWindowHeight > 0) && (current_image.rows > maxWindowHeight); + + // Check if the image is actually read - avoid other files in the folder, because glob() takes them all + // If not then simply skip this iteration + if(current_image.empty()){ + continue; + } + + if(resize_bool){ + resize(current_image, current_image, Size(current_image.cols/resizeFactor, current_image.rows/resizeFactor), 0, 0, INTER_LINEAR_EXACT); + } + + // Perform annotations & store the result inside the vectorized structure + // If the image was resized before, then resize the found annotations back to original dimensions + vector current_annotations = get_annotations(current_image); + if(resize_bool){ + for(int j =0; j < (int)current_annotations.size(); j++){ + current_annotations[j].x = current_annotations[j].x * resizeFactor; + current_annotations[j].y = current_annotations[j].y * resizeFactor; + current_annotations[j].width = current_annotations[j].width * resizeFactor; + current_annotations[j].height = current_annotations[j].height * resizeFactor; + } + } + annotations[filenames[i]] = current_annotations; + + // Check if the ESC key was hit, then exit earlier then expected + if(stop){ + break; + } + } + + // When all data is processed, store the data gathered inside the proper file + // This now even gets called when the ESC button was hit to store preliminary results + ofstream output(annotations_file.c_str()); + if ( !output.is_open() ){ + cerr << "The path for the output file contains an error and could not be opened. Please check again!" << endl; + return 0; + } + + // Store the annotations, write to the output file + for(map >::iterator it = annotations.begin(); it != annotations.end(); it++){ + vector &anno = it->second; + output << it->first << " " << anno.size(); + for(size_t j=0; j < anno.size(); j++){ + Rect temp = anno[j]; + output << " " << temp.x << " " << temp.y << " " << temp.width << " " << temp.height; + } + output << endl; + } + + return 0; +} diff --git a/opencv-apps/build/CMakeCache.txt b/opencv-apps/build/CMakeCache.txt new file mode 100644 index 0000000..18f30af --- /dev/null +++ b/opencv-apps/build/CMakeCache.txt @@ -0,0 +1,65 @@ +# This is the CMakeCache file. +# For build in directory: /home/cole/item-sort/opencv-apps/build +# It was generated by CMake: /usr/bin/cmake +# You can edit this file to change values found and used by cmake. +# If you do not want to change any of the values, simply exit the editor. +# If you do want to change a value, simply edit, save, and exit the editor. +# The syntax for the file is as follows: +# KEY:TYPE=VALUE +# KEY is the name of a variable in the cache. +# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!. +# VALUE is the current value for the KEY. + +######################## +# EXTERNAL cache entries +######################## + +//For backwards compatibility, what version of CMake commands and +// syntax should this version of CMake try to support. +CMAKE_BACKWARDS_COMPATIBILITY:STRING=2.4 + +//Single output directory for building all executables. +EXECUTABLE_OUTPUT_PATH:PATH= + +//Single output directory for building all libraries. +LIBRARY_OUTPUT_PATH:PATH= + + +######################## +# INTERNAL cache entries +######################## + +//This is the directory where this CMakeCache.txt was created +CMAKE_CACHEFILE_DIR:INTERNAL=/home/cole/item-sort/opencv-apps/build +//Major version of cmake used to create the current loaded cache +CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3 +//Minor version of cmake used to create the current loaded cache +CMAKE_CACHE_MINOR_VERSION:INTERNAL=16 +//Patch version of cmake used to create the current loaded cache +CMAKE_CACHE_PATCH_VERSION:INTERNAL=0 +//Path to CMake executable. +CMAKE_COMMAND:INTERNAL=/usr/bin/cmake +//Path to cpack program executable. +CMAKE_CPACK_COMMAND:INTERNAL=/usr/bin/cpack +//Path to ctest program executable. +CMAKE_CTEST_COMMAND:INTERNAL=/usr/bin/ctest +//Path to cache edit program executable. +CMAKE_EDIT_COMMAND:INTERNAL=/usr/bin/ccmake +//Name of external makefile project generator. +CMAKE_EXTRA_GENERATOR:INTERNAL= +//Name of generator. +CMAKE_GENERATOR:INTERNAL=Unix Makefiles +//Generator instance identifier. +CMAKE_GENERATOR_INSTANCE:INTERNAL= +//Name of generator platform. +CMAKE_GENERATOR_PLATFORM:INTERNAL= +//Name of generator toolset. +CMAKE_GENERATOR_TOOLSET:INTERNAL= +//Source directory with the top level CMakeLists.txt file for this +// project +CMAKE_HOME_DIRECTORY:INTERNAL=/home/cole/item-sort/opencv-apps +//number of local generators +CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=2 +//Path to CMake installation. +CMAKE_ROOT:INTERNAL=/usr/share/cmake-3.16 + diff --git a/opencv-apps/build/CMakeFiles/cmake.check_cache b/opencv-apps/build/CMakeFiles/cmake.check_cache new file mode 100644 index 0000000..3dccd73 --- /dev/null +++ b/opencv-apps/build/CMakeFiles/cmake.check_cache @@ -0,0 +1 @@ +# This file is generated by cmake for dependency checking of the CMakeCache.txt file diff --git a/opencv-apps/createsamples/CMakeLists.txt b/opencv-apps/createsamples/CMakeLists.txt new file mode 100644 index 0000000..7fb2b67 --- /dev/null +++ b/opencv-apps/createsamples/CMakeLists.txt @@ -0,0 +1,4 @@ +file(GLOB SRCS *.cpp) +ocv_add_application(opencv_createsamples + MODULES opencv_core opencv_imgproc opencv_objdetect opencv_imgcodecs opencv_highgui opencv_calib3d opencv_features2d opencv_videoio + SRCS ${SRCS}) diff --git a/opencv-apps/createsamples/createsamples.cpp b/opencv-apps/createsamples/createsamples.cpp new file mode 100644 index 0000000..83a55f0 --- /dev/null +++ b/opencv-apps/createsamples/createsamples.cpp @@ -0,0 +1,258 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +/* + * createsamples.cpp + * + * Create test/training samples + */ + +#include "opencv2/core.hpp" +#include "utility.hpp" +#include +#include +#include +#include + +using namespace std; + +int main( int argc, char* argv[] ) +{ + int i = 0; + char* nullname = (char*)"(NULL)"; + char* vecname = NULL; /* .vec file name */ + char* infoname = NULL; /* file name with marked up image descriptions */ + char* imagename = NULL; /* single sample image */ + char* bgfilename = NULL; /* background */ + int num = 1000; + int bgcolor = 0; + int bgthreshold = 80; + int invert = 0; + int maxintensitydev = 40; + double maxxangle = 1.1; + double maxyangle = 1.1; + double maxzangle = 0.5; + int showsamples = 0; + /* the samples are adjusted to this scale in the sample preview window */ + double scale = 4.0; + int width = 24; + int height = 24; + double maxscale = -1.0; + int rngseed = 12345; + + if( argc == 1 ) + { + printf( "Usage: %s\n [-info ]\n" + " [-img ]\n" + " [-vec ]\n" + " [-bg ]\n [-num ]\n" + " [-bgcolor ]\n" + " [-inv] [-randinv] [-bgthresh ]\n" + " [-maxidev ]\n" + " [-maxxangle ]\n" + " [-maxyangle ]\n" + " [-maxzangle ]\n" + " [-show []]\n" + " [-w ]\n [-h ]\n" + " [-maxscale ]\n" + " [-rngseed ]\n", + argv[0], num, bgcolor, bgthreshold, maxintensitydev, + maxxangle, maxyangle, maxzangle, scale, width, height, maxscale, rngseed ); + + return 0; + } + + for( i = 1; i < argc; ++i ) + { + if( !strcmp( argv[i], "-info" ) ) + { + infoname = argv[++i]; + } + else if( !strcmp( argv[i], "-img" ) ) + { + imagename = argv[++i]; + } + else if( !strcmp( argv[i], "-vec" ) ) + { + vecname = argv[++i]; + } + else if( !strcmp( argv[i], "-bg" ) ) + { + bgfilename = argv[++i]; + } + else if( !strcmp( argv[i], "-num" ) ) + { + num = atoi( argv[++i] ); + } + else if( !strcmp( argv[i], "-bgcolor" ) ) + { + bgcolor = atoi( argv[++i] ); + } + else if( !strcmp( argv[i], "-bgthresh" ) ) + { + bgthreshold = atoi( argv[++i] ); + } + else if( !strcmp( argv[i], "-inv" ) ) + { + invert = 1; + } + else if( !strcmp( argv[i], "-randinv" ) ) + { + invert = CV_RANDOM_INVERT; + } + else if( !strcmp( argv[i], "-maxidev" ) ) + { + maxintensitydev = atoi( argv[++i] ); + } + else if( !strcmp( argv[i], "-maxxangle" ) ) + { + maxxangle = atof( argv[++i] ); + } + else if( !strcmp( argv[i], "-maxyangle" ) ) + { + maxyangle = atof( argv[++i] ); + } + else if( !strcmp( argv[i], "-maxzangle" ) ) + { + maxzangle = atof( argv[++i] ); + } + else if( !strcmp( argv[i], "-show" ) ) + { + showsamples = 1; + if( i+1 < argc && strlen( argv[i+1] ) > 0 && argv[i+1][0] != '-' ) + { + double d; + d = strtod( argv[i+1], 0 ); + if( d != -HUGE_VAL && d != HUGE_VAL && d > 0 ) scale = d; + ++i; + } + } + else if( !strcmp( argv[i], "-w" ) ) + { + width = atoi( argv[++i] ); + } + else if( !strcmp( argv[i], "-h" ) ) + { + height = atoi( argv[++i] ); + } + else if( !strcmp( argv[i], "-maxscale" ) ) + { + maxscale = atof( argv[++i] ); + } + else if (!strcmp(argv[i], "-rngseed")) + { + rngseed = atoi(argv[++i]); + } + } + + cv::setRNGSeed( rngseed ); + + printf( "Info file name: %s\n", ((infoname == NULL) ? nullname : infoname ) ); + printf( "Img file name: %s\n", ((imagename == NULL) ? nullname : imagename ) ); + printf( "Vec file name: %s\n", ((vecname == NULL) ? nullname : vecname ) ); + printf( "BG file name: %s\n", ((bgfilename == NULL) ? nullname : bgfilename ) ); + printf( "Num: %d\n", num ); + printf( "BG color: %d\n", bgcolor ); + printf( "BG threshold: %d\n", bgthreshold ); + printf( "Invert: %s\n", (invert == CV_RANDOM_INVERT) ? "RANDOM" + : ( (invert) ? "TRUE" : "FALSE" ) ); + printf( "Max intensity deviation: %d\n", maxintensitydev ); + printf( "Max x angle: %g\n", maxxangle ); + printf( "Max y angle: %g\n", maxyangle ); + printf( "Max z angle: %g\n", maxzangle ); + printf( "Show samples: %s\n", (showsamples) ? "TRUE" : "FALSE" ); + if( showsamples ) + { + printf( "Scale: %g\n", scale ); + } + printf( "Width: %d\n", width ); + printf( "Height: %d\n", height ); + printf( "Max Scale: %g\n", maxscale ); + printf( "RNG Seed: %d\n", rngseed ); + + /* determine action */ + if( imagename && vecname ) + { + printf( "Create training samples from single image applying distortions...\n" ); + + cvCreateTrainingSamples( vecname, imagename, bgcolor, bgthreshold, bgfilename, + num, invert, maxintensitydev, + maxxangle, maxyangle, maxzangle, + showsamples, width, height ); + + printf( "Done\n" ); + } + else if( imagename && bgfilename && infoname ) + { + printf( "Create test samples from single image applying distortions...\n" ); + + cvCreateTestSamples( infoname, imagename, bgcolor, bgthreshold, bgfilename, num, + invert, maxintensitydev, + maxxangle, maxyangle, maxzangle, showsamples, width, height, maxscale); + + printf( "Done\n" ); + } + else if( infoname && vecname ) + { + int total; + + printf( "Create training samples from images collection...\n" ); + + total = cvCreateTrainingSamplesFromInfo( infoname, vecname, num, showsamples, + width, height ); + + printf( "Done. Created %d samples\n", total ); + } + else if( vecname ) + { + printf( "View samples from vec file (press ESC to exit)...\n" ); + + cvShowVecSamples( vecname, width, height, scale ); + + printf( "Done\n" ); + } + else + { + printf( "Nothing to do\n" ); + } + + return 0; +} diff --git a/opencv-apps/createsamples/utility.cpp b/opencv-apps/createsamples/utility.cpp new file mode 100644 index 0000000..919ad2d --- /dev/null +++ b/opencv-apps/createsamples/utility.cpp @@ -0,0 +1,1463 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ +#include +#include + +#include +#include +#ifdef _WIN32 +#include +#endif /* _WIN32 */ + +#include "utility.hpp" +#include "opencv2/core.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" +#include "opencv2/highgui.hpp" +#include "opencv2/calib3d.hpp" + +#if defined __GNUC__ && __GNUC__ >= 8 +#pragma GCC diagnostic ignored "-Wclass-memaccess" +#endif + +using namespace cv; + +#ifndef PATH_MAX +#define PATH_MAX 512 +#endif /* PATH_MAX */ + +#define __BEGIN__ __CV_BEGIN__ +#define __END__ __CV_END__ +#define EXIT __CV_EXIT__ + +static int icvMkDir( const char* filename ) +{ + char path[PATH_MAX]; + char* p; + int pos; + +#ifdef _WIN32 + struct _stat st; +#else /* _WIN32 */ + struct stat st; + mode_t mode; + + mode = 0755; +#endif /* _WIN32 */ + + strcpy( path, filename ); + + p = path; + for( ; ; ) + { + pos = (int)strcspn( p, "/\\" ); + + if( pos == (int) strlen( p ) ) break; + if( pos != 0 ) + { + p[pos] = '\0'; + +#ifdef _WIN32 + if( p[pos-1] != ':' ) + { + if( _stat( path, &st ) != 0 ) + { + if( _mkdir( path ) != 0 ) return 0; + } + } +#else /* _WIN32 */ + if( stat( path, &st ) != 0 ) + { + if( mkdir( path, mode ) != 0 ) return 0; + } +#endif /* _WIN32 */ + } + + p[pos] = '/'; + + p += pos + 1; + } + + return 1; +} + +static void icvWriteVecHeader( FILE* file, int count, int width, int height ) +{ + int vecsize; + short tmp; + + /* number of samples */ + fwrite( &count, sizeof( count ), 1, file ); + /* vector size */ + vecsize = width * height; + fwrite( &vecsize, sizeof( vecsize ), 1, file ); + /* min/max values */ + tmp = 0; + fwrite( &tmp, sizeof( tmp ), 1, file ); + fwrite( &tmp, sizeof( tmp ), 1, file ); +} + +static void icvWriteVecSample( FILE* file, Mat sample ) +{ + uchar chartmp = 0; + fwrite( &chartmp, sizeof( chartmp ), 1, file ); + for(int r = 0; r < sample.rows; r++ ) + { + for(int c = 0; c < sample.cols; c++ ) + { + short tmp = sample.at(r,c); + fwrite( &tmp, sizeof( tmp ), 1, file ); + } + } +} + +/* Calculates coefficients of perspective transformation + * which maps into rectangle ((0,0), (w,0), (w,h), (h,0)): + * + * c00*xi + c01*yi + c02 + * ui = --------------------- + * c20*xi + c21*yi + c22 + * + * c10*xi + c11*yi + c12 + * vi = --------------------- + * c20*xi + c21*yi + c22 + * + * Coefficients are calculated by solving linear system: + * / x0 y0 1 0 0 0 -x0*u0 -y0*u0 \ /c00\ /u0\ + * | x1 y1 1 0 0 0 -x1*u1 -y1*u1 | |c01| |u1| + * | x2 y2 1 0 0 0 -x2*u2 -y2*u2 | |c02| |u2| + * | x3 y3 1 0 0 0 -x3*u3 -y3*u3 |.|c10|=|u3|, + * | 0 0 0 x0 y0 1 -x0*v0 -y0*v0 | |c11| |v0| + * | 0 0 0 x1 y1 1 -x1*v1 -y1*v1 | |c12| |v1| + * | 0 0 0 x2 y2 1 -x2*v2 -y2*v2 | |c20| |v2| + * \ 0 0 0 x3 y3 1 -x3*v3 -y3*v3 / \c21/ \v3/ + * + * where: + * (xi, yi) = (quad[i][0], quad[i][1]) + * cij - coeffs[i][j], coeffs[2][2] = 1 + * (ui, vi) - rectangle vertices + */ +static void cvGetPerspectiveTransform( Size src_size, double quad[4][2], double coeffs[3][3] ) +{ + double a[8][8]; + double b[8]; + + Mat A( 8, 8, CV_64FC1, a ); + Mat B( 8, 1, CV_64FC1, b ); + Mat X( 8, 1, CV_64FC1, coeffs ); + + int i; + for( i = 0; i < 4; ++i ) + { + a[i][0] = quad[i][0]; a[i][1] = quad[i][1]; a[i][2] = 1; + a[i][3] = a[i][4] = a[i][5] = a[i][6] = a[i][7] = 0; + b[i] = 0; + } + for( i = 4; i < 8; ++i ) + { + a[i][3] = quad[i-4][0]; a[i][4] = quad[i-4][1]; a[i][5] = 1; + a[i][0] = a[i][1] = a[i][2] = a[i][6] = a[i][7] = 0; + b[i] = 0; + } + + int u = src_size.width - 1; + int v = src_size.height - 1; + + a[1][6] = -quad[1][0] * u; a[1][7] = -quad[1][1] * u; + a[2][6] = -quad[2][0] * u; a[2][7] = -quad[2][1] * u; + b[1] = b[2] = u; + + a[6][6] = -quad[2][0] * v; a[6][7] = -quad[2][1] * v; + a[7][6] = -quad[3][0] * v; a[7][7] = -quad[3][1] * v; + b[6] = b[7] = v; + + solve( A, B, X ); + + coeffs[2][2] = 1; +} + +/* Warps source into destination by a perspective transform */ +static void cvWarpPerspective( Mat src, Mat dst, double quad[4][2] ) +{ + int fill_value = 0; + + double c[3][3]; /* transformation coefficients */ + double q[4][2]; /* rearranged quad */ + + int left = 0; + int right = 0; + int next_right = 0; + int next_left = 0; + double y_min = 0; + double y_max = 0; + double k_left, b_left, k_right, b_right; + + double d = 0; + int direction = 0; + int i; + + if( src.type() != CV_8UC1 || src.dims != 2 ) + { + CV_Error( Error::StsBadArg, + "Source must be two-dimensional array of CV_8UC1 type." ); + } + if( dst.type() != CV_8UC1 || dst.dims != 2 ) + { + CV_Error( Error::StsBadArg, + "Destination must be two-dimensional array of CV_8UC1 type." ); + } + + cvGetPerspectiveTransform( src.size(), quad, c ); + + /* if direction > 0 then vertices in quad follow in a CW direction, + otherwise they follow in a CCW direction */ + direction = 0; + for( i = 0; i < 4; ++i ) + { + int ni = i + 1; if( ni == 4 ) ni = 0; + int pi = i - 1; if( pi == -1 ) pi = 3; + + d = (quad[i][0] - quad[pi][0])*(quad[ni][1] - quad[i][1]) - + (quad[i][1] - quad[pi][1])*(quad[ni][0] - quad[i][0]); + int cur_direction = d > 0 ? 1 : d < 0 ? -1 : 0; + if( direction == 0 ) + { + direction = cur_direction; + } + else if( direction * cur_direction < 0 ) + { + direction = 0; + break; + } + } + if( direction == 0 ) + { + CV_Error(Error::StsBadArg, "Quadrangle is nonconvex or degenerated." ); + } + + /* is the index of the topmost quad vertice + if there are two such vertices is the leftmost one */ + left = 0; + for( i = 1; i < 4; ++i ) + { + if( (quad[i][1] < quad[left][1]) || + ((quad[i][1] == quad[left][1]) && (quad[i][0] < quad[left][0])) ) + { + left = i; + } + } + /* rearrange vertices in such way that they follow in a CW + direction and the first vertice is the topmost one and put them + into */ + if( direction > 0 ) + { + for( i = left; i < 4; ++i ) + { + q[i-left][0] = quad[i][0]; + q[i-left][1] = quad[i][1]; + } + for( i = 0; i < left; ++i ) + { + q[4-left+i][0] = quad[i][0]; + q[4-left+i][1] = quad[i][1]; + } + } + else + { + for( i = left; i >= 0; --i ) + { + q[left-i][0] = quad[i][0]; + q[left-i][1] = quad[i][1]; + } + for( i = 3; i > left; --i ) + { + q[4+left-i][0] = quad[i][0]; + q[4+left-i][1] = quad[i][1]; + } + } + + left = right = 0; + /* if there are two topmost points, is the index of the rightmost one + otherwise */ + if( q[left][1] == q[left+1][1] ) + { + right = 1; + } + + /* follows in a CCW direction */ + next_left = 3; + /* follows in a CW direction */ + next_right = right + 1; + + /* subtraction of 1 prevents skipping of the first row */ + y_min = q[left][1] - 1; + + /* left edge equation: y = k_left * x + b_left */ + k_left = (q[left][0] - q[next_left][0]) / + (q[left][1] - q[next_left][1]); + b_left = (q[left][1] * q[next_left][0] - + q[left][0] * q[next_left][1]) / + (q[left][1] - q[next_left][1]); + + /* right edge equation: y = k_right * x + b_right */ + k_right = (q[right][0] - q[next_right][0]) / + (q[right][1] - q[next_right][1]); + b_right = (q[right][1] * q[next_right][0] - + q[right][0] * q[next_right][1]) / + (q[right][1] - q[next_right][1]); + + for(;;) + { + int x, y; + + y_max = MIN( q[next_left][1], q[next_right][1] ); + + int iy_min = MAX( cvRound(y_min), 0 ) + 1; + int iy_max = MIN( cvRound(y_max), dst.rows - 1 ); + + double x_min = k_left * iy_min + b_left; + double x_max = k_right * iy_min + b_right; + + /* walk through the destination quadrangle row by row */ + for( y = iy_min; y <= iy_max; ++y ) + { + int ix_min = MAX( cvRound( x_min ), 0 ); + int ix_max = MIN( cvRound( x_max ), dst.cols - 1 ); + + for( x = ix_min; x <= ix_max; ++x ) + { + /* calculate coordinates of the corresponding source array point */ + double div = (c[2][0] * x + c[2][1] * y + c[2][2]); + double src_x = (c[0][0] * x + c[0][1] * y + c[0][2]) / div; + double src_y = (c[1][0] * x + c[1][1] * y + c[1][2]) / div; + + int isrc_x = cvFloor( src_x ); + int isrc_y = cvFloor( src_y ); + double delta_x = src_x - isrc_x; + double delta_y = src_y - isrc_y; + + int i00, i10, i01, i11; + i00 = i10 = i01 = i11 = (int) fill_value; + + /* linear interpolation using 2x2 neighborhood */ + if( isrc_x >= 0 && isrc_x < src.cols && + isrc_y >= 0 && isrc_y < src.rows ) + { + i00 = src.at(isrc_y, isrc_x); + } + if( isrc_x >= -1 && isrc_x + 1 < src.cols && + isrc_y >= 0 && isrc_y < src.rows ) + { + i10 = src.at(isrc_y, isrc_x + 1); + } + if( isrc_x >= 0 && isrc_x < src.cols && + isrc_y >= -1 && isrc_y + 1 < src.rows ) + { + i01 = src.at(isrc_y + 1, isrc_x); + } + if( isrc_x >= -1 && isrc_x + 1 < src.cols && + isrc_y >= -1 && isrc_y + 1 < src.rows ) + { + i11 = src.at(isrc_y + 1, isrc_x + 1); + } + + double i0 = i00 + (i10 - i00)*delta_x; + double i1 = i01 + (i11 - i01)*delta_x; + + dst.at(y, x) = (uchar) (i0 + (i1 - i0)*delta_y); + } + x_min += k_left; + x_max += k_right; + } + + if( (next_left == next_right) || + (next_left+1 == next_right && q[next_left][1] == q[next_right][1]) ) + { + break; + } + + if( y_max == q[next_left][1] ) + { + left = next_left; + next_left = left - 1; + + k_left = (q[left][0] - q[next_left][0]) / + (q[left][1] - q[next_left][1]); + b_left = (q[left][1] * q[next_left][0] - + q[left][0] * q[next_left][1]) / + (q[left][1] - q[next_left][1]); + } + if( y_max == q[next_right][1] ) + { + right = next_right; + next_right = right + 1; + + k_right = (q[right][0] - q[next_right][0]) / + (q[right][1] - q[next_right][1]); + b_right = (q[right][1] * q[next_right][0] - + q[right][0] * q[next_right][1]) / + (q[right][1] - q[next_right][1]); + } + y_min = y_max; + } +} + +static +void icvRandomQuad( int width, int height, double quad[4][2], + double maxxangle, + double maxyangle, + double maxzangle ) +{ + double distfactor = 3.0; + double distfactor2 = 1.0; + + double halfw, halfh; + int i; + + double rotVectData[3]; + double vectData[3]; + double rotMatData[9]; + + double d; + + Mat rotVect( 3, 1, CV_64FC1, &rotVectData[0] ); + Mat rotMat( 3, 3, CV_64FC1, &rotMatData[0] ); + Mat vect( 3, 1, CV_64FC1, &vectData[0] ); + + rotVectData[0] = theRNG().uniform( -maxxangle, maxxangle ); + rotVectData[1] = ( maxyangle - fabs( rotVectData[0] ) ) * theRNG().uniform( -1.0, 1.0 ); + rotVectData[2] = theRNG().uniform( -maxzangle, maxzangle ); + d = ( distfactor + distfactor2 * theRNG().uniform( -1.0, 1.0 ) ) * width; + + Rodrigues( rotVect, rotMat ); + + halfw = 0.5 * width; + halfh = 0.5 * height; + + quad[0][0] = -halfw; + quad[0][1] = -halfh; + quad[1][0] = halfw; + quad[1][1] = -halfh; + quad[2][0] = halfw; + quad[2][1] = halfh; + quad[3][0] = -halfw; + quad[3][1] = halfh; + + for( i = 0; i < 4; i++ ) + { + rotVectData[0] = quad[i][0]; + rotVectData[1] = quad[i][1]; + rotVectData[2] = 0.0; + gemm(rotMat, rotVect, 1., Mat(), 1., vect); + quad[i][0] = vectData[0] * d / (d + vectData[2]) + halfw; + quad[i][1] = vectData[1] * d / (d + vectData[2]) + halfh; + } +} + + +typedef struct CvSampleDistortionData +{ + Mat src; + Mat erode; + Mat dilate; + Mat mask; + Mat img; + Mat maskimg; + int dx; + int dy; + int bgcolor; +} CvSampleDistortionData; + +#if defined CV_OPENMP && (defined _MSC_VER || defined CV_ICC) +#define CV_OPENMP 1 +#else +#undef CV_OPENMP +#endif + +typedef struct CvBackgroundData +{ + int count; + char** filename; + int last; + int round; + Size winsize; +} CvBackgroundData; + +typedef struct CvBackgroundReader +{ + Mat src; + Mat img; + Point offset; + float scale; + float scalefactor; + float stepfactor; + Point point; +} CvBackgroundReader; + +/* + * Background reader + * Created in each thread + */ +CvBackgroundReader* cvbgreader = NULL; + +#if defined CV_OPENMP +#pragma omp threadprivate(cvbgreader) +#endif + +CvBackgroundData* cvbgdata = NULL; + +static int icvStartSampleDistortion( const char* imgfilename, int bgcolor, int bgthreshold, + CvSampleDistortionData* data ) +{ + memset( data, 0, sizeof( *data ) ); + data->src = imread( imgfilename, IMREAD_GRAYSCALE ); + if( !(data->src.empty()) && data->src.type() == CV_8UC1 ) + { + int r, c; + + data->dx = data->src.cols / 2; + data->dy = data->src.rows / 2; + data->bgcolor = bgcolor; + + data->mask = data->src.clone(); + data->erode = data->src.clone(); + data->dilate = data->src.clone(); + + /* make mask image */ + for( r = 0; r < data->mask.rows; r++ ) + { + for( c = 0; c < data->mask.cols; c++ ) + { + uchar& pmask = data->mask.at(r, c); + if( bgcolor - bgthreshold <= (int)pmask && + (int)pmask <= bgcolor + bgthreshold ) + { + pmask = (uchar) 0; + } + else + { + pmask = (uchar) 255; + } + } + } + + /* extend borders of source image */ + erode( data->src, data->erode, Mat() ); + dilate( data->src, data->dilate, Mat() ); + for( r = 0; r < data->mask.rows; r++ ) + { + for( c = 0; c < data->mask.cols; c++ ) + { + uchar& pmask = data->mask.at(r, c); + if( pmask == 0 ) + { + uchar& psrc = data->src.at(r, c); + uchar& perode = data->erode.at(r, c); + uchar& pdilate = data->dilate.at(r, c); + uchar de = (uchar)(bgcolor - perode); + uchar dd = (uchar)(pdilate - bgcolor); + if( de >= dd && de > bgthreshold ) + { + psrc = perode; + } + if( dd > de && dd > bgthreshold ) + { + psrc = pdilate; + } + } + } + } + + data->img = Mat(Size( data->src.cols + 2 * data->dx, data->src.rows + 2 * data->dy ), CV_8UC1); + data->maskimg = Mat(Size(data->src.cols + 2 * data->dx, data->src.rows + 2 * data->dy), CV_8UC1); + + return 1; + } + + return 0; +} + +static +void icvPlaceDistortedSample( Mat background, + int inverse, int maxintensitydev, + double maxxangle, double maxyangle, double maxzangle, + int inscribe, double maxshiftf, double maxscalef, + CvSampleDistortionData* data ) +{ + double quad[4][2]; + int r, c; + int forecolordev; + float scale; + + Rect cr; + Rect roi; + + double xshift, yshift, randscale; + + icvRandomQuad( data->src.cols, data->src.rows, quad, + maxxangle, maxyangle, maxzangle ); + quad[0][0] += (double) data->dx; + quad[0][1] += (double) data->dy; + quad[1][0] += (double) data->dx; + quad[1][1] += (double) data->dy; + quad[2][0] += (double) data->dx; + quad[2][1] += (double) data->dy; + quad[3][0] += (double) data->dx; + quad[3][1] += (double) data->dy; + + data->img = data->bgcolor; + data->maskimg = 0; + + cvWarpPerspective( data->src, data->img, quad ); + cvWarpPerspective( data->mask, data->maskimg, quad ); + + GaussianBlur( data->maskimg, data->maskimg, Size(3, 3), 0, 0 ); + + cr.x = data->dx; + cr.y = data->dy; + cr.width = data->src.cols; + cr.height = data->src.rows; + + if( inscribe ) + { + /* quad's circumscribing rectangle */ + cr.x = (int) MIN( quad[0][0], quad[3][0] ); + cr.y = (int) MIN( quad[0][1], quad[1][1] ); + cr.width = (int) (MAX( quad[1][0], quad[2][0] ) + 0.5F ) - cr.x; + cr.height = (int) (MAX( quad[2][1], quad[3][1] ) + 0.5F ) - cr.y; + } + + xshift = theRNG().uniform( 0., maxshiftf ); + yshift = theRNG().uniform( 0., maxshiftf ); + + cr.x -= (int) ( xshift * cr.width ); + cr.y -= (int) ( yshift * cr.height ); + cr.width = (int) ((1.0 + maxshiftf) * cr.width ); + cr.height = (int) ((1.0 + maxshiftf) * cr.height); + + randscale = theRNG().uniform( 0., maxscalef ); + cr.x -= (int) ( 0.5 * randscale * cr.width ); + cr.y -= (int) ( 0.5 * randscale * cr.height ); + cr.width = (int) ((1.0 + randscale) * cr.width ); + cr.height = (int) ((1.0 + randscale) * cr.height); + + scale = MAX( ((float) cr.width) / background.cols, ((float) cr.height) / background.rows ); + + roi.x = (int) (-0.5F * (scale * background.cols - cr.width) + cr.x); + roi.y = (int) (-0.5F * (scale * background.rows - cr.height) + cr.y); + roi.width = (int) (scale * background.cols); + roi.height = (int) (scale * background.rows); + + Mat img( background.size(), CV_8UC1 ); + Mat maskimg( background.size(), CV_8UC1 ); + + resize( data->img(roi & Rect(Point(0,0), data->img.size())), img, img.size(), 0, 0, INTER_LINEAR_EXACT); + resize( data->maskimg(roi & Rect(Point(0, 0), data->maskimg.size())), maskimg, maskimg.size(), 0, 0, INTER_LINEAR_EXACT); + + forecolordev = theRNG().uniform( -maxintensitydev, maxintensitydev ); + + for( r = 0; r < img.rows; r++ ) + { + for( c = 0; c < img.cols; c++ ) + { + uchar& pbg = background.at(r, c); + uchar& palpha = maskimg.at(r, c); + uchar chartmp = (uchar) MAX( 0, MIN( 255, forecolordev + img.at(r, c)) ); + if( inverse ) + { + chartmp ^= 0xFF; + } + pbg = (uchar) ((chartmp*palpha + (255 - palpha)*pbg) / 255); + } + } +} + +static +CvBackgroundData* icvCreateBackgroundData( const char* filename, Size winsize ) +{ + CvBackgroundData* data = NULL; + + const char* dir = NULL; + char full[PATH_MAX]; + char* imgfilename = NULL; + size_t datasize = 0; + int count = 0; + FILE* input = NULL; + char* tmp = NULL; + int len = 0; + + CV_Assert( filename != NULL ); + + dir = strrchr( filename, '\\' ); + if( dir == NULL ) + { + dir = strrchr( filename, '/' ); + } + if( dir == NULL ) + { + imgfilename = &(full[0]); + } + else + { + strncpy( &(full[0]), filename, (dir - filename + 1) ); + imgfilename = &(full[(dir - filename + 1)]); + } + + input = fopen( filename, "r" ); + if( input != NULL ) + { + count = 0; + datasize = 0; + + /* count */ + while( !feof( input ) ) + { + *imgfilename = '\0'; + if( !fgets( imgfilename, PATH_MAX - (int)(imgfilename - full) - 1, input )) + break; + len = (int)strlen( imgfilename ); + for( ; len > 0 && isspace(imgfilename[len-1]); len-- ) + imgfilename[len-1] = '\0'; + if( len > 0 ) + { + if( (*imgfilename) == '#' ) continue; /* comment */ + count++; + datasize += sizeof( char ) * (strlen( &(full[0]) ) + 1); + } + } + if( count > 0 ) + { + //rewind( input ); + fseek( input, 0, SEEK_SET ); + datasize += sizeof( *data ) + sizeof( char* ) * count; + data = (CvBackgroundData*) fastMalloc( datasize ); + memset( (void*) data, 0, datasize ); + data->count = count; + data->filename = (char**) (data + 1); + data->last = 0; + data->round = 0; + data->winsize = winsize; + tmp = (char*) (data->filename + data->count); + count = 0; + while( !feof( input ) ) + { + *imgfilename = '\0'; + if( !fgets( imgfilename, PATH_MAX - (int)(imgfilename - full) - 1, input )) + break; + len = (int)strlen( imgfilename ); + if( len > 0 && imgfilename[len-1] == '\n' ) + imgfilename[len-1] = 0, len--; + if( len > 0 ) + { + if( (*imgfilename) == '#' ) continue; /* comment */ + data->filename[count++] = tmp; + strcpy( tmp, &(full[0]) ); + tmp += strlen( &(full[0]) ) + 1; + } + } + } + fclose( input ); + } + + return data; +} + +static +CvBackgroundReader* icvCreateBackgroundReader() +{ + CvBackgroundReader* reader = NULL; + + reader = new CvBackgroundReader; + reader->scale = 1.0F; + reader->scalefactor = 1.4142135623730950488016887242097F; + reader->stepfactor = 0.5F; + + return reader; +} + +static +void icvGetNextFromBackgroundData( CvBackgroundData* data, + CvBackgroundReader* reader ) +{ + Mat img; + int round = 0; + int i = 0; + Point offset; + + CV_Assert( data != NULL && reader != NULL ); + + #ifdef CV_OPENMP + #pragma omp critical(c_background_data) + #endif /* CV_OPENMP */ + { + for( i = 0; i < data->count; i++ ) + { + round = data->round; + + data->last = theRNG().uniform( 0, RAND_MAX ) % data->count; + +#ifdef CV_VERBOSE + printf( "Open background image: %s\n", data->filename[data->last] ); +#endif /* CV_VERBOSE */ + + img = imread( data->filename[data->last], IMREAD_GRAYSCALE ); + if( img.empty() ) + continue; + data->round += data->last / data->count; + data->round = data->round % (data->winsize.width * data->winsize.height); + + offset.x = round % data->winsize.width; + offset.y = round / data->winsize.width; + + offset.x = MIN( offset.x, img.cols - data->winsize.width ); + offset.y = MIN( offset.y, img.rows - data->winsize.height ); + + if( !img.empty() && img.type() == CV_8UC1 && offset.x >= 0 && offset.y >= 0 ) + { + break; + } + img = Mat(); + } + } + if( img.empty() ) + { + /* no appropriate image */ + +#ifdef CV_VERBOSE + printf( "Invalid background description file.\n" ); +#endif /* CV_VERBOSE */ + + CV_Assert( 0 ); + exit( 1 ); + } + + reader->src = img; + + //reader->offset.x = round % data->winsize.width; + //reader->offset.y = round / data->winsize.width; + reader->offset = offset; + reader->point = reader->offset; + reader->scale = MAX( + ((float) data->winsize.width + reader->point.x) / ((float) reader->src.cols), + ((float) data->winsize.height + reader->point.y) / ((float) reader->src.rows) ); + + resize( reader->src, reader->img, + Size((int)(reader->scale * reader->src.cols + 0.5F), (int)(reader->scale * reader->src.rows + 0.5F)), 0, 0, INTER_LINEAR_EXACT); +} + +/* + * icvGetBackgroundImage + * + * Get an image from background + * must be allocated and have size, previously passed to icvInitBackgroundReaders + * + * Usage example: + * icvInitBackgroundReaders( "bg.txt", cvSize( 24, 24 ) ); + * ... + * #pragma omp parallel + * { + * ... + * icvGetBackgroundImage( cvbgdata, cvbgreader, img ); + * ... + * } + * ... + * icvDestroyBackgroundReaders(); + */ +static +void icvGetBackgroundImage( CvBackgroundData* data, + CvBackgroundReader* reader, + Mat& img ) +{ + CV_Assert( data != NULL && reader != NULL ); + + if( reader->img.empty() ) + { + icvGetNextFromBackgroundData( data, reader ); + } + + img = reader->img(Rect(reader->point.x, reader->point.y, data->winsize.width, data->winsize.height)).clone(); + + if( (int) ( reader->point.x + (1.0F + reader->stepfactor ) * data->winsize.width ) + < reader->img.cols ) + { + reader->point.x += (int) (reader->stepfactor * data->winsize.width); + } + else + { + reader->point.x = reader->offset.x; + if( (int) ( reader->point.y + (1.0F + reader->stepfactor ) * data->winsize.height ) + < reader->img.rows ) + { + reader->point.y += (int) (reader->stepfactor * data->winsize.height); + } + else + { + reader->point.y = reader->offset.y; + reader->scale *= reader->scalefactor; + if( reader->scale <= 1.0F ) + { + resize(reader->src, reader->img, + Size((int)(reader->scale * reader->src.cols), (int)(reader->scale * reader->src.rows)), 0, 0, INTER_LINEAR_EXACT); + } + else + { + icvGetNextFromBackgroundData( data, reader ); + } + } + } +} + +/* + * icvInitBackgroundReaders + * + * Initialize background reading process. + * and are initialized. + * Must be called before any usage of background + * + * filename - name of background description file + * winsize - size of images will be obtained from background + * + * return 1 on success, 0 otherwise. + */ +static int icvInitBackgroundReaders( const char* filename, Size winsize ) +{ + if( cvbgdata == NULL && filename != NULL ) + { + cvbgdata = icvCreateBackgroundData( filename, winsize ); + } + + if( cvbgdata ) + { + + #ifdef CV_OPENMP + #pragma omp parallel + #endif /* CV_OPENMP */ + { + #ifdef CV_OPENMP + #pragma omp critical(c_create_bg_data) + #endif /* CV_OPENMP */ + { + if( cvbgreader == NULL ) + { + cvbgreader = icvCreateBackgroundReader(); + } + } + } + + } + + return (cvbgdata != NULL); +} + +/* + * icvDestroyBackgroundReaders + * + * Finish background reading process + */ +static +void icvDestroyBackgroundReaders() +{ + /* release background reader in each thread */ + #ifdef CV_OPENMP + #pragma omp parallel + #endif /* CV_OPENMP */ + { + #ifdef CV_OPENMP + #pragma omp critical(c_release_bg_data) + #endif /* CV_OPENMP */ + { + if( cvbgreader != NULL ) + { + delete cvbgreader; + cvbgreader = NULL; + } + } + } + + if( cvbgdata != NULL ) + { + fastFree(cvbgdata); + cvbgdata = NULL; + } +} + +void cvCreateTrainingSamples( const char* filename, + const char* imgfilename, int bgcolor, int bgthreshold, + const char* bgfilename, int count, + int invert, int maxintensitydev, + double maxxangle, double maxyangle, double maxzangle, + int showsamples, + int winwidth, int winheight ) +{ + CvSampleDistortionData data; + + CV_Assert( filename != NULL ); + CV_Assert( imgfilename != NULL ); + + if( !icvMkDir( filename ) ) + { + fprintf( stderr, "Unable to create output file: %s\n", filename ); + return; + } + if( icvStartSampleDistortion( imgfilename, bgcolor, bgthreshold, &data ) ) + { + FILE* output = NULL; + + output = fopen( filename, "wb" ); + if( output != NULL ) + { + int i; + int inverse; + + const int hasbg = (bgfilename != NULL && icvInitBackgroundReaders( bgfilename, + Size( winwidth,winheight ) ) ); + + Mat sample( winheight, winwidth, CV_8UC1 ); + + icvWriteVecHeader( output, count, sample.cols, sample.rows ); + + if( showsamples ) + { + namedWindow( "Sample", WINDOW_AUTOSIZE ); + } + + inverse = invert; + for( i = 0; i < count; i++ ) + { + if( hasbg ) + { + icvGetBackgroundImage( cvbgdata, cvbgreader, sample ); + } + else + { + sample = bgcolor; + } + + if( invert == CV_RANDOM_INVERT ) + { + inverse = theRNG().uniform( 0, 2 ); + } + icvPlaceDistortedSample( sample, inverse, maxintensitydev, + maxxangle, maxyangle, maxzangle, + 0 /* nonzero means placing image without cut offs */, + 0.0 /* nozero adds random shifting */, + 0.0 /* nozero adds random scaling */, + &data ); + + if( showsamples ) + { + imshow( "Sample", sample ); + if( (waitKey( 0 ) & 0xFF) == 27 ) + { + showsamples = 0; + } + } + + icvWriteVecSample( output, sample ); + +#ifdef CV_VERBOSE + if( i % 500 == 0 ) + { + printf( "\r%3d%%", 100 * i / count ); + } +#endif /* CV_VERBOSE */ + } + icvDestroyBackgroundReaders(); + fclose( output ); + } /* if( output != NULL ) */ + } + +#ifdef CV_VERBOSE + printf( "\r \r" ); +#endif /* CV_VERBOSE */ + +} + +#define CV_INFO_FILENAME "info.dat" + +void cvCreateTestSamples( const char* infoname, + const char* imgfilename, int bgcolor, int bgthreshold, + const char* bgfilename, int count, + int invert, int maxintensitydev, + double maxxangle, double maxyangle, double maxzangle, + int showsamples, + int winwidth, int winheight, double maxscale ) +{ + CvSampleDistortionData data; + + CV_Assert( infoname != NULL ); + CV_Assert( imgfilename != NULL ); + CV_Assert( bgfilename != NULL ); + + if( !icvMkDir( infoname ) ) + { + +#if CV_VERBOSE + fprintf( stderr, "Unable to create directory hierarchy: %s\n", infoname ); +#endif /* CV_VERBOSE */ + + return; + } + if( icvStartSampleDistortion( imgfilename, bgcolor, bgthreshold, &data ) ) + { + char fullname[PATH_MAX]; + char* filename; + FILE* info; + + if( icvInitBackgroundReaders( bgfilename, Size( 10, 10 ) ) ) + { + int i; + int x, y, width, height; + float scale; + int inverse; + + if( showsamples ) + { + namedWindow( "Image", WINDOW_AUTOSIZE ); + } + + info = fopen( infoname, "w" ); + strcpy( fullname, infoname ); + filename = strrchr( fullname, '\\' ); + if( filename == NULL ) + { + filename = strrchr( fullname, '/' ); + } + if( filename == NULL ) + { + filename = fullname; + } + else + { + filename++; + } + + count = MIN( count, cvbgdata->count ); + inverse = invert; + for( i = 0; i < count; i++ ) + { + icvGetNextFromBackgroundData( cvbgdata, cvbgreader ); + if( maxscale < 0.0 ) + { + maxscale = MIN( 0.7F * cvbgreader->src.cols / winwidth, + 0.7F * cvbgreader->src.rows / winheight ); + } + + if( maxscale < 1.0F ) continue; + + scale = theRNG().uniform( 1.0F, (float)maxscale ); + + width = (int) (scale * winwidth); + height = (int) (scale * winheight); + x = (int) ( theRNG().uniform( 0.1, 0.8 ) * (cvbgreader->src.cols - width)); + y = (int) ( theRNG().uniform( 0.1, 0.8 ) * (cvbgreader->src.rows - height)); + + if( invert == CV_RANDOM_INVERT ) + { + inverse = theRNG().uniform( 0, 2 ); + } + icvPlaceDistortedSample( cvbgreader->src(Rect(x, y, width, height)), inverse, maxintensitydev, + maxxangle, maxyangle, maxzangle, + 1, 0.0, 0.0, &data ); + + + sprintf( filename, "%04d_%04d_%04d_%04d_%04d.jpg", + (i + 1), x, y, width, height ); + + if( info ) + { + fprintf( info, "%s %d %d %d %d %d\n", + filename, 1, x, y, width, height ); + } + + imwrite( fullname, cvbgreader->src ); + if( showsamples ) + { + imshow( "Image", cvbgreader->src ); + if( (waitKey( 0 ) & 0xFF) == 27 ) + { + showsamples = 0; + } + } + } + if( info ) fclose( info ); + icvDestroyBackgroundReaders(); + } + } +} + + +int cvCreateTrainingSamplesFromInfo( const char* infoname, const char* vecfilename, + int num, + int showsamples, + int winwidth, int winheight ) +{ + char fullname[PATH_MAX]; + char* filename; + + FILE* info; + FILE* vec; + int line; + int error; + int i; + int x, y, width, height; + int total; + + CV_Assert( infoname != NULL ); + CV_Assert( vecfilename != NULL ); + + total = 0; + if( !icvMkDir( vecfilename ) ) + { + +#if CV_VERBOSE + fprintf( stderr, "Unable to create directory hierarchy: %s\n", vecfilename ); +#endif /* CV_VERBOSE */ + + return total; + } + + info = fopen( infoname, "r" ); + if( info == NULL ) + { + +#if CV_VERBOSE + fprintf( stderr, "Unable to open file: %s\n", infoname ); +#endif /* CV_VERBOSE */ + + return total; + } + + vec = fopen( vecfilename, "wb" ); + if( vec == NULL ) + { + +#if CV_VERBOSE + fprintf( stderr, "Unable to open file: %s\n", vecfilename ); +#endif /* CV_VERBOSE */ + + fclose( info ); + + return total; + } + + icvWriteVecHeader( vec, num, winwidth, winheight ); + + if( showsamples ) + { + namedWindow( "Sample", WINDOW_AUTOSIZE ); + } + + strcpy( fullname, infoname ); + filename = strrchr( fullname, '\\' ); + if( filename == NULL ) + { + filename = strrchr( fullname, '/' ); + } + if( filename == NULL ) + { + filename = fullname; + } + else + { + filename++; + } + + for( line = 1, error = 0, total = 0; total < num ;line++ ) + { + Mat src; + int count; + + if(fscanf(info, "%s %d", filename, &count) == 2) + { + src = imread( fullname, IMREAD_GRAYSCALE ); + if(src.empty()) + { + +#if CV_VERBOSE + fprintf( stderr, "Unable to open image: %s\n", fullname ); +#endif /* CV_VERBOSE */ + + } + } + for( i = 0; (i < count) && (total < num); i++, total++ ) + { + error = ( fscanf( info, "%d %d %d %d", &x, &y, &width, &height ) != 4 ); + if( error ) break; + Mat sample; + resize( src(Rect(x, y, width, height)), sample, Size(winwidth, winheight), 0, 0, + width >= winwidth && height >= winheight ? INTER_AREA : INTER_LINEAR_EXACT ); + + if( showsamples ) + { + imshow( "Sample", sample ); + if( (waitKey( 0 ) & 0xFF) == 27 ) + { + showsamples = 0; + } + } + icvWriteVecSample( vec, sample ); + } + + if( error ) + { + +#if CV_VERBOSE + fprintf( stderr, "%s(%d) : parse error", infoname, line ); +#endif /* CV_VERBOSE */ + + break; + } + } + + fclose( vec ); + fclose( info ); + + return total; +} + +typedef struct CvVecFile +{ + FILE* input; + int count; + int vecsize; + int last; +} CvVecFile; + +static +int icvGetTraininDataFromVec( Mat& img, CvVecFile& userdata ) +{ + AutoBuffer vector(userdata.vecsize); + uchar tmp = 0; + int r = 0; + int c = 0; + + CV_Assert( img.rows * img.cols == userdata.vecsize ); + + size_t elements_read = fread( &tmp, sizeof( tmp ), 1, userdata.input ); + CV_Assert(elements_read == 1); + elements_read = fread(vector.data(), sizeof(short), userdata.vecsize, userdata.input); + CV_Assert(elements_read == (size_t)userdata.vecsize); + + if( feof( userdata.input ) || userdata.last++ >= userdata.count ) + { + return 0; + } + + for( r = 0; r < img.rows; r++ ) + { + for( c = 0; c < img.cols; c++ ) + { + img.at(r, c) = (uchar) ( vector[r * img.cols + c] ); + } + } + + return 1; +} +void cvShowVecSamples( const char* filename, int winwidth, int winheight, + double scale ) +{ + CvVecFile file; + short tmp; + int i; + + tmp = 0; + file.input = fopen( filename, "rb" ); + + if( file.input != NULL ) + { + size_t elements_read1 = fread( &file.count, sizeof( file.count ), 1, file.input ); + size_t elements_read2 = fread( &file.vecsize, sizeof( file.vecsize ), 1, file.input ); + size_t elements_read3 = fread( &tmp, sizeof( tmp ), 1, file.input ); + size_t elements_read4 = fread( &tmp, sizeof( tmp ), 1, file.input ); + CV_Assert(elements_read1 == 1 && elements_read2 == 1 && elements_read3 == 1 && elements_read4 == 1); + + if( file.vecsize != winwidth * winheight ) + { + int guessed_w = 0; + int guessed_h = 0; + + fprintf( stderr, "Warning: specified sample width=%d and height=%d " + "does not correspond to .vec file vector size=%d.\n", + winwidth, winheight, file.vecsize ); + if( file.vecsize > 0 ) + { + guessed_w = cvFloor( sqrt( (float) file.vecsize ) ); + if( guessed_w > 0 ) + { + guessed_h = file.vecsize / guessed_w; + } + } + + if( guessed_w <= 0 || guessed_h <= 0 || guessed_w * guessed_h != file.vecsize) + { + fprintf( stderr, "Error: failed to guess sample width and height\n" ); + fclose( file.input ); + + return; + } + else + { + winwidth = guessed_w; + winheight = guessed_h; + fprintf( stderr, "Guessed width=%d, guessed height=%d\n", + winwidth, winheight ); + } + } + + if( !feof( file.input ) && scale > 0 ) + { + file.last = 0; + namedWindow( "Sample", WINDOW_AUTOSIZE ); + for( i = 0; i < file.count; i++ ) + { + Mat sample(winheight, winwidth, CV_8UC1); + icvGetTraininDataFromVec( sample, file ); + if( scale != 1.0 ) + resize( sample, sample, + Size(MAX(1, cvCeil(scale * winwidth)), MAX(1, cvCeil(scale * winheight))), 0, 0, INTER_LINEAR_EXACT); + imshow( "Sample", sample ); + if( waitKey( 0 ) == 27 ) break; + } + } + fclose( file.input ); + } +} diff --git a/opencv-apps/createsamples/utility.hpp b/opencv-apps/createsamples/utility.hpp new file mode 100644 index 0000000..d04947c --- /dev/null +++ b/opencv-apps/createsamples/utility.hpp @@ -0,0 +1,124 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#ifndef __CREATESAMPLES_UTILITY_HPP__ +#define __CREATESAMPLES_UTILITY_HPP__ + +#define CV_VERBOSE 1 + +/* + * cvCreateTrainingSamples + * + * Create training samples applying random distortions to sample image and + * store them in .vec file + * + * filename - .vec file name + * imgfilename - sample image file name + * bgcolor - background color for sample image + * bgthreshold - background color threshold. Pixels those colors are in range + * [bgcolor-bgthreshold, bgcolor+bgthreshold] are considered as transparent + * bgfilename - background description file name. If not NULL samples + * will be put on arbitrary background + * count - desired number of samples + * invert - if not 0 sample foreground pixels will be inverted + * if invert == CV_RANDOM_INVERT then samples will be inverted randomly + * maxintensitydev - desired max intensity deviation of foreground samples pixels + * maxxangle - max rotation angles + * maxyangle + * maxzangle + * showsamples - if not 0 samples will be shown + * winwidth - desired samples width + * winheight - desired samples height + */ +#define CV_RANDOM_INVERT 0x7FFFFFFF + +void cvCreateTrainingSamples( const char* filename, + const char* imgfilename, int bgcolor, int bgthreshold, + const char* bgfilename, int count, + int invert = 0, int maxintensitydev = 40, + double maxxangle = 1.1, + double maxyangle = 1.1, + double maxzangle = 0.5, + int showsamples = 0, + int winwidth = 24, int winheight = 24 ); + +void cvCreateTestSamples( const char* infoname, + const char* imgfilename, int bgcolor, int bgthreshold, + const char* bgfilename, int count, + int invert, int maxintensitydev, + double maxxangle, double maxyangle, double maxzangle, + int showsamples, + int winwidth, int winheight, double maxscale ); + +/* + * cvCreateTrainingSamplesFromInfo + * + * Create training samples from a set of marked up images and store them into .vec file + * infoname - file in which marked up image descriptions are stored + * num - desired number of samples + * showsamples - if not 0 samples will be shown + * winwidth - sample width + * winheight - sample height + * + * Return number of successfully created samples + */ +int cvCreateTrainingSamplesFromInfo( const char* infoname, const char* vecfilename, + int num, + int showsamples, + int winwidth, int winheight ); + +/* + * cvShowVecSamples + * + * Shows samples stored in .vec file + * + * filename + * .vec file name + * winwidth + * sample width + * winheight + * sample height + * scale + * the scale each sample is adjusted to + */ +void cvShowVecSamples( const char* filename, int winwidth, int winheight, double scale ); + +#endif //__CREATESAMPLES_UTILITY_HPP__ diff --git a/opencv-apps/createsamples/utility.hpp.gch b/opencv-apps/createsamples/utility.hpp.gch new file mode 100644 index 0000000..22c239c Binary files /dev/null and b/opencv-apps/createsamples/utility.hpp.gch differ diff --git a/opencv-apps/interactive-calibration/CMakeLists.txt b/opencv-apps/interactive-calibration/CMakeLists.txt new file mode 100644 index 0000000..dacbb13 --- /dev/null +++ b/opencv-apps/interactive-calibration/CMakeLists.txt @@ -0,0 +1,6 @@ +set(DEPS opencv_core opencv_imgproc opencv_features2d opencv_highgui opencv_calib3d opencv_videoio) +if(${BUILD_opencv_aruco}) + list(APPEND DEPS opencv_aruco) +endif() +file(GLOB SRCS *.cpp) +ocv_add_application(opencv_interactive-calibration MODULES ${DEPS} SRCS ${SRCS}) diff --git a/opencv-apps/interactive-calibration/calibCommon.hpp b/opencv-apps/interactive-calibration/calibCommon.hpp new file mode 100644 index 0000000..6acf610 --- /dev/null +++ b/opencv-apps/interactive-calibration/calibCommon.hpp @@ -0,0 +1,123 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef CALIB_COMMON_HPP +#define CALIB_COMMON_HPP + +#include + +#include +#include +#include + +namespace calib +{ + #define OVERLAY_DELAY 1000 + #define IMAGE_MAX_WIDTH 1280 + #define IMAGE_MAX_HEIGHT 960 + + bool showOverlayMessage(const std::string& message); + + enum InputType { Video, Pictures }; + enum InputVideoSource { Camera, File }; + enum TemplateType { AcirclesGrid, Chessboard, chAruco, DoubleAcirclesGrid }; + + static const std::string mainWindowName = "Calibration"; + static const std::string gridWindowName = "Board locations"; + static const std::string consoleHelp = "Hot keys:\nesc - exit application\n" + "s - save current data to .xml file\n" + "r - delete last frame\n" + "u - enable/disable applying undistortion\n" + "d - delete all frames\n" + "v - switch visualization"; + + static const double sigmaMult = 1.96; + + struct calibrationData + { + cv::Mat cameraMatrix; + cv::Mat distCoeffs; + cv::Mat stdDeviations; + cv::Mat perViewErrors; + std::vector rvecs; + std::vector tvecs; + double totalAvgErr; + cv::Size imageSize; + + std::vector > imagePoints; + std::vector< std::vector > objectPoints; + + std::vector allCharucoCorners; + std::vector allCharucoIds; + + cv::Mat undistMap1, undistMap2; + + calibrationData() + { + imageSize = cv::Size(IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT); + } + }; + + struct cameraParameters + { + cv::Mat cameraMatrix; + cv::Mat distCoeffs; + cv::Mat stdDeviations; + double avgError; + + cameraParameters(){} + cameraParameters(cv::Mat& _cameraMatrix, cv::Mat& _distCoeffs, cv::Mat& _stdDeviations, double _avgError = 0) : + cameraMatrix(_cameraMatrix), distCoeffs(_distCoeffs), stdDeviations(_stdDeviations), avgError(_avgError) + {} + }; + + struct captureParameters + { + InputType captureMethod; + InputVideoSource source; + TemplateType board; + cv::Size boardSize; + int charucoDictName; + int calibrationStep; + float charucoSquareLenght, charucoMarkerSize; + float captureDelay; + float squareSize; + float templDst; + std::string videoFileName; + bool flipVertical; + int camID; + int fps; + cv::Size cameraResolution; + int maxFramesNum; + int minFramesNum; + + captureParameters() + { + calibrationStep = 1; + captureDelay = 500.f; + maxFramesNum = 30; + minFramesNum = 10; + fps = 30; + cameraResolution = cv::Size(IMAGE_MAX_WIDTH, IMAGE_MAX_HEIGHT); + } + }; + + struct internalParameters + { + double solverEps; + int solverMaxIters; + bool fastSolving; + double filterAlpha; + + internalParameters() + { + solverEps = 1e-7; + solverMaxIters = 30; + fastSolving = false; + filterAlpha = 0.1; + } + }; +} + +#endif diff --git a/opencv-apps/interactive-calibration/calibController.cpp b/opencv-apps/interactive-calibration/calibController.cpp new file mode 100644 index 0000000..efe937b --- /dev/null +++ b/opencv-apps/interactive-calibration/calibController.cpp @@ -0,0 +1,334 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "calibController.hpp" + +#include +#include +#include + +#include +#include + +double calib::calibController::estimateCoverageQuality() +{ + int gridSize = 10; + int xGridStep = mCalibData->imageSize.width / gridSize; + int yGridStep = mCalibData->imageSize.height / gridSize; + std::vector pointsInCell(gridSize*gridSize); + + std::fill(pointsInCell.begin(), pointsInCell.end(), 0); + + for(std::vector >::iterator it = mCalibData->imagePoints.begin(); it != mCalibData->imagePoints.end(); ++it) + for(std::vector::iterator pointIt = (*it).begin(); pointIt != (*it).end(); ++pointIt) { + int i = (int)((*pointIt).x / xGridStep); + int j = (int)((*pointIt).y / yGridStep); + pointsInCell[i*gridSize + j]++; + } + + for(std::vector::iterator it = mCalibData->allCharucoCorners.begin(); it != mCalibData->allCharucoCorners.end(); ++it) + for(int l = 0; l < (*it).size[0]; l++) { + int i = (int)((*it).at(l, 0) / xGridStep); + int j = (int)((*it).at(l, 1) / yGridStep); + pointsInCell[i*gridSize + j]++; + } + + cv::Mat mean, stdDev; + cv::meanStdDev(pointsInCell, mean, stdDev); + + return mean.at(0) / (stdDev.at(0) + 1e-7); +} + +calib::calibController::calibController() +{ + mCalibFlags = 0; +} + +calib::calibController::calibController(cv::Ptr data, int initialFlags, bool autoTuning, int minFramesNum) : + mCalibData(data) +{ + mCalibFlags = initialFlags; + mNeedTuning = autoTuning; + mMinFramesNum = minFramesNum; + mConfIntervalsState = false; + mCoverageQualityState = false; +} + +void calib::calibController::updateState() +{ + if(mCalibData->cameraMatrix.total()) { + const double relErrEps = 0.05; + bool fConfState = false, cConfState = false, dConfState = true; + if(sigmaMult*mCalibData->stdDeviations.at(0) / mCalibData->cameraMatrix.at(0,0) < relErrEps && + sigmaMult*mCalibData->stdDeviations.at(1) / mCalibData->cameraMatrix.at(1,1) < relErrEps) + fConfState = true; + if(sigmaMult*mCalibData->stdDeviations.at(2) / mCalibData->cameraMatrix.at(0,2) < relErrEps && + sigmaMult*mCalibData->stdDeviations.at(3) / mCalibData->cameraMatrix.at(1,2) < relErrEps) + cConfState = true; + + for(int i = 0; i < 5; i++) + if(mCalibData->stdDeviations.at(4+i) / fabs(mCalibData->distCoeffs.at(i)) > 1) + dConfState = false; + + mConfIntervalsState = fConfState && cConfState && dConfState; + } + + if(getFramesNumberState()) + mCoverageQualityState = estimateCoverageQuality() > 1.8 ? true : false; + + if (getFramesNumberState() && mNeedTuning) { + if( !(mCalibFlags & cv::CALIB_FIX_ASPECT_RATIO) && + mCalibData->cameraMatrix.total()) { + double fDiff = fabs(mCalibData->cameraMatrix.at(0,0) - + mCalibData->cameraMatrix.at(1,1)); + + if (fDiff < 3*mCalibData->stdDeviations.at(0) && + fDiff < 3*mCalibData->stdDeviations.at(1)) { + mCalibFlags |= cv::CALIB_FIX_ASPECT_RATIO; + mCalibData->cameraMatrix.at(0,0) = + mCalibData->cameraMatrix.at(1,1); + } + } + + if(!(mCalibFlags & cv::CALIB_ZERO_TANGENT_DIST)) { + const double eps = 0.005; + if(fabs(mCalibData->distCoeffs.at(2)) < eps && + fabs(mCalibData->distCoeffs.at(3)) < eps) + mCalibFlags |= cv::CALIB_ZERO_TANGENT_DIST; + } + + if(!(mCalibFlags & cv::CALIB_FIX_K1)) { + const double eps = 0.005; + if(fabs(mCalibData->distCoeffs.at(0)) < eps) + mCalibFlags |= cv::CALIB_FIX_K1; + } + + if(!(mCalibFlags & cv::CALIB_FIX_K2)) { + const double eps = 0.005; + if(fabs(mCalibData->distCoeffs.at(1)) < eps) + mCalibFlags |= cv::CALIB_FIX_K2; + } + + if(!(mCalibFlags & cv::CALIB_FIX_K3)) { + const double eps = 0.005; + if(fabs(mCalibData->distCoeffs.at(4)) < eps) + mCalibFlags |= cv::CALIB_FIX_K3; + } + + } +} + +bool calib::calibController::getCommonCalibrationState() const +{ + int rating = (int)getFramesNumberState() + (int)getConfidenceIntrervalsState() + + (int)getRMSState() + (int)mCoverageQualityState; + return rating == 4; +} + +bool calib::calibController::getFramesNumberState() const +{ + return std::max(mCalibData->imagePoints.size(), mCalibData->allCharucoCorners.size()) > mMinFramesNum; +} + +bool calib::calibController::getConfidenceIntrervalsState() const +{ + return mConfIntervalsState; +} + +bool calib::calibController::getRMSState() const +{ + return mCalibData->totalAvgErr < 0.5; +} + +int calib::calibController::getNewFlags() const +{ + return mCalibFlags; +} + + +//////////////////// calibDataController + +double calib::calibDataController::estimateGridSubsetQuality(size_t excludedIndex) +{ + { + int gridSize = 10; + int xGridStep = mCalibData->imageSize.width / gridSize; + int yGridStep = mCalibData->imageSize.height / gridSize; + std::vector pointsInCell(gridSize*gridSize); + + std::fill(pointsInCell.begin(), pointsInCell.end(), 0); + + for(size_t k = 0; k < mCalibData->imagePoints.size(); k++) + if(k != excludedIndex) + for(std::vector::iterator pointIt = mCalibData->imagePoints[k].begin(); pointIt != mCalibData->imagePoints[k].end(); ++pointIt) { + int i = (int)((*pointIt).x / xGridStep); + int j = (int)((*pointIt).y / yGridStep); + pointsInCell[i*gridSize + j]++; + } + + for(size_t k = 0; k < mCalibData->allCharucoCorners.size(); k++) + if(k != excludedIndex) + for(int l = 0; l < mCalibData->allCharucoCorners[k].size[0]; l++) { + int i = (int)(mCalibData->allCharucoCorners[k].at(l, 0) / xGridStep); + int j = (int)(mCalibData->allCharucoCorners[k].at(l, 1) / yGridStep); + pointsInCell[i*gridSize + j]++; + } + + cv::Mat mean, stdDev; + cv::meanStdDev(pointsInCell, mean, stdDev); + + return mean.at(0) / (stdDev.at(0) + 1e-7); + } +} + +calib::calibDataController::calibDataController(cv::Ptr data, int maxFrames, double convParameter) : + mCalibData(data), mParamsFileName("CamParams.xml") +{ + mMaxFramesNum = maxFrames; + mAlpha = convParameter; +} + +calib::calibDataController::calibDataController() +{ + +} + +void calib::calibDataController::filterFrames() +{ + size_t numberOfFrames = std::max(mCalibData->allCharucoIds.size(), mCalibData->imagePoints.size()); + CV_Assert(numberOfFrames == mCalibData->perViewErrors.total()); + if(numberOfFrames >= mMaxFramesNum) { + + double worstValue = -HUGE_VAL, maxQuality = estimateGridSubsetQuality(numberOfFrames); + size_t worstElemIndex = 0; + for(size_t i = 0; i < numberOfFrames; i++) { + double gridQDelta = estimateGridSubsetQuality(i) - maxQuality; + double currentValue = mCalibData->perViewErrors.at((int)i)*mAlpha + gridQDelta*(1. - mAlpha); + if(currentValue > worstValue) { + worstValue = currentValue; + worstElemIndex = i; + } + } + showOverlayMessage(cv::format("Frame %zu is worst", worstElemIndex + 1)); + + if(mCalibData->imagePoints.size()) { + mCalibData->imagePoints.erase(mCalibData->imagePoints.begin() + worstElemIndex); + mCalibData->objectPoints.erase(mCalibData->objectPoints.begin() + worstElemIndex); + } + else { + mCalibData->allCharucoCorners.erase(mCalibData->allCharucoCorners.begin() + worstElemIndex); + mCalibData->allCharucoIds.erase(mCalibData->allCharucoIds.begin() + worstElemIndex); + } + + cv::Mat newErrorsVec = cv::Mat((int)numberOfFrames - 1, 1, CV_64F); + std::copy(mCalibData->perViewErrors.ptr(0), + mCalibData->perViewErrors.ptr((int)worstElemIndex), newErrorsVec.ptr(0)); + if((int)worstElemIndex < (int)numberOfFrames-1) { + std::copy(mCalibData->perViewErrors.ptr((int)worstElemIndex + 1), mCalibData->perViewErrors.ptr((int)numberOfFrames), + newErrorsVec.ptr((int)worstElemIndex)); + } + mCalibData->perViewErrors = newErrorsVec; + } +} + +void calib::calibDataController::setParametersFileName(const std::string &name) +{ + mParamsFileName = name; +} + +void calib::calibDataController::deleteLastFrame() +{ + if( !mCalibData->imagePoints.empty()) { + mCalibData->imagePoints.pop_back(); + mCalibData->objectPoints.pop_back(); + } + + if (!mCalibData->allCharucoCorners.empty()) { + mCalibData->allCharucoCorners.pop_back(); + mCalibData->allCharucoIds.pop_back(); + } + + if(!mParamsStack.empty()) { + mCalibData->cameraMatrix = (mParamsStack.top()).cameraMatrix; + mCalibData->distCoeffs = (mParamsStack.top()).distCoeffs; + mCalibData->stdDeviations = (mParamsStack.top()).stdDeviations; + mCalibData->totalAvgErr = (mParamsStack.top()).avgError; + mParamsStack.pop(); + } +} + +void calib::calibDataController::rememberCurrentParameters() +{ + cv::Mat oldCameraMat, oldDistcoeefs, oldStdDevs; + mCalibData->cameraMatrix.copyTo(oldCameraMat); + mCalibData->distCoeffs.copyTo(oldDistcoeefs); + mCalibData->stdDeviations.copyTo(oldStdDevs); + mParamsStack.push(cameraParameters(oldCameraMat, oldDistcoeefs, oldStdDevs, mCalibData->totalAvgErr)); +} + +void calib::calibDataController::deleteAllData() +{ + mCalibData->imagePoints.clear(); + mCalibData->objectPoints.clear(); + mCalibData->allCharucoCorners.clear(); + mCalibData->allCharucoIds.clear(); + mCalibData->cameraMatrix = mCalibData->distCoeffs = cv::Mat(); + mParamsStack = std::stack(); + rememberCurrentParameters(); +} + +bool calib::calibDataController::saveCurrentCameraParameters() const +{ + bool success = false; + if(mCalibData->cameraMatrix.total()) { + cv::FileStorage parametersWriter(mParamsFileName, cv::FileStorage::WRITE); + if(parametersWriter.isOpened()) { + time_t rawtime; + time(&rawtime); + char buf[256]; + strftime(buf, sizeof(buf)-1, "%c", localtime(&rawtime)); + + parametersWriter << "calibrationDate" << buf; + parametersWriter << "framesCount" << std::max((int)mCalibData->objectPoints.size(), (int)mCalibData->allCharucoCorners.size()); + parametersWriter << "cameraResolution" << mCalibData->imageSize; + parametersWriter << "cameraMatrix" << mCalibData->cameraMatrix; + parametersWriter << "cameraMatrix_std_dev" << mCalibData->stdDeviations.rowRange(cv::Range(0, 4)); + parametersWriter << "dist_coeffs" << mCalibData->distCoeffs; + parametersWriter << "dist_coeffs_std_dev" << mCalibData->stdDeviations.rowRange(cv::Range(4, 9)); + parametersWriter << "avg_reprojection_error" << mCalibData->totalAvgErr; + + parametersWriter.release(); + success = true; + } + } + return success; +} + +void calib::calibDataController::printParametersToConsole(std::ostream &output) const +{ + const char* border = "---------------------------------------------------"; + output << border << std::endl; + output << "Frames used for calibration: " << std::max(mCalibData->objectPoints.size(), mCalibData->allCharucoCorners.size()) + << " \t RMS = " << mCalibData->totalAvgErr << std::endl; + if(mCalibData->cameraMatrix.at(0,0) == mCalibData->cameraMatrix.at(1,1)) + output << "F = " << mCalibData->cameraMatrix.at(1,1) << " +- " << sigmaMult*mCalibData->stdDeviations.at(1) << std::endl; + else + output << "Fx = " << mCalibData->cameraMatrix.at(0,0) << " +- " << sigmaMult*mCalibData->stdDeviations.at(0) << " \t " + << "Fy = " << mCalibData->cameraMatrix.at(1,1) << " +- " << sigmaMult*mCalibData->stdDeviations.at(1) << std::endl; + output << "Cx = " << mCalibData->cameraMatrix.at(0,2) << " +- " << sigmaMult*mCalibData->stdDeviations.at(2) << " \t" + << "Cy = " << mCalibData->cameraMatrix.at(1,2) << " +- " << sigmaMult*mCalibData->stdDeviations.at(3) << std::endl; + output << "K1 = " << mCalibData->distCoeffs.at(0) << " +- " << sigmaMult*mCalibData->stdDeviations.at(4) << std::endl; + output << "K2 = " << mCalibData->distCoeffs.at(1) << " +- " << sigmaMult*mCalibData->stdDeviations.at(5) << std::endl; + output << "K3 = " << mCalibData->distCoeffs.at(4) << " +- " << sigmaMult*mCalibData->stdDeviations.at(8) << std::endl; + output << "TD1 = " << mCalibData->distCoeffs.at(2) << " +- " << sigmaMult*mCalibData->stdDeviations.at(6) << std::endl; + output << "TD2 = " << mCalibData->distCoeffs.at(3) << " +- " << sigmaMult*mCalibData->stdDeviations.at(7) << std::endl; +} + +void calib::calibDataController::updateUndistortMap() +{ + cv::initUndistortRectifyMap(mCalibData->cameraMatrix, mCalibData->distCoeffs, cv::noArray(), + cv::getOptimalNewCameraMatrix(mCalibData->cameraMatrix, mCalibData->distCoeffs, mCalibData->imageSize, 0.0, mCalibData->imageSize), + mCalibData->imageSize, CV_16SC2, mCalibData->undistMap1, mCalibData->undistMap2); + +} diff --git a/opencv-apps/interactive-calibration/calibController.hpp b/opencv-apps/interactive-calibration/calibController.hpp new file mode 100644 index 0000000..7a0f3f7 --- /dev/null +++ b/opencv-apps/interactive-calibration/calibController.hpp @@ -0,0 +1,69 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef CALIB_CONTROLLER_HPP +#define CALIB_CONTROLLER_HPP + +#include "calibCommon.hpp" + +#include +#include +#include + +namespace calib { + + class calibController + { + protected: + cv::Ptr mCalibData; + int mCalibFlags; + unsigned mMinFramesNum; + bool mNeedTuning; + bool mConfIntervalsState; + bool mCoverageQualityState; + + double estimateCoverageQuality(); + public: + calibController(); + calibController(cv::Ptr data, int initialFlags, bool autoTuning, + int minFramesNum); + + void updateState(); + + bool getCommonCalibrationState() const; + + bool getFramesNumberState() const; + bool getConfidenceIntrervalsState() const; + bool getRMSState() const; + bool getPointsCoverageState() const; + int getNewFlags() const; + }; + + class calibDataController + { + protected: + cv::Ptr mCalibData; + std::stack mParamsStack; + std::string mParamsFileName; + unsigned mMaxFramesNum; + double mAlpha; + + double estimateGridSubsetQuality(size_t excludedIndex); + public: + calibDataController(cv::Ptr data, int maxFrames, double convParameter); + calibDataController(); + + void filterFrames(); + void setParametersFileName(const std::string& name); + void deleteLastFrame(); + void rememberCurrentParameters(); + void deleteAllData(); + bool saveCurrentCameraParameters() const; + void printParametersToConsole(std::ostream &output) const; + void updateUndistortMap(); + }; + +} + +#endif diff --git a/opencv-apps/interactive-calibration/calibPipeline.cpp b/opencv-apps/interactive-calibration/calibPipeline.cpp new file mode 100644 index 0000000..a92dbff --- /dev/null +++ b/opencv-apps/interactive-calibration/calibPipeline.cpp @@ -0,0 +1,97 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "calibPipeline.hpp" + +#include + +#include + +using namespace calib; + +#define CAP_DELAY 10 + +cv::Size CalibPipeline::getCameraResolution() +{ + mCapture.set(cv::CAP_PROP_FRAME_WIDTH, 10000); + mCapture.set(cv::CAP_PROP_FRAME_HEIGHT, 10000); + int w = (int)mCapture.get(cv::CAP_PROP_FRAME_WIDTH); + int h = (int)mCapture.get(cv::CAP_PROP_FRAME_HEIGHT); + return cv::Size(w,h); +} + +CalibPipeline::CalibPipeline(captureParameters params) : + mCaptureParams(params) +{ + +} + +PipelineExitStatus CalibPipeline::start(std::vector > processors) +{ + if(mCaptureParams.source == Camera && !mCapture.isOpened()) + { + mCapture.open(mCaptureParams.camID); + cv::Size maxRes = getCameraResolution(); + cv::Size neededRes = mCaptureParams.cameraResolution; + + if(maxRes.width < neededRes.width) { + double aR = (double)maxRes.width / maxRes.height; + mCapture.set(cv::CAP_PROP_FRAME_WIDTH, neededRes.width); + mCapture.set(cv::CAP_PROP_FRAME_HEIGHT, neededRes.width/aR); + } + else if(maxRes.height < neededRes.height) { + double aR = (double)maxRes.width / maxRes.height; + mCapture.set(cv::CAP_PROP_FRAME_HEIGHT, neededRes.height); + mCapture.set(cv::CAP_PROP_FRAME_WIDTH, neededRes.height*aR); + } + else { + mCapture.set(cv::CAP_PROP_FRAME_HEIGHT, neededRes.height); + mCapture.set(cv::CAP_PROP_FRAME_WIDTH, neededRes.width); + } + mCapture.set(cv::CAP_PROP_AUTOFOCUS, 0); + } + else if (mCaptureParams.source == File && !mCapture.isOpened()) + mCapture.open(mCaptureParams.videoFileName); + mImageSize = cv::Size((int)mCapture.get(cv::CAP_PROP_FRAME_WIDTH), (int)mCapture.get(cv::CAP_PROP_FRAME_HEIGHT)); + + if(!mCapture.isOpened()) + throw std::runtime_error("Unable to open video source"); + + cv::Mat frame, processedFrame; + while(mCapture.grab()) { + mCapture.retrieve(frame); + if(mCaptureParams.flipVertical) + cv::flip(frame, frame, -1); + + frame.copyTo(processedFrame); + for (std::vector >::iterator it = processors.begin(); it != processors.end(); ++it) + processedFrame = (*it)->processFrame(processedFrame); + cv::imshow(mainWindowName, processedFrame); + char key = (char)cv::waitKey(CAP_DELAY); + + if(key == 27) // esc + return Finished; + else if (key == 114) // r + return DeleteLastFrame; + else if (key == 100) // d + return DeleteAllFrames; + else if (key == 115) // s + return SaveCurrentData; + else if (key == 117) // u + return SwitchUndistort; + else if (key == 118) // v + return SwitchVisualisation; + + for (std::vector >::iterator it = processors.begin(); it != processors.end(); ++it) + if((*it)->isProcessed()) + return Calibrate; + } + + return Finished; +} + +cv::Size CalibPipeline::getImageSize() const +{ + return mImageSize; +} diff --git a/opencv-apps/interactive-calibration/calibPipeline.hpp b/opencv-apps/interactive-calibration/calibPipeline.hpp new file mode 100644 index 0000000..1c22b0d --- /dev/null +++ b/opencv-apps/interactive-calibration/calibPipeline.hpp @@ -0,0 +1,45 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + + +#ifndef CALIB_PIPELINE_HPP +#define CALIB_PIPELINE_HPP + +#include + +#include + +#include "calibCommon.hpp" +#include "frameProcessor.hpp" + +namespace calib +{ + +enum PipelineExitStatus { Finished, + DeleteLastFrame, + Calibrate, + DeleteAllFrames, + SaveCurrentData, + SwitchUndistort, + SwitchVisualisation + }; + +class CalibPipeline +{ +protected: + captureParameters mCaptureParams; + cv::Size mImageSize; + cv::VideoCapture mCapture; + + cv::Size getCameraResolution(); + +public: + CalibPipeline(captureParameters params); + PipelineExitStatus start(std::vector > processors); + cv::Size getImageSize() const; +}; + +} + +#endif diff --git a/opencv-apps/interactive-calibration/defaultConfig.xml b/opencv-apps/interactive-calibration/defaultConfig.xml new file mode 100644 index 0000000..d14ba86 --- /dev/null +++ b/opencv-apps/interactive-calibration/defaultConfig.xml @@ -0,0 +1,14 @@ + + +0 +200 +100 +1 +30 +10 +1e-7 +30 +0 +0.1 +800 600 + \ No newline at end of file diff --git a/opencv-apps/interactive-calibration/frameProcessor.cpp b/opencv-apps/interactive-calibration/frameProcessor.cpp new file mode 100644 index 0000000..aecc2c0 --- /dev/null +++ b/opencv-apps/interactive-calibration/frameProcessor.cpp @@ -0,0 +1,530 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "frameProcessor.hpp" +#include "rotationConverters.hpp" + +#include +#include +#include + +#include +#include +#include +#include + +using namespace calib; + +#define VIDEO_TEXT_SIZE 4 +#define POINT_SIZE 5 + +static cv::SimpleBlobDetector::Params getDetectorParams() +{ + cv::SimpleBlobDetector::Params detectorParams; + + detectorParams.thresholdStep = 40; + detectorParams.minThreshold = 20; + detectorParams.maxThreshold = 500; + detectorParams.minRepeatability = 2; + detectorParams.minDistBetweenBlobs = 5; + + detectorParams.filterByColor = true; + detectorParams.blobColor = 0; + + detectorParams.filterByArea = true; + detectorParams.minArea = 5; + detectorParams.maxArea = 5000; + + detectorParams.filterByCircularity = false; + detectorParams.minCircularity = 0.8f; + detectorParams.maxCircularity = std::numeric_limits::max(); + + detectorParams.filterByInertia = true; + detectorParams.minInertiaRatio = 0.1f; + detectorParams.maxInertiaRatio = std::numeric_limits::max(); + + detectorParams.filterByConvexity = true; + detectorParams.minConvexity = 0.8f; + detectorParams.maxConvexity = std::numeric_limits::max(); + + return detectorParams; +} + +FrameProcessor::~FrameProcessor() +{ + +} + +bool CalibProcessor::detectAndParseChessboard(const cv::Mat &frame) +{ + int chessBoardFlags = cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_NORMALIZE_IMAGE | cv::CALIB_CB_FAST_CHECK; + bool isTemplateFound = cv::findChessboardCorners(frame, mBoardSize, mCurrentImagePoints, chessBoardFlags); + + if (isTemplateFound) { + cv::Mat viewGray; + cv::cvtColor(frame, viewGray, cv::COLOR_BGR2GRAY); + cv::cornerSubPix(viewGray, mCurrentImagePoints, cv::Size(11,11), + cv::Size(-1,-1), cv::TermCriteria( cv::TermCriteria::EPS+cv::TermCriteria::COUNT, 30, 0.1 )); + cv::drawChessboardCorners(frame, mBoardSize, cv::Mat(mCurrentImagePoints), isTemplateFound); + mTemplateLocations.insert(mTemplateLocations.begin(), mCurrentImagePoints[0]); + } + return isTemplateFound; +} + +bool CalibProcessor::detectAndParseChAruco(const cv::Mat &frame) +{ +#ifdef HAVE_OPENCV_ARUCO + cv::Ptr board = mCharucoBoard.staticCast(); + + std::vector > corners, rejected; + std::vector ids; + cv::aruco::detectMarkers(frame, mArucoDictionary, corners, ids, cv::aruco::DetectorParameters::create(), rejected); + cv::aruco::refineDetectedMarkers(frame, board, corners, ids, rejected); + cv::Mat currentCharucoCorners, currentCharucoIds; + if(ids.size() > 0) + cv::aruco::interpolateCornersCharuco(corners, ids, frame, mCharucoBoard, currentCharucoCorners, + currentCharucoIds); + if(ids.size() > 0) cv::aruco::drawDetectedMarkers(frame, corners); + + if(currentCharucoCorners.total() > 3) { + float centerX = 0, centerY = 0; + for (int i = 0; i < currentCharucoCorners.size[0]; i++) { + centerX += currentCharucoCorners.at(i, 0); + centerY += currentCharucoCorners.at(i, 1); + } + centerX /= currentCharucoCorners.size[0]; + centerY /= currentCharucoCorners.size[0]; + + mTemplateLocations.insert(mTemplateLocations.begin(), cv::Point2f(centerX, centerY)); + cv::aruco::drawDetectedCornersCharuco(frame, currentCharucoCorners, currentCharucoIds); + mCurrentCharucoCorners = currentCharucoCorners; + mCurrentCharucoIds = currentCharucoIds; + return true; + } +#else + CV_UNUSED(frame); +#endif + return false; +} + +bool CalibProcessor::detectAndParseACircles(const cv::Mat &frame) +{ + bool isTemplateFound = findCirclesGrid(frame, mBoardSize, mCurrentImagePoints, cv::CALIB_CB_ASYMMETRIC_GRID, mBlobDetectorPtr); + if(isTemplateFound) { + mTemplateLocations.insert(mTemplateLocations.begin(), mCurrentImagePoints[0]); + cv::drawChessboardCorners(frame, mBoardSize, cv::Mat(mCurrentImagePoints), isTemplateFound); + } + return isTemplateFound; +} + +bool CalibProcessor::detectAndParseDualACircles(const cv::Mat &frame) +{ + std::vector blackPointbuf; + + cv::Mat invertedView; + cv::bitwise_not(frame, invertedView); + bool isWhiteGridFound = cv::findCirclesGrid(frame, mBoardSize, mCurrentImagePoints, cv::CALIB_CB_ASYMMETRIC_GRID, mBlobDetectorPtr); + if(!isWhiteGridFound) + return false; + bool isBlackGridFound = cv::findCirclesGrid(invertedView, mBoardSize, blackPointbuf, cv::CALIB_CB_ASYMMETRIC_GRID, mBlobDetectorPtr); + + if(!isBlackGridFound) + { + mCurrentImagePoints.clear(); + return false; + } + cv::drawChessboardCorners(frame, mBoardSize, cv::Mat(mCurrentImagePoints), isWhiteGridFound); + cv::drawChessboardCorners(frame, mBoardSize, cv::Mat(blackPointbuf), isBlackGridFound); + mCurrentImagePoints.insert(mCurrentImagePoints.end(), blackPointbuf.begin(), blackPointbuf.end()); + mTemplateLocations.insert(mTemplateLocations.begin(), mCurrentImagePoints[0]); + + return true; +} + +void CalibProcessor::saveFrameData() +{ + std::vector objectPoints; + + switch(mBoardType) + { + case Chessboard: + objectPoints.reserve(mBoardSize.height*mBoardSize.width); + for( int i = 0; i < mBoardSize.height; ++i ) + for( int j = 0; j < mBoardSize.width; ++j ) + objectPoints.push_back(cv::Point3f(j*mSquareSize, i*mSquareSize, 0)); + mCalibData->imagePoints.push_back(mCurrentImagePoints); + mCalibData->objectPoints.push_back(objectPoints); + break; + case chAruco: + mCalibData->allCharucoCorners.push_back(mCurrentCharucoCorners); + mCalibData->allCharucoIds.push_back(mCurrentCharucoIds); + break; + case AcirclesGrid: + objectPoints.reserve(mBoardSize.height*mBoardSize.width); + for( int i = 0; i < mBoardSize.height; i++ ) + for( int j = 0; j < mBoardSize.width; j++ ) + objectPoints.push_back(cv::Point3f((2*j + i % 2)*mSquareSize, i*mSquareSize, 0)); + mCalibData->imagePoints.push_back(mCurrentImagePoints); + mCalibData->objectPoints.push_back(objectPoints); + break; + case DoubleAcirclesGrid: + { + float gridCenterX = (2*((float)mBoardSize.width - 1) + 1)*mSquareSize + mTemplDist / 2; + float gridCenterY = (mBoardSize.height - 1)*mSquareSize / 2; + objectPoints.reserve(2*mBoardSize.height*mBoardSize.width); + + //white part + for( int i = 0; i < mBoardSize.height; i++ ) + for( int j = 0; j < mBoardSize.width; j++ ) + objectPoints.push_back( + cv::Point3f(-float((2*j + i % 2)*mSquareSize + mTemplDist + + (2*(mBoardSize.width - 1) + 1)*mSquareSize - gridCenterX), + -float(i*mSquareSize) - gridCenterY, + 0)); + //black part + for( int i = 0; i < mBoardSize.height; i++ ) + for( int j = 0; j < mBoardSize.width; j++ ) + objectPoints.push_back(cv::Point3f(-float((2*j + i % 2)*mSquareSize - gridCenterX), + -float(i*mSquareSize) - gridCenterY, 0)); + + mCalibData->imagePoints.push_back(mCurrentImagePoints); + mCalibData->objectPoints.push_back(objectPoints); + } + break; + } +} + +void CalibProcessor::showCaptureMessage(const cv::Mat& frame, const std::string &message) +{ + cv::Point textOrigin(100, 100); + double textSize = VIDEO_TEXT_SIZE * frame.cols / (double) IMAGE_MAX_WIDTH; + cv::bitwise_not(frame, frame); + cv::putText(frame, message, textOrigin, 1, textSize, cv::Scalar(0,0,255), 2, cv::LINE_AA); + cv::imshow(mainWindowName, frame); + cv::waitKey(300); +} + +bool CalibProcessor::checkLastFrame() +{ + bool isFrameBad = false; + cv::Mat tmpCamMatrix; + const double badAngleThresh = 40; + + if(!mCalibData->cameraMatrix.total()) { + tmpCamMatrix = cv::Mat::eye(3, 3, CV_64F); + tmpCamMatrix.at(0,0) = 20000; + tmpCamMatrix.at(1,1) = 20000; + tmpCamMatrix.at(0,2) = mCalibData->imageSize.height/2; + tmpCamMatrix.at(1,2) = mCalibData->imageSize.width/2; + } + else + mCalibData->cameraMatrix.copyTo(tmpCamMatrix); + + if(mBoardType != chAruco) { + cv::Mat r, t, angles; + cv::solvePnP(mCalibData->objectPoints.back(), mCurrentImagePoints, tmpCamMatrix, mCalibData->distCoeffs, r, t); + RodriguesToEuler(r, angles, CALIB_DEGREES); + + if(fabs(angles.at(0)) > badAngleThresh || fabs(angles.at(1)) > badAngleThresh) { + mCalibData->objectPoints.pop_back(); + mCalibData->imagePoints.pop_back(); + isFrameBad = true; + } + } + else { +#ifdef HAVE_OPENCV_ARUCO + cv::Mat r, t, angles; + std::vector allObjPoints; + allObjPoints.reserve(mCurrentCharucoIds.total()); + for(size_t i = 0; i < mCurrentCharucoIds.total(); i++) { + int pointID = mCurrentCharucoIds.at((int)i); + CV_Assert(pointID >= 0 && pointID < (int)mCharucoBoard->chessboardCorners.size()); + allObjPoints.push_back(mCharucoBoard->chessboardCorners[pointID]); + } + + cv::solvePnP(allObjPoints, mCurrentCharucoCorners, tmpCamMatrix, mCalibData->distCoeffs, r, t); + RodriguesToEuler(r, angles, CALIB_DEGREES); + + if(180.0 - fabs(angles.at(0)) > badAngleThresh || fabs(angles.at(1)) > badAngleThresh) { + isFrameBad = true; + mCalibData->allCharucoCorners.pop_back(); + mCalibData->allCharucoIds.pop_back(); + } +#endif + } + return isFrameBad; +} + +CalibProcessor::CalibProcessor(cv::Ptr data, captureParameters &capParams) : + mCalibData(data), mBoardType(capParams.board), mBoardSize(capParams.boardSize) +{ + mCapuredFrames = 0; + mNeededFramesNum = capParams.calibrationStep; + mDelayBetweenCaptures = static_cast(capParams.captureDelay * capParams.fps); + mMaxTemplateOffset = std::sqrt(static_cast(mCalibData->imageSize.height * mCalibData->imageSize.height) + + static_cast(mCalibData->imageSize.width * mCalibData->imageSize.width)) / 20.0; + mSquareSize = capParams.squareSize; + mTemplDist = capParams.templDst; + + switch(mBoardType) + { + case chAruco: +#ifdef HAVE_OPENCV_ARUCO + mArucoDictionary = cv::aruco::getPredefinedDictionary( + cv::aruco::PREDEFINED_DICTIONARY_NAME(capParams.charucoDictName)); + mCharucoBoard = cv::aruco::CharucoBoard::create(mBoardSize.width, mBoardSize.height, capParams.charucoSquareLenght, + capParams.charucoMarkerSize, mArucoDictionary); +#endif + break; + case AcirclesGrid: + mBlobDetectorPtr = cv::SimpleBlobDetector::create(); + break; + case DoubleAcirclesGrid: + mBlobDetectorPtr = cv::SimpleBlobDetector::create(getDetectorParams()); + break; + case Chessboard: + break; + } +} + +cv::Mat CalibProcessor::processFrame(const cv::Mat &frame) +{ + cv::Mat frameCopy; + frame.copyTo(frameCopy); + bool isTemplateFound = false; + mCurrentImagePoints.clear(); + + switch(mBoardType) + { + case Chessboard: + isTemplateFound = detectAndParseChessboard(frameCopy); + break; + case chAruco: + isTemplateFound = detectAndParseChAruco(frameCopy); + break; + case AcirclesGrid: + isTemplateFound = detectAndParseACircles(frameCopy); + break; + case DoubleAcirclesGrid: + isTemplateFound = detectAndParseDualACircles(frameCopy); + break; + } + + if(mTemplateLocations.size() > mDelayBetweenCaptures) + mTemplateLocations.pop_back(); + if(mTemplateLocations.size() == mDelayBetweenCaptures && isTemplateFound) { + if(cv::norm(mTemplateLocations.front() - mTemplateLocations.back()) < mMaxTemplateOffset) { + saveFrameData(); + bool isFrameBad = checkLastFrame(); + if (!isFrameBad) { + std::string displayMessage = cv::format("Frame # %zu captured", std::max(mCalibData->imagePoints.size(), + mCalibData->allCharucoCorners.size())); + if(!showOverlayMessage(displayMessage)) + showCaptureMessage(frame, displayMessage); + mCapuredFrames++; + } + else { + std::string displayMessage = "Frame rejected"; + if(!showOverlayMessage(displayMessage)) + showCaptureMessage(frame, displayMessage); + } + mTemplateLocations.clear(); + mTemplateLocations.reserve(mDelayBetweenCaptures); + } + } + + return frameCopy; +} + +bool CalibProcessor::isProcessed() const +{ + if(mCapuredFrames < mNeededFramesNum) + return false; + else + return true; +} + +void CalibProcessor::resetState() +{ + mCapuredFrames = 0; + mTemplateLocations.clear(); +} + +CalibProcessor::~CalibProcessor() +{ + +} + +//////////////////////////////////////////// + +void ShowProcessor::drawBoard(cv::Mat &img, cv::InputArray points) +{ + cv::Mat tmpView = cv::Mat::zeros(img.rows, img.cols, CV_8UC3); + std::vector templateHull; + std::vector poly; + cv::convexHull(points, templateHull); + poly.resize(templateHull.size()); + for(size_t i=0; i >::iterator it = mCalibdata->imagePoints.begin(); it != mCalibdata->imagePoints.end(); ++it) + for(std::vector::iterator pointIt = (*it).begin(); pointIt != (*it).end(); ++pointIt) + cv::circle(frame, *pointIt, POINT_SIZE, cv::Scalar(0, 255, 0), 1, cv::LINE_AA); + else + for(std::vector::iterator it = mCalibdata->allCharucoCorners.begin(); it != mCalibdata->allCharucoCorners.end(); ++it) + for(int i = 0; i < (*it).size[0]; i++) + cv::circle(frame, cv::Point((int)(*it).at(i, 0), (int)(*it).at(i, 1)), + POINT_SIZE, cv::Scalar(0, 255, 0), 1, cv::LINE_AA); +} + +ShowProcessor::ShowProcessor(cv::Ptr data, cv::Ptr controller, TemplateType board) : + mCalibdata(data), mController(controller), mBoardType(board) +{ + mNeedUndistort = true; + mVisMode = Grid; + mGridViewScale = 0.5; + mTextSize = VIDEO_TEXT_SIZE; +} + +cv::Mat ShowProcessor::processFrame(const cv::Mat &frame) +{ + if (!mCalibdata->cameraMatrix.empty() && !mCalibdata->distCoeffs.empty()) + { + mTextSize = VIDEO_TEXT_SIZE * (double) frame.cols / IMAGE_MAX_WIDTH; + cv::Scalar textColor = cv::Scalar(0,0,255); + cv::Mat frameCopy; + + if (mNeedUndistort && mController->getFramesNumberState()) { + if(mVisMode == Grid) + drawGridPoints(frame); + cv::remap(frame, frameCopy, mCalibdata->undistMap1, mCalibdata->undistMap2, cv::INTER_LINEAR); + int baseLine = 100; + cv::Size textSize = cv::getTextSize("Undistorted view", 1, mTextSize, 2, &baseLine); + cv::Point textOrigin(baseLine, frame.rows - (int)(2.5*textSize.height)); + cv::putText(frameCopy, "Undistorted view", textOrigin, 1, mTextSize, textColor, 2, cv::LINE_AA); + } + else { + frame.copyTo(frameCopy); + if(mVisMode == Grid) + drawGridPoints(frameCopy); + } + std::string displayMessage; + if(mCalibdata->stdDeviations.at(0) == 0) + displayMessage = cv::format("F = %d RMS = %.3f", (int)mCalibdata->cameraMatrix.at(0,0), mCalibdata->totalAvgErr); + else + displayMessage = cv::format("Fx = %d Fy = %d RMS = %.3f", (int)mCalibdata->cameraMatrix.at(0,0), + (int)mCalibdata->cameraMatrix.at(1,1), mCalibdata->totalAvgErr); + if(mController->getRMSState() && mController->getFramesNumberState()) + displayMessage.append(" OK"); + + int baseLine = 100; + cv::Size textSize = cv::getTextSize(displayMessage, 1, mTextSize - 1, 2, &baseLine); + cv::Point textOrigin = cv::Point(baseLine, 2*textSize.height); + cv::putText(frameCopy, displayMessage, textOrigin, 1, mTextSize - 1, textColor, 2, cv::LINE_AA); + + if(mCalibdata->stdDeviations.at(0) == 0) + displayMessage = cv::format("DF = %.2f", mCalibdata->stdDeviations.at(1)*sigmaMult); + else + displayMessage = cv::format("DFx = %.2f DFy = %.2f", mCalibdata->stdDeviations.at(0)*sigmaMult, + mCalibdata->stdDeviations.at(1)*sigmaMult); + if(mController->getConfidenceIntrervalsState() && mController->getFramesNumberState()) + displayMessage.append(" OK"); + cv::putText(frameCopy, displayMessage, cv::Point(baseLine, 4*textSize.height), 1, mTextSize - 1, textColor, 2, cv::LINE_AA); + + if(mController->getCommonCalibrationState()) { + displayMessage = cv::format("Calibration is done"); + cv::putText(frameCopy, displayMessage, cv::Point(baseLine, 6*textSize.height), 1, mTextSize - 1, textColor, 2, cv::LINE_AA); + } + int calibFlags = mController->getNewFlags(); + displayMessage = ""; + if(!(calibFlags & cv::CALIB_FIX_ASPECT_RATIO)) + displayMessage.append(cv::format("AR=%.3f ", mCalibdata->cameraMatrix.at(0,0)/mCalibdata->cameraMatrix.at(1,1))); + if(calibFlags & cv::CALIB_ZERO_TANGENT_DIST) + displayMessage.append("TD=0 "); + displayMessage.append(cv::format("K1=%.2f K2=%.2f K3=%.2f", mCalibdata->distCoeffs.at(0), mCalibdata->distCoeffs.at(1), + mCalibdata->distCoeffs.at(4))); + cv::putText(frameCopy, displayMessage, cv::Point(baseLine, frameCopy.rows - (int)(1.5*textSize.height)), + 1, mTextSize - 1, textColor, 2, cv::LINE_AA); + return frameCopy; + } + + return frame; +} + +bool ShowProcessor::isProcessed() const +{ + return false; +} + +void ShowProcessor::resetState() +{ + +} + +void ShowProcessor::setVisualizationMode(visualisationMode mode) +{ + mVisMode = mode; +} + +void ShowProcessor::switchVisualizationMode() +{ + if(mVisMode == Grid) { + mVisMode = Window; + updateBoardsView(); + } + else { + mVisMode = Grid; + cv::destroyWindow(gridWindowName); + } +} + +void ShowProcessor::clearBoardsView() +{ + cv::imshow(gridWindowName, cv::Mat()); +} + +void ShowProcessor::updateBoardsView() +{ + if(mVisMode == Window) { + cv::Size originSize = mCalibdata->imageSize; + cv::Mat altGridView = cv::Mat::zeros((int)(originSize.height*mGridViewScale), (int)(originSize.width*mGridViewScale), CV_8UC3); + if(mBoardType != chAruco) + for(std::vector >::iterator it = mCalibdata->imagePoints.begin(); it != mCalibdata->imagePoints.end(); ++it) + if(mBoardType != DoubleAcirclesGrid) + drawBoard(altGridView, *it); + else { + size_t pointsNum = (*it).size()/2; + std::vector points(pointsNum); + std::copy((*it).begin(), (*it).begin() + pointsNum, points.begin()); + drawBoard(altGridView, points); + std::copy((*it).begin() + pointsNum, (*it).begin() + 2*pointsNum, points.begin()); + drawBoard(altGridView, points); + } + else + for(std::vector::iterator it = mCalibdata->allCharucoCorners.begin(); it != mCalibdata->allCharucoCorners.end(); ++it) + drawBoard(altGridView, *it); + cv::imshow(gridWindowName, altGridView); + } +} + +void ShowProcessor::switchUndistort() +{ + mNeedUndistort = !mNeedUndistort; +} + +void ShowProcessor::setUndistort(bool isEnabled) +{ + mNeedUndistort = isEnabled; +} + +ShowProcessor::~ShowProcessor() +{ + +} diff --git a/opencv-apps/interactive-calibration/frameProcessor.hpp b/opencv-apps/interactive-calibration/frameProcessor.hpp new file mode 100644 index 0000000..6fd788d --- /dev/null +++ b/opencv-apps/interactive-calibration/frameProcessor.hpp @@ -0,0 +1,104 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef FRAME_PROCESSOR_HPP +#define FRAME_PROCESSOR_HPP + +#include +#include +#ifdef HAVE_OPENCV_ARUCO +#include +#endif + +#include "calibCommon.hpp" +#include "calibController.hpp" + +namespace calib +{ +class FrameProcessor +{ +protected: + +public: + virtual ~FrameProcessor(); + virtual cv::Mat processFrame(const cv::Mat& frame) = 0; + virtual bool isProcessed() const = 0; + virtual void resetState() = 0; +}; + +class CalibProcessor : public FrameProcessor +{ +protected: + cv::Ptr mCalibData; + TemplateType mBoardType; + cv::Size mBoardSize; + std::vector mTemplateLocations; + std::vector mCurrentImagePoints; + cv::Mat mCurrentCharucoCorners; + cv::Mat mCurrentCharucoIds; + + cv::Ptr mBlobDetectorPtr; +#ifdef HAVE_OPENCV_ARUCO + cv::Ptr mArucoDictionary; + cv::Ptr mCharucoBoard; +#endif + + int mNeededFramesNum; + unsigned mDelayBetweenCaptures; + int mCapuredFrames; + double mMaxTemplateOffset; + float mSquareSize; + float mTemplDist; + + bool detectAndParseChessboard(const cv::Mat& frame); + bool detectAndParseChAruco(const cv::Mat& frame); + bool detectAndParseACircles(const cv::Mat& frame); + bool detectAndParseDualACircles(const cv::Mat& frame); + void saveFrameData(); + void showCaptureMessage(const cv::Mat &frame, const std::string& message); + bool checkLastFrame(); + +public: + CalibProcessor(cv::Ptr data, captureParameters& capParams); + virtual cv::Mat processFrame(const cv::Mat& frame) CV_OVERRIDE; + virtual bool isProcessed() const CV_OVERRIDE; + virtual void resetState() CV_OVERRIDE; + ~CalibProcessor() CV_OVERRIDE; +}; + +enum visualisationMode {Grid, Window}; + +class ShowProcessor : public FrameProcessor +{ +protected: + cv::Ptr mCalibdata; + cv::Ptr mController; + TemplateType mBoardType; + visualisationMode mVisMode; + bool mNeedUndistort; + double mGridViewScale; + double mTextSize; + + void drawBoard(cv::Mat& img, cv::InputArray points); + void drawGridPoints(const cv::Mat& frame); +public: + ShowProcessor(cv::Ptr data, cv::Ptr controller, TemplateType board); + virtual cv::Mat processFrame(const cv::Mat& frame) CV_OVERRIDE; + virtual bool isProcessed() const CV_OVERRIDE; + virtual void resetState() CV_OVERRIDE; + + void setVisualizationMode(visualisationMode mode); + void switchVisualizationMode(); + void clearBoardsView(); + void updateBoardsView(); + + void switchUndistort(); + void setUndistort(bool isEnabled); + ~ShowProcessor() CV_OVERRIDE; +}; + +} + + +#endif diff --git a/opencv-apps/interactive-calibration/main.cpp b/opencv-apps/interactive-calibration/main.cpp new file mode 100644 index 0000000..f3d1e5d --- /dev/null +++ b/opencv-apps/interactive-calibration/main.cpp @@ -0,0 +1,225 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include +#include +#include +#include + +#ifdef HAVE_OPENCV_ARUCO +#include +#endif + +#include +#include +#include +#include +#include + +#include "calibCommon.hpp" +#include "calibPipeline.hpp" +#include "frameProcessor.hpp" +#include "calibController.hpp" +#include "parametersController.hpp" +#include "rotationConverters.hpp" + +using namespace calib; + +const std::string keys = + "{v | | Input from video file }" + "{ci | 0 | Default camera id }" + "{flip | false | Vertical flip of input frames }" + "{t | circles | Template for calibration (circles, chessboard, dualCircles, charuco) }" + "{sz | 16.3 | Distance between two nearest centers of circles or squares on calibration board}" + "{dst | 295 | Distance between white and black parts of daulCircles template}" + "{w | | Width of template (in corners or circles)}" + "{h | | Height of template (in corners or circles)}" + "{of | cameraParameters.xml | Output file name}" + "{ft | true | Auto tuning of calibration flags}" + "{vis | grid | Captured boards visualisation (grid, window)}" + "{d | 0.8 | Min delay between captures}" + "{pf | defaultConfig.xml| Advanced application parameters}" + "{help | | Print help}"; + +bool calib::showOverlayMessage(const std::string& message) +{ +#ifdef HAVE_QT + cv::displayOverlay(mainWindowName, message, OVERLAY_DELAY); + return true; +#else + std::cout << message << std::endl; + return false; +#endif +} + +static void deleteButton(int, void* data) +{ + (static_cast*>(data))->get()->deleteLastFrame(); + calib::showOverlayMessage("Last frame deleted"); +} + +static void deleteAllButton(int, void* data) +{ + (static_cast*>(data))->get()->deleteAllData(); + calib::showOverlayMessage("All frames deleted"); +} + +static void saveCurrentParamsButton(int, void* data) +{ + if((static_cast*>(data))->get()->saveCurrentCameraParameters()) + calib::showOverlayMessage("Calibration parameters saved"); +} + +#ifdef HAVE_QT +static void switchVisualizationModeButton(int, void* data) +{ + ShowProcessor* processor = static_cast(((cv::Ptr*)data)->get()); + processor->switchVisualizationMode(); +} + +static void undistortButton(int state, void* data) +{ + ShowProcessor* processor = static_cast(((cv::Ptr*)data)->get()); + processor->setUndistort(static_cast(state)); + calib::showOverlayMessage(std::string("Undistort is ") + + (static_cast(state) ? std::string("on") : std::string("off"))); +} +#endif //HAVE_QT + +int main(int argc, char** argv) +{ + cv::CommandLineParser parser(argc, argv, keys); + if(parser.has("help")) { + parser.printMessage(); + return 0; + } + std::cout << consoleHelp << std::endl; + parametersController paramsController; + + if(!paramsController.loadFromParser(parser)) + return 0; + + captureParameters capParams = paramsController.getCaptureParameters(); + internalParameters intParams = paramsController.getInternalParameters(); +#ifndef HAVE_OPENCV_ARUCO + if(capParams.board == chAruco) + CV_Error(cv::Error::StsNotImplemented, "Aruco module is disabled in current build configuration." + " Consider usage of another calibration pattern\n"); +#endif + + cv::TermCriteria solverTermCrit = cv::TermCriteria(cv::TermCriteria::COUNT+cv::TermCriteria::EPS, + intParams.solverMaxIters, intParams.solverEps); + cv::Ptr globalData(new calibrationData); + if(!parser.has("v")) globalData->imageSize = capParams.cameraResolution; + + int calibrationFlags = 0; + if(intParams.fastSolving) calibrationFlags |= cv::CALIB_USE_QR; + cv::Ptr controller(new calibController(globalData, calibrationFlags, + parser.get("ft"), capParams.minFramesNum)); + cv::Ptr dataController(new calibDataController(globalData, capParams.maxFramesNum, + intParams.filterAlpha)); + dataController->setParametersFileName(parser.get("of")); + + cv::Ptr capProcessor, showProcessor; + capProcessor = cv::Ptr(new CalibProcessor(globalData, capParams)); + showProcessor = cv::Ptr(new ShowProcessor(globalData, controller, capParams.board)); + + if(parser.get("vis").find("window") == 0) { + static_cast(showProcessor.get())->setVisualizationMode(Window); + cv::namedWindow(gridWindowName); + cv::moveWindow(gridWindowName, 1280, 500); + } + + cv::Ptr pipeline(new CalibPipeline(capParams)); + std::vector > processors; + processors.push_back(capProcessor); + processors.push_back(showProcessor); + + cv::namedWindow(mainWindowName); + cv::moveWindow(mainWindowName, 10, 10); +#ifdef HAVE_QT + cv::createButton("Delete last frame", deleteButton, &dataController, + cv::QT_PUSH_BUTTON | cv::QT_NEW_BUTTONBAR); + cv::createButton("Delete all frames", deleteAllButton, &dataController, + cv::QT_PUSH_BUTTON | cv::QT_NEW_BUTTONBAR); + cv::createButton("Undistort", undistortButton, &showProcessor, + cv::QT_CHECKBOX | cv::QT_NEW_BUTTONBAR, false); + cv::createButton("Save current parameters", saveCurrentParamsButton, &dataController, + cv::QT_PUSH_BUTTON | cv::QT_NEW_BUTTONBAR); + cv::createButton("Switch visualisation mode", switchVisualizationModeButton, &showProcessor, + cv::QT_PUSH_BUTTON | cv::QT_NEW_BUTTONBAR); +#endif //HAVE_QT + try { + bool pipelineFinished = false; + while(!pipelineFinished) + { + PipelineExitStatus exitStatus = pipeline->start(processors); + if (exitStatus == Finished) { + if(controller->getCommonCalibrationState()) + saveCurrentParamsButton(0, &dataController); + pipelineFinished = true; + continue; + } + else if (exitStatus == Calibrate) { + + dataController->rememberCurrentParameters(); + globalData->imageSize = pipeline->getImageSize(); + calibrationFlags = controller->getNewFlags(); + + if(capParams.board != chAruco) { + globalData->totalAvgErr = + cv::calibrateCamera(globalData->objectPoints, globalData->imagePoints, + globalData->imageSize, globalData->cameraMatrix, + globalData->distCoeffs, cv::noArray(), cv::noArray(), + globalData->stdDeviations, cv::noArray(), globalData->perViewErrors, + calibrationFlags, solverTermCrit); + } + else { +#ifdef HAVE_OPENCV_ARUCO + cv::Ptr dictionary = + cv::aruco::getPredefinedDictionary(cv::aruco::PREDEFINED_DICTIONARY_NAME(capParams.charucoDictName)); + cv::Ptr charucoboard = + cv::aruco::CharucoBoard::create(capParams.boardSize.width, capParams.boardSize.height, + capParams.charucoSquareLenght, capParams.charucoMarkerSize, dictionary); + globalData->totalAvgErr = + cv::aruco::calibrateCameraCharuco(globalData->allCharucoCorners, globalData->allCharucoIds, + charucoboard, globalData->imageSize, + globalData->cameraMatrix, globalData->distCoeffs, + cv::noArray(), cv::noArray(), globalData->stdDeviations, cv::noArray(), + globalData->perViewErrors, calibrationFlags, solverTermCrit); +#endif + } + dataController->updateUndistortMap(); + dataController->printParametersToConsole(std::cout); + controller->updateState(); + for(int j = 0; j < capParams.calibrationStep; j++) + dataController->filterFrames(); + static_cast(showProcessor.get())->updateBoardsView(); + } + else if (exitStatus == DeleteLastFrame) { + deleteButton(0, &dataController); + static_cast(showProcessor.get())->updateBoardsView(); + } + else if (exitStatus == DeleteAllFrames) { + deleteAllButton(0, &dataController); + static_cast(showProcessor.get())->updateBoardsView(); + } + else if (exitStatus == SaveCurrentData) { + saveCurrentParamsButton(0, &dataController); + } + else if (exitStatus == SwitchUndistort) + static_cast(showProcessor.get())->switchUndistort(); + else if (exitStatus == SwitchVisualisation) + static_cast(showProcessor.get())->switchVisualizationMode(); + + for (std::vector >::iterator it = processors.begin(); it != processors.end(); ++it) + (*it)->resetState(); + } + } + catch (const std::runtime_error& exp) { + std::cout << exp.what() << std::endl; + } + + return 0; +} diff --git a/opencv-apps/interactive-calibration/parametersController.cpp b/opencv-apps/interactive-calibration/parametersController.cpp new file mode 100644 index 0000000..8b44ba0 --- /dev/null +++ b/opencv-apps/interactive-calibration/parametersController.cpp @@ -0,0 +1,143 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "parametersController.hpp" + +#include + +template +static bool readFromNode(cv::FileNode node, T& value) +{ + if(!node.isNone()) { + node >> value; + return true; + } + else + return false; +} + +static bool checkAssertion(bool value, const std::string& msg) +{ + if(!value) + std::cerr << "Error: " << msg << std::endl; + + return value; +} + +bool calib::parametersController::loadFromFile(const std::string &inputFileName) +{ + cv::FileStorage reader; + reader.open(inputFileName, cv::FileStorage::READ); + + if(!reader.isOpened()) { + std::cerr << "Warning: Unable to open " << inputFileName << + " Applicatioin stated with default advanced parameters" << std::endl; + return true; + } + + readFromNode(reader["charuco_dict"], mCapParams.charucoDictName); + readFromNode(reader["charuco_square_lenght"], mCapParams.charucoSquareLenght); + readFromNode(reader["charuco_marker_size"], mCapParams.charucoMarkerSize); + readFromNode(reader["camera_resolution"], mCapParams.cameraResolution); + readFromNode(reader["calibration_step"], mCapParams.calibrationStep); + readFromNode(reader["max_frames_num"], mCapParams.maxFramesNum); + readFromNode(reader["min_frames_num"], mCapParams.minFramesNum); + readFromNode(reader["solver_eps"], mInternalParameters.solverEps); + readFromNode(reader["solver_max_iters"], mInternalParameters.solverMaxIters); + readFromNode(reader["fast_solver"], mInternalParameters.fastSolving); + readFromNode(reader["frame_filter_conv_param"], mInternalParameters.filterAlpha); + + bool retValue = + checkAssertion(mCapParams.charucoDictName >= 0, "Dict name must be >= 0") && + checkAssertion(mCapParams.charucoMarkerSize > 0, "Marker size must be positive") && + checkAssertion(mCapParams.charucoSquareLenght > 0, "Square size must be positive") && + checkAssertion(mCapParams.minFramesNum > 1, "Minimal number of frames for calibration < 1") && + checkAssertion(mCapParams.calibrationStep > 0, "Calibration step must be positive") && + checkAssertion(mCapParams.maxFramesNum > mCapParams.minFramesNum, "maxFramesNum < minFramesNum") && + checkAssertion(mInternalParameters.solverEps > 0, "Solver precision must be positive") && + checkAssertion(mInternalParameters.solverMaxIters > 0, "Max solver iterations number must be positive") && + checkAssertion(mInternalParameters.filterAlpha >=0 && mInternalParameters.filterAlpha <=1 , + "Frame filter convolution parameter must be in [0,1] interval") && + checkAssertion(mCapParams.cameraResolution.width > 0 && mCapParams.cameraResolution.height > 0, + "Wrong camera resolution values"); + + reader.release(); + return retValue; +} + +calib::parametersController::parametersController() +{ +} + +calib::captureParameters calib::parametersController::getCaptureParameters() const +{ + return mCapParams; +} + +calib::internalParameters calib::parametersController::getInternalParameters() const +{ + return mInternalParameters; +} + +bool calib::parametersController::loadFromParser(cv::CommandLineParser &parser) +{ + mCapParams.flipVertical = parser.get("flip"); + mCapParams.captureDelay = parser.get("d"); + mCapParams.squareSize = parser.get("sz"); + mCapParams.templDst = parser.get("dst"); + + if(!checkAssertion(mCapParams.squareSize > 0, "Distance between corners or circles must be positive")) + return false; + if(!checkAssertion(mCapParams.templDst > 0, "Distance between parts of dual template must be positive")) + return false; + + if (parser.has("v")) { + mCapParams.source = File; + mCapParams.videoFileName = parser.get("v"); + } + else { + mCapParams.source = Camera; + mCapParams.camID = parser.get("ci"); + } + + std::string templateType = parser.get("t"); + + if(templateType.find("circles", 0) == 0) { + mCapParams.board = AcirclesGrid; + mCapParams.boardSize = cv::Size(4, 11); + } + else if(templateType.find("chessboard", 0) == 0) { + mCapParams.board = Chessboard; + mCapParams.boardSize = cv::Size(7, 7); + } + else if(templateType.find("dualcircles", 0) == 0) { + mCapParams.board = DoubleAcirclesGrid; + mCapParams.boardSize = cv::Size(4, 11); + } + else if(templateType.find("charuco", 0) == 0) { + mCapParams.board = chAruco; + mCapParams.boardSize = cv::Size(6, 8); + mCapParams.charucoDictName = 0; + mCapParams.charucoSquareLenght = 200; + mCapParams.charucoMarkerSize = 100; + } + else { + std::cerr << "Wrong template name\n"; + return false; + } + + if(parser.has("w") && parser.has("h")) { + mCapParams.boardSize = cv::Size(parser.get("w"), parser.get("h")); + if(!checkAssertion(mCapParams.boardSize.width > 0 || mCapParams.boardSize.height > 0, + "Board size must be positive")) + return false; + } + + if(!checkAssertion(parser.get("of").find(".xml") > 0, + "Wrong output file name: correct format is [name].xml")) + return false; + + loadFromFile(parser.get("pf")); + return true; +} diff --git a/opencv-apps/interactive-calibration/parametersController.hpp b/opencv-apps/interactive-calibration/parametersController.hpp new file mode 100644 index 0000000..616f4e7 --- /dev/null +++ b/opencv-apps/interactive-calibration/parametersController.hpp @@ -0,0 +1,35 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef PARAMETERS_CONTROLLER_HPP +#define PARAMETERS_CONTROLLER_HPP + +#include + +#include + +#include "calibCommon.hpp" + +namespace calib { + +class parametersController +{ +protected: + captureParameters mCapParams; + internalParameters mInternalParameters; + + bool loadFromFile(const std::string& inputFileName); +public: + parametersController(); + parametersController(cv::Ptr params); + + captureParameters getCaptureParameters() const; + internalParameters getInternalParameters() const; + + bool loadFromParser(cv::CommandLineParser& parser); +}; + +} + +#endif diff --git a/opencv-apps/interactive-calibration/rotationConverters.cpp b/opencv-apps/interactive-calibration/rotationConverters.cpp new file mode 100644 index 0000000..421d15a --- /dev/null +++ b/opencv-apps/interactive-calibration/rotationConverters.cpp @@ -0,0 +1,126 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "rotationConverters.hpp" + +#include + +#include +#include + +#define CALIB_PI 3.14159265358979323846 +#define CALIB_PI_2 1.57079632679489661923 + +void calib::Euler(const cv::Mat& src, cv::Mat& dst, int argType) +{ + if((src.rows == 3) && (src.cols == 3)) + { + //convert rotation matrix to 3 angles (pitch, yaw, roll) + dst = cv::Mat(3, 1, CV_64F); + double pitch, yaw, roll; + + if(src.at(0,2) < -0.998) + { + pitch = -atan2(src.at(1,0), src.at(1,1)); + yaw = -CALIB_PI_2; + roll = 0.; + } + else if(src.at(0,2) > 0.998) + { + pitch = atan2(src.at(1,0), src.at(1,1)); + yaw = CALIB_PI_2; + roll = 0.; + } + else + { + pitch = atan2(-src.at(1,2), src.at(2,2)); + yaw = asin(src.at(0,2)); + roll = atan2(-src.at(0,1), src.at(0,0)); + } + + if(argType == CALIB_DEGREES) + { + pitch *= 180./CALIB_PI; + yaw *= 180./CALIB_PI; + roll *= 180./CALIB_PI; + } + else if(argType != CALIB_RADIANS) + CV_Error(cv::Error::StsBadFlag, "Invalid argument type"); + + dst.at(0,0) = pitch; + dst.at(1,0) = yaw; + dst.at(2,0) = roll; + } + else if( (src.cols == 1 && src.rows == 3) || + (src.cols == 3 && src.rows == 1 ) ) + { + //convert vector which contains 3 angles (pitch, yaw, roll) to rotation matrix + double pitch, yaw, roll; + if(src.cols == 1 && src.rows == 3) + { + pitch = src.at(0,0); + yaw = src.at(1,0); + roll = src.at(2,0); + } + else{ + pitch = src.at(0,0); + yaw = src.at(0,1); + roll = src.at(0,2); + } + + if(argType == CALIB_DEGREES) + { + pitch *= CALIB_PI / 180.; + yaw *= CALIB_PI / 180.; + roll *= CALIB_PI / 180.; + } + else if(argType != CALIB_RADIANS) + CV_Error(cv::Error::StsBadFlag, "Invalid argument type"); + + dst = cv::Mat(3, 3, CV_64F); + cv::Mat M(3, 3, CV_64F); + cv::Mat i = cv::Mat::eye(3, 3, CV_64F); + i.copyTo(dst); + i.copyTo(M); + + double* pR = dst.ptr(); + pR[4] = cos(pitch); + pR[7] = sin(pitch); + pR[8] = pR[4]; + pR[5] = -pR[7]; + + double* pM = M.ptr(); + pM[0] = cos(yaw); + pM[2] = sin(yaw); + pM[8] = pM[0]; + pM[6] = -pM[2]; + + dst *= M; + i.copyTo(M); + pM[0] = cos(roll); + pM[3] = sin(roll); + pM[4] = pM[0]; + pM[1] = -pM[3]; + + dst *= M; + } + else + CV_Error(cv::Error::StsBadFlag, "Input matrix must be 1x3, 3x1 or 3x3" ); +} + +void calib::RodriguesToEuler(const cv::Mat& src, cv::Mat& dst, int argType) +{ + CV_Assert((src.cols == 1 && src.rows == 3) || (src.cols == 3 && src.rows == 1)); + cv::Mat R; + cv::Rodrigues(src, R); + Euler(R, dst, argType); +} + +void calib::EulerToRodrigues(const cv::Mat& src, cv::Mat& dst, int argType) +{ + CV_Assert((src.cols == 1 && src.rows == 3) || (src.cols == 3 && src.rows == 1)); + cv::Mat R; + Euler(src, R, argType); + cv::Rodrigues(R, dst); +} diff --git a/opencv-apps/interactive-calibration/rotationConverters.hpp b/opencv-apps/interactive-calibration/rotationConverters.hpp new file mode 100644 index 0000000..fcb5bcc --- /dev/null +++ b/opencv-apps/interactive-calibration/rotationConverters.hpp @@ -0,0 +1,20 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef ROTATION_CONVERTERS_HPP +#define ROTATION_CONVERTERS_HPP + +#include + +namespace calib +{ +#define CALIB_RADIANS 0 +#define CALIB_DEGREES 1 + + void Euler(const cv::Mat& src, cv::Mat& dst, int argType = CALIB_RADIANS); + void RodriguesToEuler(const cv::Mat& src, cv::Mat& dst, int argType = CALIB_RADIANS); + void EulerToRodrigues(const cv::Mat& src, cv::Mat& dst, int argType = CALIB_RADIANS); + +} +#endif diff --git a/opencv-apps/traincascade/CMakeLists.txt b/opencv-apps/traincascade/CMakeLists.txt new file mode 100644 index 0000000..ef80ce8 --- /dev/null +++ b/opencv-apps/traincascade/CMakeLists.txt @@ -0,0 +1,5 @@ +ocv_warnings_disable(CMAKE_CXX_FLAGS -Woverloaded-virtual -Winconsistent-missing-override -Wsuggest-override) +file(GLOB SRCS *.cpp) +ocv_add_application(opencv_traincascade + MODULES opencv_core opencv_imgproc opencv_objdetect opencv_imgcodecs opencv_highgui opencv_calib3d opencv_features2d + SRCS ${SRCS}) diff --git a/opencv-apps/traincascade/HOGfeatures.cpp b/opencv-apps/traincascade/HOGfeatures.cpp new file mode 100644 index 0000000..0707f93 --- /dev/null +++ b/opencv-apps/traincascade/HOGfeatures.cpp @@ -0,0 +1,250 @@ +#include "opencv2/core.hpp" +#include "opencv2/imgproc.hpp" + +#include "HOGfeatures.h" +#include "cascadeclassifier.h" + +using namespace std; +using namespace cv; + +CvHOGFeatureParams::CvHOGFeatureParams() +{ + maxCatCount = 0; + name = HOGF_NAME; + featSize = N_BINS * N_CELLS; +} + +void CvHOGEvaluator::init(const CvFeatureParams *_featureParams, int _maxSampleCount, Size _winSize) +{ + CV_Assert( _maxSampleCount > 0); + int cols = (_winSize.width + 1) * (_winSize.height + 1); + for (int bin = 0; bin < N_BINS; bin++) + { + hist.push_back(Mat(_maxSampleCount, cols, CV_32FC1)); + } + normSum.create( (int)_maxSampleCount, cols, CV_32FC1 ); + CvFeatureEvaluator::init( _featureParams, _maxSampleCount, _winSize ); +} + +void CvHOGEvaluator::setImage(const Mat &img, uchar clsLabel, int idx) +{ + CV_DbgAssert( !hist.empty()); + CvFeatureEvaluator::setImage( img, clsLabel, idx ); + vector integralHist; + for (int bin = 0; bin < N_BINS; bin++) + { + integralHist.push_back( Mat(winSize.height + 1, winSize.width + 1, hist[bin].type(), hist[bin].ptr((int)idx)) ); + } + Mat integralNorm(winSize.height + 1, winSize.width + 1, normSum.type(), normSum.ptr((int)idx)); + integralHistogram(img, integralHist, integralNorm, (int)N_BINS); +} + +//void CvHOGEvaluator::writeFeatures( FileStorage &fs, const Mat& featureMap ) const +//{ +// _writeFeatures( features, fs, featureMap ); +//} + +void CvHOGEvaluator::writeFeatures( FileStorage &fs, const Mat& featureMap ) const +{ + int featIdx; + int componentIdx; + const Mat_& featureMap_ = (const Mat_&)featureMap; + fs << FEATURES << "["; + for ( int fi = 0; fi < featureMap.cols; fi++ ) + if ( featureMap_(0, fi) >= 0 ) + { + fs << "{"; + featIdx = fi / getFeatureSize(); + componentIdx = fi % getFeatureSize(); + features[featIdx].write( fs, componentIdx ); + fs << "}"; + } + fs << "]"; +} + +void CvHOGEvaluator::generateFeatures() +{ + int offset = winSize.width + 1; + Size blockStep; + int x, y, t, w, h; + + for (t = 8; t <= winSize.width/2; t+=8) //t = size of a cell. blocksize = 4*cellSize + { + blockStep = Size(4,4); + w = 2*t; //width of a block + h = 2*t; //height of a block + for (x = 0; x <= winSize.width - w; x += blockStep.width) + { + for (y = 0; y <= winSize.height - h; y += blockStep.height) + { + features.push_back(Feature(offset, x, y, t, t)); + } + } + w = 2*t; + h = 4*t; + for (x = 0; x <= winSize.width - w; x += blockStep.width) + { + for (y = 0; y <= winSize.height - h; y += blockStep.height) + { + features.push_back(Feature(offset, x, y, t, 2*t)); + } + } + w = 4*t; + h = 2*t; + for (x = 0; x <= winSize.width - w; x += blockStep.width) + { + for (y = 0; y <= winSize.height - h; y += blockStep.height) + { + features.push_back(Feature(offset, x, y, 2*t, t)); + } + } + } + + numFeatures = (int)features.size(); +} + +CvHOGEvaluator::Feature::Feature() +{ + for (int i = 0; i < N_CELLS; i++) + { + rect[i] = Rect(0, 0, 0, 0); + } +} + +CvHOGEvaluator::Feature::Feature( int offset, int x, int y, int cellW, int cellH ) +{ + rect[0] = Rect(x, y, cellW, cellH); //cell0 + rect[1] = Rect(x+cellW, y, cellW, cellH); //cell1 + rect[2] = Rect(x, y+cellH, cellW, cellH); //cell2 + rect[3] = Rect(x+cellW, y+cellH, cellW, cellH); //cell3 + + for (int i = 0; i < N_CELLS; i++) + { + CV_SUM_OFFSETS(fastRect[i].p0, fastRect[i].p1, fastRect[i].p2, fastRect[i].p3, rect[i], offset); + } +} + +void CvHOGEvaluator::Feature::write(FileStorage &fs) const +{ + fs << CC_RECTS << "["; + for( int i = 0; i < N_CELLS; i++ ) + { + fs << "[:" << rect[i].x << rect[i].y << rect[i].width << rect[i].height << "]"; + } + fs << "]"; +} + +//cell and bin idx writing +//void CvHOGEvaluator::Feature::write(FileStorage &fs, int varIdx) const +//{ +// int featComponent = varIdx % (N_CELLS * N_BINS); +// int cellIdx = featComponent / N_BINS; +// int binIdx = featComponent % N_BINS; +// +// fs << CC_RECTS << "[:" << rect[cellIdx].x << rect[cellIdx].y << +// rect[cellIdx].width << rect[cellIdx].height << binIdx << "]"; +//} + +//cell[0] and featComponent idx writing. By cell[0] it's possible to recover all block +//All block is necessary for block normalization +void CvHOGEvaluator::Feature::write(FileStorage &fs, int featComponentIdx) const +{ + fs << CC_RECT << "[:" << rect[0].x << rect[0].y << + rect[0].width << rect[0].height << featComponentIdx << "]"; +} + + +void CvHOGEvaluator::integralHistogram(const Mat &img, vector &histogram, Mat &norm, int nbins) const +{ + CV_Assert( img.type() == CV_8U || img.type() == CV_8UC3 ); + int x, y, binIdx; + + Size gradSize(img.size()); + Size histSize(histogram[0].size()); + Mat grad(gradSize, CV_32F); + Mat qangle(gradSize, CV_8U); + + AutoBuffer mapbuf(gradSize.width + gradSize.height + 4); + int* xmap = mapbuf.data() + 1; + int* ymap = xmap + gradSize.width + 2; + + const int borderType = (int)BORDER_REPLICATE; + + for( x = -1; x < gradSize.width + 1; x++ ) + xmap[x] = borderInterpolate(x, gradSize.width, borderType); + for( y = -1; y < gradSize.height + 1; y++ ) + ymap[y] = borderInterpolate(y, gradSize.height, borderType); + + int width = gradSize.width; + AutoBuffer _dbuf(width*4); + float* dbuf = _dbuf.data(); + Mat Dx(1, width, CV_32F, dbuf); + Mat Dy(1, width, CV_32F, dbuf + width); + Mat Mag(1, width, CV_32F, dbuf + width*2); + Mat Angle(1, width, CV_32F, dbuf + width*3); + + float angleScale = (float)(nbins/CV_PI); + + for( y = 0; y < gradSize.height; y++ ) + { + const uchar* currPtr = img.ptr(ymap[y]); + const uchar* prevPtr = img.ptr(ymap[y-1]); + const uchar* nextPtr = img.ptr(ymap[y+1]); + float* gradPtr = grad.ptr(y); + uchar* qanglePtr = qangle.ptr(y); + + for( x = 0; x < width; x++ ) + { + dbuf[x] = (float)(currPtr[xmap[x+1]] - currPtr[xmap[x-1]]); + dbuf[width + x] = (float)(nextPtr[xmap[x]] - prevPtr[xmap[x]]); + } + cartToPolar( Dx, Dy, Mag, Angle, false ); + for( x = 0; x < width; x++ ) + { + float mag = dbuf[x+width*2]; + float angle = dbuf[x+width*3]; + angle = angle*angleScale - 0.5f; + int bidx = cvFloor(angle); + angle -= bidx; + if( bidx < 0 ) + bidx += nbins; + else if( bidx >= nbins ) + bidx -= nbins; + + qanglePtr[x] = (uchar)bidx; + gradPtr[x] = mag; + } + } + integral(grad, norm, grad.depth()); + + float* histBuf; + const float* magBuf; + const uchar* binsBuf; + + int binsStep = (int)( qangle.step / sizeof(uchar) ); + int histStep = (int)( histogram[0].step / sizeof(float) ); + int magStep = (int)( grad.step / sizeof(float) ); + for( binIdx = 0; binIdx < nbins; binIdx++ ) + { + histBuf = histogram[binIdx].ptr(); + magBuf = grad.ptr(); + binsBuf = qangle.ptr(); + + memset( histBuf, 0, histSize.width * sizeof(histBuf[0]) ); + histBuf += histStep + 1; + for( y = 0; y < qangle.rows; y++ ) + { + histBuf[-1] = 0.f; + float strSum = 0.f; + for( x = 0; x < qangle.cols; x++ ) + { + if( binsBuf[x] == binIdx ) + strSum += magBuf[x]; + histBuf[x] = histBuf[-histStep + x] + strSum; + } + histBuf += histStep; + binsBuf += binsStep; + magBuf += magStep; + } + } +} diff --git a/opencv-apps/traincascade/HOGfeatures.h b/opencv-apps/traincascade/HOGfeatures.h new file mode 100644 index 0000000..cdf7587 --- /dev/null +++ b/opencv-apps/traincascade/HOGfeatures.h @@ -0,0 +1,78 @@ +#ifndef _OPENCV_HOGFEATURES_H_ +#define _OPENCV_HOGFEATURES_H_ + +#include "traincascade_features.h" + +//#define TEST_INTHIST_BUILD +//#define TEST_FEAT_CALC + +#define N_BINS 9 +#define N_CELLS 4 + +#define HOGF_NAME "HOGFeatureParams" +struct CvHOGFeatureParams : public CvFeatureParams +{ + CvHOGFeatureParams(); +}; + +class CvHOGEvaluator : public CvFeatureEvaluator +{ +public: + virtual ~CvHOGEvaluator() {} + virtual void init(const CvFeatureParams *_featureParams, + int _maxSampleCount, cv::Size _winSize ); + virtual void setImage(const cv::Mat& img, uchar clsLabel, int idx); + virtual float operator()(int varIdx, int sampleIdx) const; + virtual void writeFeatures( cv::FileStorage &fs, const cv::Mat& featureMap ) const; +protected: + virtual void generateFeatures(); + virtual void integralHistogram(const cv::Mat &img, std::vector &histogram, cv::Mat &norm, int nbins) const; + class Feature + { + public: + Feature(); + Feature( int offset, int x, int y, int cellW, int cellH ); + float calc( const std::vector &_hists, const cv::Mat &_normSum, size_t y, int featComponent ) const; + void write( cv::FileStorage &fs ) const; + void write( cv::FileStorage &fs, int varIdx ) const; + + cv::Rect rect[N_CELLS]; //cells + + struct + { + int p0, p1, p2, p3; + } fastRect[N_CELLS]; + }; + std::vector features; + + cv::Mat normSum; //for nomalization calculation (L1 or L2) + std::vector hist; +}; + +inline float CvHOGEvaluator::operator()(int varIdx, int sampleIdx) const +{ + int featureIdx = varIdx / (N_BINS * N_CELLS); + int componentIdx = varIdx % (N_BINS * N_CELLS); + //return features[featureIdx].calc( hist, sampleIdx, componentIdx); + return features[featureIdx].calc( hist, normSum, sampleIdx, componentIdx); +} + +inline float CvHOGEvaluator::Feature::calc( const std::vector& _hists, const cv::Mat& _normSum, size_t y, int featComponent ) const +{ + float normFactor; + float res; + + int binIdx = featComponent % N_BINS; + int cellIdx = featComponent / N_BINS; + + const float *phist = _hists[binIdx].ptr((int)y); + res = phist[fastRect[cellIdx].p0] - phist[fastRect[cellIdx].p1] - phist[fastRect[cellIdx].p2] + phist[fastRect[cellIdx].p3]; + + const float *pnormSum = _normSum.ptr((int)y); + normFactor = (float)(pnormSum[fastRect[0].p0] - pnormSum[fastRect[1].p1] - pnormSum[fastRect[2].p2] + pnormSum[fastRect[3].p3]); + res = (res > 0.001f) ? ( res / (normFactor + 0.001f) ) : 0.f; //for cutting negative values, which apper due to floating precision + + return res; +} + +#endif // _OPENCV_HOGFEATURES_H_ diff --git a/opencv-apps/traincascade/boost.cpp b/opencv-apps/traincascade/boost.cpp new file mode 100644 index 0000000..ee0486e --- /dev/null +++ b/opencv-apps/traincascade/boost.cpp @@ -0,0 +1,1718 @@ +#include "opencv2/core.hpp" +#include "opencv2/core/utility.hpp" + +using cv::Size; +using cv::Mat; +using cv::Point; +using cv::FileStorage; +using cv::Rect; +using cv::Ptr; +using cv::FileNode; +using cv::Mat_; +using cv::Range; +using cv::FileNodeIterator; +using cv::ParallelLoopBody; + + +using cv::Size; +using cv::Mat; +using cv::Point; +using cv::FileStorage; +using cv::Rect; +using cv::Ptr; +using cv::FileNode; +using cv::Mat_; +using cv::Range; +using cv::FileNodeIterator; +using cv::ParallelLoopBody; + + +#include "boost.h" +#include "cascadeclassifier.h" +#include + +#include "cvconfig.h" + +using namespace std; + +static inline double +logRatio( double val ) +{ + const double eps = 1e-5; + + val = max( val, eps ); + val = min( val, 1. - eps ); + return log( val/(1. - val) ); +} + +template +class LessThanIdx +{ +public: + LessThanIdx( const T* _arr ) : arr(_arr) {} + bool operator()(Idx a, Idx b) const { return arr[a] < arr[b]; } + const T* arr; +}; + +static inline int cvAlign( int size, int align ) +{ + CV_DbgAssert( (align & (align-1)) == 0 && size < INT_MAX ); + return (size + align - 1) & -align; +} + +#define CV_THRESHOLD_EPS (0.00001F) + +static const int MinBlockSize = 1 << 16; +static const int BlockSizeDelta = 1 << 10; + +// TODO remove this code duplication with ml/precomp.hpp + +static int CV_CDECL icvCmpIntegers( const void* a, const void* b ) +{ + return *(const int*)a - *(const int*)b; +} + +static CvMat* cvPreprocessIndexArray( const CvMat* idx_arr, int data_arr_size, bool check_for_duplicates=false ) +{ + CvMat* idx = 0; + + CV_FUNCNAME( "cvPreprocessIndexArray" ); + + __CV_BEGIN__; + + int i, idx_total, idx_selected = 0, step, type, prev = INT_MIN, is_sorted = 1; + uchar* srcb = 0; + int* srci = 0; + int* dsti; + + if( !CV_IS_MAT(idx_arr) ) + CV_ERROR( CV_StsBadArg, "Invalid index array" ); + + if( idx_arr->rows != 1 && idx_arr->cols != 1 ) + CV_ERROR( CV_StsBadSize, "the index array must be 1-dimensional" ); + + idx_total = idx_arr->rows + idx_arr->cols - 1; + srcb = idx_arr->data.ptr; + srci = idx_arr->data.i; + + type = CV_MAT_TYPE(idx_arr->type); + step = CV_IS_MAT_CONT(idx_arr->type) ? 1 : idx_arr->step/CV_ELEM_SIZE(type); + + switch( type ) + { + case CV_8UC1: + case CV_8SC1: + // idx_arr is array of 1's and 0's - + // i.e. it is a mask of the selected components + if( idx_total != data_arr_size ) + CV_ERROR( CV_StsUnmatchedSizes, + "Component mask should contain as many elements as the total number of input variables" ); + + for( i = 0; i < idx_total; i++ ) + idx_selected += srcb[i*step] != 0; + + if( idx_selected == 0 ) + CV_ERROR( CV_StsOutOfRange, "No components/input_variables is selected!" ); + + break; + case CV_32SC1: + // idx_arr is array of integer indices of selected components + if( idx_total > data_arr_size ) + CV_ERROR( CV_StsOutOfRange, + "index array may not contain more elements than the total number of input variables" ); + idx_selected = idx_total; + // check if sorted already + for( i = 0; i < idx_total; i++ ) + { + int val = srci[i*step]; + if( val >= prev ) + { + is_sorted = 0; + break; + } + prev = val; + } + break; + default: + CV_ERROR( CV_StsUnsupportedFormat, "Unsupported index array data type " + "(it should be 8uC1, 8sC1 or 32sC1)" ); + } + + CV_CALL( idx = cvCreateMat( 1, idx_selected, CV_32SC1 )); + dsti = idx->data.i; + + if( type < CV_32SC1 ) + { + for( i = 0; i < idx_total; i++ ) + if( srcb[i*step] ) + *dsti++ = i; + } + else + { + for( i = 0; i < idx_total; i++ ) + dsti[i] = srci[i*step]; + + if( !is_sorted ) + qsort( dsti, idx_total, sizeof(dsti[0]), icvCmpIntegers ); + + if( dsti[0] < 0 || dsti[idx_total-1] >= data_arr_size ) + CV_ERROR( CV_StsOutOfRange, "the index array elements are out of range" ); + + if( check_for_duplicates ) + { + for( i = 1; i < idx_total; i++ ) + if( dsti[i] <= dsti[i-1] ) + CV_ERROR( CV_StsBadArg, "There are duplicated index array elements" ); + } + } + + __CV_END__; + + if( cvGetErrStatus() < 0 ) + cvReleaseMat( &idx ); + + return idx; +} + +//----------------------------- CascadeBoostParams ------------------------------------------------- + +CvCascadeBoostParams::CvCascadeBoostParams() : minHitRate( 0.995F), maxFalseAlarm( 0.5F ) +{ + boost_type = CvBoost::GENTLE; + use_surrogates = use_1se_rule = truncate_pruned_tree = false; +} + +CvCascadeBoostParams::CvCascadeBoostParams( int _boostType, + float _minHitRate, float _maxFalseAlarm, + double _weightTrimRate, int _maxDepth, int _maxWeakCount ) : + CvBoostParams( _boostType, _maxWeakCount, _weightTrimRate, _maxDepth, false, 0 ) +{ + boost_type = CvBoost::GENTLE; + minHitRate = _minHitRate; + maxFalseAlarm = _maxFalseAlarm; + use_surrogates = use_1se_rule = truncate_pruned_tree = false; +} + +void CvCascadeBoostParams::write( FileStorage &fs ) const +{ + string boostTypeStr = boost_type == CvBoost::DISCRETE ? CC_DISCRETE_BOOST : + boost_type == CvBoost::REAL ? CC_REAL_BOOST : + boost_type == CvBoost::LOGIT ? CC_LOGIT_BOOST : + boost_type == CvBoost::GENTLE ? CC_GENTLE_BOOST : string(); + CV_Assert( !boostTypeStr.empty() ); + fs << CC_BOOST_TYPE << boostTypeStr; + fs << CC_MINHITRATE << minHitRate; + fs << CC_MAXFALSEALARM << maxFalseAlarm; + fs << CC_TRIM_RATE << weight_trim_rate; + fs << CC_MAX_DEPTH << max_depth; + fs << CC_WEAK_COUNT << weak_count; +} + +bool CvCascadeBoostParams::read( const FileNode &node ) +{ + string boostTypeStr; + FileNode rnode = node[CC_BOOST_TYPE]; + rnode >> boostTypeStr; + boost_type = !boostTypeStr.compare( CC_DISCRETE_BOOST ) ? CvBoost::DISCRETE : + !boostTypeStr.compare( CC_REAL_BOOST ) ? CvBoost::REAL : + !boostTypeStr.compare( CC_LOGIT_BOOST ) ? CvBoost::LOGIT : + !boostTypeStr.compare( CC_GENTLE_BOOST ) ? CvBoost::GENTLE : -1; + if (boost_type == -1) + CV_Error( CV_StsBadArg, "unsupported Boost type" ); + node[CC_MINHITRATE] >> minHitRate; + node[CC_MAXFALSEALARM] >> maxFalseAlarm; + node[CC_TRIM_RATE] >> weight_trim_rate ; + node[CC_MAX_DEPTH] >> max_depth ; + node[CC_WEAK_COUNT] >> weak_count ; + if ( minHitRate <= 0 || minHitRate > 1 || + maxFalseAlarm <= 0 || maxFalseAlarm > 1 || + weight_trim_rate <= 0 || weight_trim_rate > 1 || + max_depth <= 0 || weak_count <= 0 ) + CV_Error( CV_StsBadArg, "bad parameters range"); + return true; +} + +void CvCascadeBoostParams::printDefaults() const +{ + cout << "--boostParams--" << endl; + cout << " [-bt <{" << CC_DISCRETE_BOOST << ", " + << CC_REAL_BOOST << ", " + << CC_LOGIT_BOOST ", " + << CC_GENTLE_BOOST << "(default)}>]" << endl; + cout << " [-minHitRate = " << minHitRate << ">]" << endl; + cout << " [-maxFalseAlarmRate ]" << endl; + cout << " [-weightTrimRate ]" << endl; + cout << " [-maxDepth ]" << endl; + cout << " [-maxWeakCount ]" << endl; +} + +void CvCascadeBoostParams::printAttrs() const +{ + string boostTypeStr = boost_type == CvBoost::DISCRETE ? CC_DISCRETE_BOOST : + boost_type == CvBoost::REAL ? CC_REAL_BOOST : + boost_type == CvBoost::LOGIT ? CC_LOGIT_BOOST : + boost_type == CvBoost::GENTLE ? CC_GENTLE_BOOST : string(); + CV_Assert( !boostTypeStr.empty() ); + cout << "boostType: " << boostTypeStr << endl; + cout << "minHitRate: " << minHitRate << endl; + cout << "maxFalseAlarmRate: " << maxFalseAlarm << endl; + cout << "weightTrimRate: " << weight_trim_rate << endl; + cout << "maxDepth: " << max_depth << endl; + cout << "maxWeakCount: " << weak_count << endl; +} + +bool CvCascadeBoostParams::scanAttr( const string prmName, const string val) +{ + bool res = true; + + if( !prmName.compare( "-bt" ) ) + { + boost_type = !val.compare( CC_DISCRETE_BOOST ) ? CvBoost::DISCRETE : + !val.compare( CC_REAL_BOOST ) ? CvBoost::REAL : + !val.compare( CC_LOGIT_BOOST ) ? CvBoost::LOGIT : + !val.compare( CC_GENTLE_BOOST ) ? CvBoost::GENTLE : -1; + if (boost_type == -1) + res = false; + } + else if( !prmName.compare( "-minHitRate" ) ) + { + minHitRate = (float) atof( val.c_str() ); + } + else if( !prmName.compare( "-maxFalseAlarmRate" ) ) + { + maxFalseAlarm = (float) atof( val.c_str() ); + } + else if( !prmName.compare( "-weightTrimRate" ) ) + { + weight_trim_rate = (float) atof( val.c_str() ); + } + else if( !prmName.compare( "-maxDepth" ) ) + { + max_depth = atoi( val.c_str() ); + } + else if( !prmName.compare( "-maxWeakCount" ) ) + { + weak_count = atoi( val.c_str() ); + } + else + res = false; + + return res; +} + +CvDTreeNode* CvCascadeBoostTrainData::subsample_data( const CvMat* _subsample_idx ) +{ + CvDTreeNode* root = 0; + CvMat* isubsample_idx = 0; + CvMat* subsample_co = 0; + + bool isMakeRootCopy = true; + + if( !data_root ) + CV_Error( CV_StsError, "No training data has been set" ); + + if( _subsample_idx ) + { + CV_Assert( (isubsample_idx = cvPreprocessIndexArray( _subsample_idx, sample_count )) != 0 ); + + if( isubsample_idx->cols + isubsample_idx->rows - 1 == sample_count ) + { + const int* sidx = isubsample_idx->data.i; + for( int i = 0; i < sample_count; i++ ) + { + if( sidx[i] != i ) + { + isMakeRootCopy = false; + break; + } + } + } + else + isMakeRootCopy = false; + } + + if( isMakeRootCopy ) + { + // make a copy of the root node + CvDTreeNode temp; + int i; + root = new_node( 0, 1, 0, 0 ); + temp = *root; + *root = *data_root; + root->num_valid = temp.num_valid; + if( root->num_valid ) + { + for( i = 0; i < var_count; i++ ) + root->num_valid[i] = data_root->num_valid[i]; + } + root->cv_Tn = temp.cv_Tn; + root->cv_node_risk = temp.cv_node_risk; + root->cv_node_error = temp.cv_node_error; + } + else + { + int* sidx = isubsample_idx->data.i; + // co - array of count/offset pairs (to handle duplicated values in _subsample_idx) + int* co, cur_ofs = 0; + int workVarCount = get_work_var_count(); + int count = isubsample_idx->rows + isubsample_idx->cols - 1; + + root = new_node( 0, count, 1, 0 ); + + CV_Assert( (subsample_co = cvCreateMat( 1, sample_count*2, CV_32SC1 )) != 0); + cvZero( subsample_co ); + co = subsample_co->data.i; + for( int i = 0; i < count; i++ ) + co[sidx[i]*2]++; + for( int i = 0; i < sample_count; i++ ) + { + if( co[i*2] ) + { + co[i*2+1] = cur_ofs; + cur_ofs += co[i*2]; + } + else + co[i*2+1] = -1; + } + + cv::AutoBuffer inn_buf(sample_count*(2*sizeof(int) + sizeof(float))); + // subsample ordered variables + for( int vi = 0; vi < numPrecalcIdx; vi++ ) + { + int ci = get_var_type(vi); + CV_Assert( ci < 0 ); + + int *src_idx_buf = (int*)inn_buf.data(); + float *src_val_buf = (float*)(src_idx_buf + sample_count); + int* sample_indices_buf = (int*)(src_val_buf + sample_count); + const int* src_idx = 0; + const float* src_val = 0; + get_ord_var_data( data_root, vi, src_val_buf, src_idx_buf, &src_val, &src_idx, sample_indices_buf ); + + int j = 0, idx, count_i; + int num_valid = data_root->get_num_valid(vi); + CV_Assert( num_valid == sample_count ); + + if (is_buf_16u) + { + unsigned short* udst_idx = (unsigned short*)(buf->data.s + root->buf_idx*get_length_subbuf() + + (size_t)vi*sample_count + data_root->offset); + for( int i = 0; i < num_valid; i++ ) + { + idx = src_idx[i]; + count_i = co[idx*2]; + if( count_i ) + for( cur_ofs = co[idx*2+1]; count_i > 0; count_i--, j++, cur_ofs++ ) + udst_idx[j] = (unsigned short)cur_ofs; + } + } + else + { + int* idst_idx = buf->data.i + root->buf_idx*get_length_subbuf() + + (size_t)vi*sample_count + root->offset; + for( int i = 0; i < num_valid; i++ ) + { + idx = src_idx[i]; + count_i = co[idx*2]; + if( count_i ) + for( cur_ofs = co[idx*2+1]; count_i > 0; count_i--, j++, cur_ofs++ ) + idst_idx[j] = cur_ofs; + } + } + } + + // subsample cv_lables + const int* src_lbls = get_cv_labels(data_root, (int*)inn_buf.data()); + if (is_buf_16u) + { + unsigned short* udst = (unsigned short*)(buf->data.s + root->buf_idx*get_length_subbuf() + + (size_t)(workVarCount-1)*sample_count + root->offset); + for( int i = 0; i < count; i++ ) + udst[i] = (unsigned short)src_lbls[sidx[i]]; + } + else + { + int* idst = buf->data.i + root->buf_idx*get_length_subbuf() + + (size_t)(workVarCount-1)*sample_count + root->offset; + for( int i = 0; i < count; i++ ) + idst[i] = src_lbls[sidx[i]]; + } + + // subsample sample_indices + const int* sample_idx_src = get_sample_indices(data_root, (int*)inn_buf.data()); + if (is_buf_16u) + { + unsigned short* sample_idx_dst = (unsigned short*)(buf->data.s + root->buf_idx*get_length_subbuf() + + (size_t)workVarCount*sample_count + root->offset); + for( int i = 0; i < count; i++ ) + sample_idx_dst[i] = (unsigned short)sample_idx_src[sidx[i]]; + } + else + { + int* sample_idx_dst = buf->data.i + root->buf_idx*get_length_subbuf() + + (size_t)workVarCount*sample_count + root->offset; + for( int i = 0; i < count; i++ ) + sample_idx_dst[i] = sample_idx_src[sidx[i]]; + } + + for( int vi = 0; vi < var_count; vi++ ) + root->set_num_valid(vi, count); + } + + cvReleaseMat( &isubsample_idx ); + cvReleaseMat( &subsample_co ); + + return root; +} + +//---------------------------- CascadeBoostTrainData ----------------------------- + +CvCascadeBoostTrainData::CvCascadeBoostTrainData( const CvFeatureEvaluator* _featureEvaluator, + const CvDTreeParams& _params ) +{ + is_classifier = true; + var_all = var_count = (int)_featureEvaluator->getNumFeatures(); + + featureEvaluator = _featureEvaluator; + shared = true; + set_params( _params ); + max_c_count = MAX( 2, featureEvaluator->getMaxCatCount() ); + var_type = cvCreateMat( 1, var_count + 2, CV_32SC1 ); + if ( featureEvaluator->getMaxCatCount() > 0 ) + { + numPrecalcIdx = 0; + cat_var_count = var_count; + ord_var_count = 0; + for( int vi = 0; vi < var_count; vi++ ) + { + var_type->data.i[vi] = vi; + } + } + else + { + cat_var_count = 0; + ord_var_count = var_count; + for( int vi = 1; vi <= var_count; vi++ ) + { + var_type->data.i[vi-1] = -vi; + } + } + var_type->data.i[var_count] = cat_var_count; + var_type->data.i[var_count+1] = cat_var_count+1; + + int maxSplitSize = cvAlign(sizeof(CvDTreeSplit) + (MAX(0,max_c_count - 33)/32)*sizeof(int),sizeof(void*)); + int treeBlockSize = MAX((int)sizeof(CvDTreeNode)*8, maxSplitSize); + treeBlockSize = MAX(treeBlockSize + BlockSizeDelta, MinBlockSize); + tree_storage = cvCreateMemStorage( treeBlockSize ); + node_heap = cvCreateSet( 0, sizeof(node_heap[0]), sizeof(CvDTreeNode), tree_storage ); + split_heap = cvCreateSet( 0, sizeof(split_heap[0]), maxSplitSize, tree_storage ); +} + +CvCascadeBoostTrainData::CvCascadeBoostTrainData( const CvFeatureEvaluator* _featureEvaluator, + int _numSamples, + int _precalcValBufSize, int _precalcIdxBufSize, + const CvDTreeParams& _params ) +{ + setData( _featureEvaluator, _numSamples, _precalcValBufSize, _precalcIdxBufSize, _params ); +} + +void CvCascadeBoostTrainData::setData( const CvFeatureEvaluator* _featureEvaluator, + int _numSamples, + int _precalcValBufSize, int _precalcIdxBufSize, + const CvDTreeParams& _params ) +{ + int* idst = 0; + unsigned short* udst = 0; + + uint64 effective_buf_size = 0; + int effective_buf_height = 0, effective_buf_width = 0; + + + clear(); + shared = true; + have_labels = true; + have_priors = false; + is_classifier = true; + + rng = &cv::theRNG(); + + set_params( _params ); + + CV_Assert( _featureEvaluator ); + featureEvaluator = _featureEvaluator; + + max_c_count = MAX( 2, featureEvaluator->getMaxCatCount() ); + _resp = cvMat(featureEvaluator->getCls()); + responses = &_resp; + // TODO: check responses: elements must be 0 or 1 + + if( _precalcValBufSize < 0 || _precalcIdxBufSize < 0) + CV_Error( CV_StsOutOfRange, "_numPrecalcVal and _numPrecalcIdx must be positive or 0" ); + + var_count = var_all = featureEvaluator->getNumFeatures() * featureEvaluator->getFeatureSize(); + sample_count = _numSamples; + + is_buf_16u = false; + if (sample_count < 65536) + is_buf_16u = true; + + numPrecalcVal = min( cvRound((double)_precalcValBufSize*1048576. / (sizeof(float)*sample_count)), var_count ); + numPrecalcIdx = min( cvRound((double)_precalcIdxBufSize*1048576. / + ((is_buf_16u ? sizeof(unsigned short) : sizeof (int))*sample_count)), var_count ); + + assert( numPrecalcIdx >= 0 && numPrecalcVal >= 0 ); + + valCache.create( numPrecalcVal, sample_count, CV_32FC1 ); + var_type = cvCreateMat( 1, var_count + 2, CV_32SC1 ); + + if ( featureEvaluator->getMaxCatCount() > 0 ) + { + numPrecalcIdx = 0; + cat_var_count = var_count; + ord_var_count = 0; + for( int vi = 0; vi < var_count; vi++ ) + { + var_type->data.i[vi] = vi; + } + } + else + { + cat_var_count = 0; + ord_var_count = var_count; + for( int vi = 1; vi <= var_count; vi++ ) + { + var_type->data.i[vi-1] = -vi; + } + } + var_type->data.i[var_count] = cat_var_count; + var_type->data.i[var_count+1] = cat_var_count+1; + work_var_count = ( cat_var_count ? 0 : numPrecalcIdx ) + 1/*cv_lables*/; + buf_count = 2; + + buf_size = -1; // the member buf_size is obsolete + + effective_buf_size = (uint64)(work_var_count + 1)*(uint64)sample_count * buf_count; // this is the total size of "CvMat buf" to be allocated + effective_buf_width = sample_count; + effective_buf_height = work_var_count+1; + + if (effective_buf_width >= effective_buf_height) + effective_buf_height *= buf_count; + else + effective_buf_width *= buf_count; + + if ((uint64)effective_buf_width * (uint64)effective_buf_height != effective_buf_size) + { + CV_Error(CV_StsBadArg, "The memory buffer cannot be allocated since its size exceeds integer fields limit"); + } + + if ( is_buf_16u ) + buf = cvCreateMat( effective_buf_height, effective_buf_width, CV_16UC1 ); + else + buf = cvCreateMat( effective_buf_height, effective_buf_width, CV_32SC1 ); + + cat_count = cvCreateMat( 1, cat_var_count + 1, CV_32SC1 ); + + // precalculate valCache and set indices in buf + precalculate(); + + // now calculate the maximum size of split, + // create memory storage that will keep nodes and splits of the decision tree + // allocate root node and the buffer for the whole training data + int maxSplitSize = cvAlign(sizeof(CvDTreeSplit) + + (MAX(0,sample_count - 33)/32)*sizeof(int),sizeof(void*)); + int treeBlockSize = MAX((int)sizeof(CvDTreeNode)*8, maxSplitSize); + treeBlockSize = MAX(treeBlockSize + BlockSizeDelta, MinBlockSize); + tree_storage = cvCreateMemStorage( treeBlockSize ); + node_heap = cvCreateSet( 0, sizeof(*node_heap), sizeof(CvDTreeNode), tree_storage ); + + int nvSize = var_count*sizeof(int); + nvSize = cvAlign(MAX( nvSize, (int)sizeof(CvSetElem) ), sizeof(void*)); + int tempBlockSize = nvSize; + tempBlockSize = MAX( tempBlockSize + BlockSizeDelta, MinBlockSize ); + temp_storage = cvCreateMemStorage( tempBlockSize ); + nv_heap = cvCreateSet( 0, sizeof(*nv_heap), nvSize, temp_storage ); + + data_root = new_node( 0, sample_count, 0, 0 ); + + // set sample labels + if (is_buf_16u) + udst = (unsigned short*)(buf->data.s + (size_t)work_var_count*sample_count); + else + idst = buf->data.i + (size_t)work_var_count*sample_count; + + for (int si = 0; si < sample_count; si++) + { + if (udst) + udst[si] = (unsigned short)si; + else + idst[si] = si; + } + for( int vi = 0; vi < var_count; vi++ ) + data_root->set_num_valid(vi, sample_count); + for( int vi = 0; vi < cat_var_count; vi++ ) + cat_count->data.i[vi] = max_c_count; + + cat_count->data.i[cat_var_count] = 2; + + maxSplitSize = cvAlign(sizeof(CvDTreeSplit) + + (MAX(0,max_c_count - 33)/32)*sizeof(int),sizeof(void*)); + split_heap = cvCreateSet( 0, sizeof(*split_heap), maxSplitSize, tree_storage ); + + priors = cvCreateMat( 1, get_num_classes(), CV_64F ); + cvSet(priors, cvScalar(1)); + priors_mult = cvCloneMat( priors ); + counts = cvCreateMat( 1, get_num_classes(), CV_32SC1 ); + direction = cvCreateMat( 1, sample_count, CV_8UC1 ); + split_buf = cvCreateMat( 1, sample_count, CV_32SC1 );//TODO: make a pointer +} + +void CvCascadeBoostTrainData::free_train_data() +{ + CvDTreeTrainData::free_train_data(); + valCache.release(); +} + +const int* CvCascadeBoostTrainData::get_class_labels( CvDTreeNode* n, int* labelsBuf) +{ + int nodeSampleCount = n->sample_count; + int rStep = CV_IS_MAT_CONT( responses->type ) ? 1 : responses->step / CV_ELEM_SIZE( responses->type ); + + int* sampleIndicesBuf = labelsBuf; // + const int* sampleIndices = get_sample_indices(n, sampleIndicesBuf); + for( int si = 0; si < nodeSampleCount; si++ ) + { + int sidx = sampleIndices[si]; + labelsBuf[si] = (int)responses->data.fl[sidx*rStep]; + } + return labelsBuf; +} + +const int* CvCascadeBoostTrainData::get_sample_indices( CvDTreeNode* n, int* indicesBuf ) +{ + return CvDTreeTrainData::get_cat_var_data( n, get_work_var_count(), indicesBuf ); +} + +const int* CvCascadeBoostTrainData::get_cv_labels( CvDTreeNode* n, int* labels_buf ) +{ + return CvDTreeTrainData::get_cat_var_data( n, get_work_var_count() - 1, labels_buf ); +} + +void CvCascadeBoostTrainData::get_ord_var_data( CvDTreeNode* n, int vi, float* ordValuesBuf, int* sortedIndicesBuf, + const float** ordValues, const int** sortedIndices, int* sampleIndicesBuf ) +{ + int nodeSampleCount = n->sample_count; + const int* sampleIndices = get_sample_indices(n, sampleIndicesBuf); + + if ( vi < numPrecalcIdx ) + { + if( !is_buf_16u ) + *sortedIndices = buf->data.i + n->buf_idx*get_length_subbuf() + (size_t)vi*sample_count + n->offset; + else + { + const unsigned short* shortIndices = (const unsigned short*)(buf->data.s + n->buf_idx*get_length_subbuf() + + (size_t)vi*sample_count + n->offset ); + for( int i = 0; i < nodeSampleCount; i++ ) + sortedIndicesBuf[i] = shortIndices[i]; + + *sortedIndices = sortedIndicesBuf; + } + + if( vi < numPrecalcVal ) + { + for( int i = 0; i < nodeSampleCount; i++ ) + { + int idx = (*sortedIndices)[i]; + idx = sampleIndices[idx]; + ordValuesBuf[i] = valCache.at( vi, idx); + } + } + else + { + for( int i = 0; i < nodeSampleCount; i++ ) + { + int idx = (*sortedIndices)[i]; + idx = sampleIndices[idx]; + ordValuesBuf[i] = (*featureEvaluator)( vi, idx); + } + } + } + else // vi >= numPrecalcIdx + { + cv::AutoBuffer abuf(nodeSampleCount); + float* sampleValues = &abuf[0]; + + if ( vi < numPrecalcVal ) + { + for( int i = 0; i < nodeSampleCount; i++ ) + { + sortedIndicesBuf[i] = i; + sampleValues[i] = valCache.at( vi, sampleIndices[i] ); + } + } + else + { + for( int i = 0; i < nodeSampleCount; i++ ) + { + sortedIndicesBuf[i] = i; + sampleValues[i] = (*featureEvaluator)( vi, sampleIndices[i]); + } + } + std::sort(sortedIndicesBuf, sortedIndicesBuf + nodeSampleCount, LessThanIdx(&sampleValues[0]) ); + for( int i = 0; i < nodeSampleCount; i++ ) + ordValuesBuf[i] = (&sampleValues[0])[sortedIndicesBuf[i]]; + *sortedIndices = sortedIndicesBuf; + } + + *ordValues = ordValuesBuf; +} + +const int* CvCascadeBoostTrainData::get_cat_var_data( CvDTreeNode* n, int vi, int* catValuesBuf ) +{ + int nodeSampleCount = n->sample_count; + int* sampleIndicesBuf = catValuesBuf; // + const int* sampleIndices = get_sample_indices(n, sampleIndicesBuf); + + if ( vi < numPrecalcVal ) + { + for( int i = 0; i < nodeSampleCount; i++ ) + catValuesBuf[i] = (int) valCache.at( vi, sampleIndices[i]); + } + else + { + if( vi >= numPrecalcVal && vi < var_count ) + { + for( int i = 0; i < nodeSampleCount; i++ ) + catValuesBuf[i] = (int)(*featureEvaluator)( vi, sampleIndices[i] ); + } + else + { + get_cv_labels( n, catValuesBuf ); + } + } + + return catValuesBuf; +} + +float CvCascadeBoostTrainData::getVarValue( int vi, int si ) +{ + if ( vi < numPrecalcVal && !valCache.empty() ) + return valCache.at( vi, si ); + return (*featureEvaluator)( vi, si ); +} + + +struct FeatureIdxOnlyPrecalc : ParallelLoopBody +{ + FeatureIdxOnlyPrecalc( const CvFeatureEvaluator* _featureEvaluator, CvMat* _buf, int _sample_count, bool _is_buf_16u ) + { + featureEvaluator = _featureEvaluator; + sample_count = _sample_count; + udst = (unsigned short*)_buf->data.s; + idst = _buf->data.i; + is_buf_16u = _is_buf_16u; + } + void operator()( const Range& range ) const + { + cv::AutoBuffer valCache(sample_count); + float* valCachePtr = valCache.data(); + for ( int fi = range.start; fi < range.end; fi++) + { + for( int si = 0; si < sample_count; si++ ) + { + valCachePtr[si] = (*featureEvaluator)( fi, si ); + if ( is_buf_16u ) + *(udst + (size_t)fi*sample_count + si) = (unsigned short)si; + else + *(idst + (size_t)fi*sample_count + si) = si; + } + if ( is_buf_16u ) + std::sort(udst + (size_t)fi*sample_count, udst + (size_t)(fi + 1)*sample_count, LessThanIdx(valCachePtr) ); + else + std::sort(idst + (size_t)fi*sample_count, idst + (size_t)(fi + 1)*sample_count, LessThanIdx(valCachePtr) ); + } + } + const CvFeatureEvaluator* featureEvaluator; + int sample_count; + int* idst; + unsigned short* udst; + bool is_buf_16u; +}; + +struct FeatureValAndIdxPrecalc : ParallelLoopBody +{ + FeatureValAndIdxPrecalc( const CvFeatureEvaluator* _featureEvaluator, CvMat* _buf, Mat* _valCache, int _sample_count, bool _is_buf_16u ) + { + featureEvaluator = _featureEvaluator; + valCache = _valCache; + sample_count = _sample_count; + udst = (unsigned short*)_buf->data.s; + idst = _buf->data.i; + is_buf_16u = _is_buf_16u; + } + void operator()( const Range& range ) const + { + for ( int fi = range.start; fi < range.end; fi++) + { + for( int si = 0; si < sample_count; si++ ) + { + valCache->at(fi,si) = (*featureEvaluator)( fi, si ); + if ( is_buf_16u ) + *(udst + (size_t)fi*sample_count + si) = (unsigned short)si; + else + *(idst + (size_t)fi*sample_count + si) = si; + } + if ( is_buf_16u ) + std::sort(udst + (size_t)fi*sample_count, udst + (size_t)(fi + 1)*sample_count, LessThanIdx(valCache->ptr(fi)) ); + else + std::sort(idst + (size_t)fi*sample_count, idst + (size_t)(fi + 1)*sample_count, LessThanIdx(valCache->ptr(fi)) ); + } + } + const CvFeatureEvaluator* featureEvaluator; + Mat* valCache; + int sample_count; + int* idst; + unsigned short* udst; + bool is_buf_16u; +}; + +struct FeatureValOnlyPrecalc : ParallelLoopBody +{ + FeatureValOnlyPrecalc( const CvFeatureEvaluator* _featureEvaluator, Mat* _valCache, int _sample_count ) + { + featureEvaluator = _featureEvaluator; + valCache = _valCache; + sample_count = _sample_count; + } + void operator()( const Range& range ) const + { + for ( int fi = range.start; fi < range.end; fi++) + for( int si = 0; si < sample_count; si++ ) + valCache->at(fi,si) = (*featureEvaluator)( fi, si ); + } + const CvFeatureEvaluator* featureEvaluator; + Mat* valCache; + int sample_count; +}; + +void CvCascadeBoostTrainData::precalculate() +{ + int minNum = MIN( numPrecalcVal, numPrecalcIdx); + + double proctime = -TIME( 0 ); + parallel_for_( Range(numPrecalcVal, numPrecalcIdx), + FeatureIdxOnlyPrecalc(featureEvaluator, buf, sample_count, is_buf_16u!=0) ); + parallel_for_( Range(0, minNum), + FeatureValAndIdxPrecalc(featureEvaluator, buf, &valCache, sample_count, is_buf_16u!=0) ); + parallel_for_( Range(minNum, numPrecalcVal), + FeatureValOnlyPrecalc(featureEvaluator, &valCache, sample_count) ); + cout << "Precalculation time: " << (proctime + TIME( 0 )) << endl; +} + +//-------------------------------- CascadeBoostTree ---------------------------------------- + +CvDTreeNode* CvCascadeBoostTree::predict( int sampleIdx ) const +{ + CvDTreeNode* node = root; + if( !node ) + CV_Error( CV_StsError, "The tree has not been trained yet" ); + + if ( ((CvCascadeBoostTrainData*)data)->featureEvaluator->getMaxCatCount() == 0 ) // ordered + { + while( node->left ) + { + CvDTreeSplit* split = node->split; + float val = ((CvCascadeBoostTrainData*)data)->getVarValue( split->var_idx, sampleIdx ); + node = val <= split->ord.c ? node->left : node->right; + } + } + else // categorical + { + while( node->left ) + { + CvDTreeSplit* split = node->split; + int c = (int)((CvCascadeBoostTrainData*)data)->getVarValue( split->var_idx, sampleIdx ); + node = CV_DTREE_CAT_DIR(c, split->subset) < 0 ? node->left : node->right; + } + } + return node; +} + +void CvCascadeBoostTree::write( FileStorage &fs, const Mat& featureMap ) +{ + int maxCatCount = ((CvCascadeBoostTrainData*)data)->featureEvaluator->getMaxCatCount(); + int subsetN = (maxCatCount + 31)/32; + queue internalNodesQueue; + int size = (int)pow( 2.f, (float)ensemble->get_params().max_depth); + std::vector leafVals(size); + int leafValIdx = 0; + int internalNodeIdx = 1; + CvDTreeNode* tempNode; + + CV_DbgAssert( root ); + internalNodesQueue.push( root ); + + fs << "{"; + fs << CC_INTERNAL_NODES << "[:"; + while (!internalNodesQueue.empty()) + { + tempNode = internalNodesQueue.front(); + CV_Assert( tempNode->left ); + if ( !tempNode->left->left && !tempNode->left->right) // left node is leaf + { + leafVals[-leafValIdx] = (float)tempNode->left->value; + fs << leafValIdx-- ; + } + else + { + internalNodesQueue.push( tempNode->left ); + fs << internalNodeIdx++; + } + CV_Assert( tempNode->right ); + if ( !tempNode->right->left && !tempNode->right->right) // right node is leaf + { + leafVals[-leafValIdx] = (float)tempNode->right->value; + fs << leafValIdx--; + } + else + { + internalNodesQueue.push( tempNode->right ); + fs << internalNodeIdx++; + } + int fidx = tempNode->split->var_idx; + fidx = featureMap.empty() ? fidx : featureMap.at(0, fidx); + fs << fidx; + if ( !maxCatCount ) + fs << tempNode->split->ord.c; + else + for( int i = 0; i < subsetN; i++ ) + fs << tempNode->split->subset[i]; + internalNodesQueue.pop(); + } + fs << "]"; // CC_INTERNAL_NODES + + fs << CC_LEAF_VALUES << "[:"; + for (int ni = 0; ni < -leafValIdx; ni++) + fs << leafVals[ni]; + fs << "]"; // CC_LEAF_VALUES + fs << "}"; +} + +void CvCascadeBoostTree::read( const FileNode &node, CvBoost* _ensemble, + CvDTreeTrainData* _data ) +{ + int maxCatCount = ((CvCascadeBoostTrainData*)_data)->featureEvaluator->getMaxCatCount(); + int subsetN = (maxCatCount + 31)/32; + int step = 3 + ( maxCatCount>0 ? subsetN : 1 ); + + queue internalNodesQueue; + FileNodeIterator internalNodesIt, leafValsuesIt; + CvDTreeNode* prntNode, *cldNode; + + clear(); + data = _data; + ensemble = _ensemble; + pruned_tree_idx = 0; + + // read tree nodes + FileNode rnode = node[CC_INTERNAL_NODES]; + internalNodesIt = rnode.end(); + leafValsuesIt = node[CC_LEAF_VALUES].end(); + internalNodesIt--; leafValsuesIt--; + for( size_t i = 0; i < rnode.size()/step; i++ ) + { + prntNode = data->new_node( 0, 0, 0, 0 ); + if ( maxCatCount > 0 ) + { + prntNode->split = data->new_split_cat( 0, 0 ); + for( int j = subsetN-1; j>=0; j--) + { + *internalNodesIt >> prntNode->split->subset[j]; internalNodesIt--; + } + } + else + { + float split_value; + *internalNodesIt >> split_value; internalNodesIt--; + prntNode->split = data->new_split_ord( 0, split_value, 0, 0, 0); + } + *internalNodesIt >> prntNode->split->var_idx; internalNodesIt--; + int ridx, lidx; + *internalNodesIt >> ridx; internalNodesIt--; + *internalNodesIt >> lidx;internalNodesIt--; + if ( ridx <= 0) + { + prntNode->right = cldNode = data->new_node( 0, 0, 0, 0 ); + *leafValsuesIt >> cldNode->value; leafValsuesIt--; + cldNode->parent = prntNode; + } + else + { + prntNode->right = internalNodesQueue.front(); + prntNode->right->parent = prntNode; + internalNodesQueue.pop(); + } + + if ( lidx <= 0) + { + prntNode->left = cldNode = data->new_node( 0, 0, 0, 0 ); + *leafValsuesIt >> cldNode->value; leafValsuesIt--; + cldNode->parent = prntNode; + } + else + { + prntNode->left = internalNodesQueue.front(); + prntNode->left->parent = prntNode; + internalNodesQueue.pop(); + } + + internalNodesQueue.push( prntNode ); + } + + root = internalNodesQueue.front(); + internalNodesQueue.pop(); +} + +void CvCascadeBoostTree::split_node_data( CvDTreeNode* node ) +{ + int n = node->sample_count, nl, nr, scount = data->sample_count; + char* dir = (char*)data->direction->data.ptr; + CvDTreeNode *left = 0, *right = 0; + int* newIdx = data->split_buf->data.i; + int newBufIdx = data->get_child_buf_idx( node ); + int workVarCount = data->get_work_var_count(); + CvMat* buf = data->buf; + size_t length_buf_row = data->get_length_subbuf(); + cv::AutoBuffer inn_buf(n*(3*sizeof(int)+sizeof(float))); + int* tempBuf = (int*)inn_buf.data(); + bool splitInputData; + + complete_node_dir(node); + + for( int i = nl = nr = 0; i < n; i++ ) + { + int d = dir[i]; + // initialize new indices for splitting ordered variables + newIdx[i] = (nl & (d-1)) | (nr & -d); // d ? ri : li + nr += d; + nl += d^1; + } + + node->left = left = data->new_node( node, nl, newBufIdx, node->offset ); + node->right = right = data->new_node( node, nr, newBufIdx, node->offset + nl ); + + splitInputData = node->depth + 1 < data->params.max_depth && + (node->left->sample_count > data->params.min_sample_count || + node->right->sample_count > data->params.min_sample_count); + + // split ordered variables, keep both halves sorted. + for( int vi = 0; vi < ((CvCascadeBoostTrainData*)data)->numPrecalcIdx; vi++ ) + { + int ci = data->get_var_type(vi); + if( ci >= 0 || !splitInputData ) + continue; + + int n1 = node->get_num_valid(vi); + float *src_val_buf = (float*)(tempBuf + n); + int *src_sorted_idx_buf = (int*)(src_val_buf + n); + int *src_sample_idx_buf = src_sorted_idx_buf + n; + const int* src_sorted_idx = 0; + const float* src_val = 0; + data->get_ord_var_data(node, vi, src_val_buf, src_sorted_idx_buf, &src_val, &src_sorted_idx, src_sample_idx_buf); + + for(int i = 0; i < n; i++) + tempBuf[i] = src_sorted_idx[i]; + + if (data->is_buf_16u) + { + ushort *ldst, *rdst; + ldst = (ushort*)(buf->data.s + left->buf_idx*length_buf_row + + vi*scount + left->offset); + rdst = (ushort*)(ldst + nl); + + // split sorted + for( int i = 0; i < n1; i++ ) + { + int idx = tempBuf[i]; + int d = dir[idx]; + idx = newIdx[idx]; + if (d) + { + *rdst = (ushort)idx; + rdst++; + } + else + { + *ldst = (ushort)idx; + ldst++; + } + } + CV_Assert( n1 == n ); + } + else + { + int *ldst, *rdst; + ldst = buf->data.i + left->buf_idx*length_buf_row + + vi*scount + left->offset; + rdst = buf->data.i + right->buf_idx*length_buf_row + + vi*scount + right->offset; + + // split sorted + for( int i = 0; i < n1; i++ ) + { + int idx = tempBuf[i]; + int d = dir[idx]; + idx = newIdx[idx]; + if (d) + { + *rdst = idx; + rdst++; + } + else + { + *ldst = idx; + ldst++; + } + } + CV_Assert( n1 == n ); + } + } + + // split cv_labels using newIdx relocation table + int *src_lbls_buf = tempBuf + n; + const int* src_lbls = data->get_cv_labels(node, src_lbls_buf); + + for(int i = 0; i < n; i++) + tempBuf[i] = src_lbls[i]; + + if (data->is_buf_16u) + { + unsigned short *ldst = (unsigned short *)(buf->data.s + left->buf_idx*length_buf_row + + (size_t)(workVarCount-1)*scount + left->offset); + unsigned short *rdst = (unsigned short *)(buf->data.s + right->buf_idx*length_buf_row + + (size_t)(workVarCount-1)*scount + right->offset); + + for( int i = 0; i < n; i++ ) + { + int idx = tempBuf[i]; + if (dir[i]) + { + *rdst = (unsigned short)idx; + rdst++; + } + else + { + *ldst = (unsigned short)idx; + ldst++; + } + } + + } + else + { + int *ldst = buf->data.i + left->buf_idx*length_buf_row + + (size_t)(workVarCount-1)*scount + left->offset; + int *rdst = buf->data.i + right->buf_idx*length_buf_row + + (size_t)(workVarCount-1)*scount + right->offset; + + for( int i = 0; i < n; i++ ) + { + int idx = tempBuf[i]; + if (dir[i]) + { + *rdst = idx; + rdst++; + } + else + { + *ldst = idx; + ldst++; + } + } + } + + // split sample indices + int *sampleIdx_src_buf = tempBuf + n; + const int* sampleIdx_src = data->get_sample_indices(node, sampleIdx_src_buf); + + for(int i = 0; i < n; i++) + tempBuf[i] = sampleIdx_src[i]; + + if (data->is_buf_16u) + { + unsigned short* ldst = (unsigned short*)(buf->data.s + left->buf_idx*length_buf_row + + (size_t)workVarCount*scount + left->offset); + unsigned short* rdst = (unsigned short*)(buf->data.s + right->buf_idx*length_buf_row + + (size_t)workVarCount*scount + right->offset); + for (int i = 0; i < n; i++) + { + unsigned short idx = (unsigned short)tempBuf[i]; + if (dir[i]) + { + *rdst = idx; + rdst++; + } + else + { + *ldst = idx; + ldst++; + } + } + } + else + { + int* ldst = buf->data.i + left->buf_idx*length_buf_row + + (size_t)workVarCount*scount + left->offset; + int* rdst = buf->data.i + right->buf_idx*length_buf_row + + (size_t)workVarCount*scount + right->offset; + for (int i = 0; i < n; i++) + { + int idx = tempBuf[i]; + if (dir[i]) + { + *rdst = idx; + rdst++; + } + else + { + *ldst = idx; + ldst++; + } + } + } + + for( int vi = 0; vi < data->var_count; vi++ ) + { + left->set_num_valid(vi, (int)(nl)); + right->set_num_valid(vi, (int)(nr)); + } + + // deallocate the parent node data that is not needed anymore + data->free_node_data(node); +} + +static void auxMarkFeaturesInMap( const CvDTreeNode* node, Mat& featureMap) +{ + if ( node && node->split ) + { + featureMap.ptr(0)[node->split->var_idx] = 1; + auxMarkFeaturesInMap( node->left, featureMap ); + auxMarkFeaturesInMap( node->right, featureMap ); + } +} + +void CvCascadeBoostTree::markFeaturesInMap( Mat& featureMap ) +{ + auxMarkFeaturesInMap( root, featureMap ); +} + +//----------------------------------- CascadeBoost -------------------------------------- + +bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator, + int _numSamples, + int _precalcValBufSize, int _precalcIdxBufSize, + const CvCascadeBoostParams& _params ) +{ + bool isTrained = false; + CV_Assert( !data ); + clear(); + data = new CvCascadeBoostTrainData( _featureEvaluator, _numSamples, + _precalcValBufSize, _precalcIdxBufSize, _params ); + CvMemStorage *storage = cvCreateMemStorage(); + weak = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvBoostTree*), storage ); + storage = 0; + + set_params( _params ); + if ( (_params.boost_type == LOGIT) || (_params.boost_type == GENTLE) ) + data->do_responses_copy(); + + update_weights( 0 ); + + cout << "+----+---------+---------+" << endl; + cout << "| N | HR | FA |" << endl; + cout << "+----+---------+---------+" << endl; + + do + { + CvCascadeBoostTree* tree = new CvCascadeBoostTree; + if( !tree->train( data, subsample_mask, this ) ) + { + delete tree; + break; + } + cvSeqPush( weak, &tree ); + update_weights( tree ); + trim_weights(); + if( cvCountNonZero(subsample_mask) == 0 ) + break; + } + while( !isErrDesired() && (weak->total < params.weak_count) ); + + if(weak->total > 0) + { + data->is_classifier = true; + data->free_train_data(); + isTrained = true; + } + else + clear(); + + return isTrained; +} + +float CvCascadeBoost::predict( int sampleIdx, bool returnSum ) const +{ + CV_Assert( weak ); + double sum = 0; + CvSeqReader reader; + cvStartReadSeq( weak, &reader ); + cvSetSeqReaderPos( &reader, 0 ); + for( int i = 0; i < weak->total; i++ ) + { + CvBoostTree* wtree; + CV_READ_SEQ_ELEM( wtree, reader ); + sum += ((CvCascadeBoostTree*)wtree)->predict(sampleIdx)->value; + } + if( !returnSum ) + sum = sum < threshold - CV_THRESHOLD_EPS ? 0.0 : 1.0; + return (float)sum; +} + +bool CvCascadeBoost::set_params( const CvBoostParams& _params ) +{ + minHitRate = ((CvCascadeBoostParams&)_params).minHitRate; + maxFalseAlarm = ((CvCascadeBoostParams&)_params).maxFalseAlarm; + return ( ( minHitRate > 0 ) && ( minHitRate < 1) && + ( maxFalseAlarm > 0 ) && ( maxFalseAlarm < 1) && + CvBoost::set_params( _params )); +} + +void CvCascadeBoost::update_weights( CvBoostTree* tree ) +{ + int n = data->sample_count; + double sumW = 0.; + int step = 0; + float* fdata = 0; + int *sampleIdxBuf; + const int* sampleIdx = 0; + int inn_buf_size = ((params.boost_type == LOGIT) || (params.boost_type == GENTLE) ? n*sizeof(int) : 0) + + ( !tree ? n*sizeof(int) : 0 ); + cv::AutoBuffer inn_buf(inn_buf_size); + uchar* cur_inn_buf_pos = inn_buf.data(); + if ( (params.boost_type == LOGIT) || (params.boost_type == GENTLE) ) + { + step = CV_IS_MAT_CONT(data->responses_copy->type) ? + 1 : data->responses_copy->step / CV_ELEM_SIZE(data->responses_copy->type); + fdata = data->responses_copy->data.fl; + sampleIdxBuf = (int*)cur_inn_buf_pos; cur_inn_buf_pos = (uchar*)(sampleIdxBuf + n); + sampleIdx = data->get_sample_indices( data->data_root, sampleIdxBuf ); + } + CvMat* buf = data->buf; + size_t length_buf_row = data->get_length_subbuf(); + if( !tree ) // before training the first tree, initialize weights and other parameters + { + int* classLabelsBuf = (int*)cur_inn_buf_pos; cur_inn_buf_pos = (uchar*)(classLabelsBuf + n); + const int* classLabels = data->get_class_labels(data->data_root, classLabelsBuf); + // in case of logitboost and gentle adaboost each weak tree is a regression tree, + // so we need to convert class labels to floating-point values + double w0 = 1./n; + double p[2] = { 1, 1 }; + + cvReleaseMat( &orig_response ); + cvReleaseMat( &sum_response ); + cvReleaseMat( &weak_eval ); + cvReleaseMat( &subsample_mask ); + cvReleaseMat( &weights ); + + orig_response = cvCreateMat( 1, n, CV_32S ); + weak_eval = cvCreateMat( 1, n, CV_64F ); + subsample_mask = cvCreateMat( 1, n, CV_8U ); + weights = cvCreateMat( 1, n, CV_64F ); + subtree_weights = cvCreateMat( 1, n + 2, CV_64F ); + + if (data->is_buf_16u) + { + unsigned short* labels = (unsigned short*)(buf->data.s + data->data_root->buf_idx*length_buf_row + + data->data_root->offset + (size_t)(data->work_var_count-1)*data->sample_count); + for( int i = 0; i < n; i++ ) + { + // save original categorical responses {0,1}, convert them to {-1,1} + orig_response->data.i[i] = classLabels[i]*2 - 1; + // make all the samples active at start. + // later, in trim_weights() deactivate/reactive again some, if need + subsample_mask->data.ptr[i] = (uchar)1; + // make all the initial weights the same. + weights->data.db[i] = w0*p[classLabels[i]]; + // set the labels to find (from within weak tree learning proc) + // the particular sample weight, and where to store the response. + labels[i] = (unsigned short)i; + } + } + else + { + int* labels = buf->data.i + data->data_root->buf_idx*length_buf_row + + data->data_root->offset + (size_t)(data->work_var_count-1)*data->sample_count; + + for( int i = 0; i < n; i++ ) + { + // save original categorical responses {0,1}, convert them to {-1,1} + orig_response->data.i[i] = classLabels[i]*2 - 1; + subsample_mask->data.ptr[i] = (uchar)1; + weights->data.db[i] = w0*p[classLabels[i]]; + labels[i] = i; + } + } + + if( params.boost_type == LOGIT ) + { + sum_response = cvCreateMat( 1, n, CV_64F ); + + for( int i = 0; i < n; i++ ) + { + sum_response->data.db[i] = 0; + fdata[sampleIdx[i]*step] = orig_response->data.i[i] > 0 ? 2.f : -2.f; + } + + // in case of logitboost each weak tree is a regression tree. + // the target function values are recalculated for each of the trees + data->is_classifier = false; + } + else if( params.boost_type == GENTLE ) + { + for( int i = 0; i < n; i++ ) + fdata[sampleIdx[i]*step] = (float)orig_response->data.i[i]; + + data->is_classifier = false; + } + } + else + { + // at this moment, for all the samples that participated in the training of the most + // recent weak classifier we know the responses. For other samples we need to compute them + if( have_subsample ) + { + // invert the subsample mask + cvXorS( subsample_mask, cvScalar(1.), subsample_mask ); + + // run tree through all the non-processed samples + for( int i = 0; i < n; i++ ) + if( subsample_mask->data.ptr[i] ) + { + weak_eval->data.db[i] = ((CvCascadeBoostTree*)tree)->predict( i )->value; + } + } + + // now update weights and other parameters for each type of boosting + if( params.boost_type == DISCRETE ) + { + // Discrete AdaBoost: + // weak_eval[i] (=f(x_i)) is in {-1,1} + // err = sum(w_i*(f(x_i) != y_i))/sum(w_i) + // C = log((1-err)/err) + // w_i *= exp(C*(f(x_i) != y_i)) + + double C, err = 0.; + double scale[] = { 1., 0. }; + + for( int i = 0; i < n; i++ ) + { + double w = weights->data.db[i]; + sumW += w; + err += w*(weak_eval->data.db[i] != orig_response->data.i[i]); + } + + if( sumW != 0 ) + err /= sumW; + C = err = -logRatio( err ); + scale[1] = exp(err); + + sumW = 0; + for( int i = 0; i < n; i++ ) + { + double w = weights->data.db[i]* + scale[weak_eval->data.db[i] != orig_response->data.i[i]]; + sumW += w; + weights->data.db[i] = w; + } + + tree->scale( C ); + } + else if( params.boost_type == REAL ) + { + // Real AdaBoost: + // weak_eval[i] = f(x_i) = 0.5*log(p(x_i)/(1-p(x_i))), p(x_i)=P(y=1|x_i) + // w_i *= exp(-y_i*f(x_i)) + + for( int i = 0; i < n; i++ ) + weak_eval->data.db[i] *= -orig_response->data.i[i]; + + cvExp( weak_eval, weak_eval ); + + for( int i = 0; i < n; i++ ) + { + double w = weights->data.db[i]*weak_eval->data.db[i]; + sumW += w; + weights->data.db[i] = w; + } + } + else if( params.boost_type == LOGIT ) + { + // LogitBoost: + // weak_eval[i] = f(x_i) in [-z_max,z_max] + // sum_response = F(x_i). + // F(x_i) += 0.5*f(x_i) + // p(x_i) = exp(F(x_i))/(exp(F(x_i)) + exp(-F(x_i))=1/(1+exp(-2*F(x_i))) + // reuse weak_eval: weak_eval[i] <- p(x_i) + // w_i = p(x_i)*1(1 - p(x_i)) + // z_i = ((y_i+1)/2 - p(x_i))/(p(x_i)*(1 - p(x_i))) + // store z_i to the data->data_root as the new target responses + + const double lbWeightThresh = FLT_EPSILON; + const double lbZMax = 10.; + + for( int i = 0; i < n; i++ ) + { + double s = sum_response->data.db[i] + 0.5*weak_eval->data.db[i]; + sum_response->data.db[i] = s; + weak_eval->data.db[i] = -2*s; + } + + cvExp( weak_eval, weak_eval ); + + for( int i = 0; i < n; i++ ) + { + double p = 1./(1. + weak_eval->data.db[i]); + double w = p*(1 - p), z; + w = MAX( w, lbWeightThresh ); + weights->data.db[i] = w; + sumW += w; + if( orig_response->data.i[i] > 0 ) + { + z = 1./p; + fdata[sampleIdx[i]*step] = (float)min(z, lbZMax); + } + else + { + z = 1./(1-p); + fdata[sampleIdx[i]*step] = (float)-min(z, lbZMax); + } + } + } + else + { + // Gentle AdaBoost: + // weak_eval[i] = f(x_i) in [-1,1] + // w_i *= exp(-y_i*f(x_i)) + assert( params.boost_type == GENTLE ); + + for( int i = 0; i < n; i++ ) + weak_eval->data.db[i] *= -orig_response->data.i[i]; + + cvExp( weak_eval, weak_eval ); + + for( int i = 0; i < n; i++ ) + { + double w = weights->data.db[i] * weak_eval->data.db[i]; + weights->data.db[i] = w; + sumW += w; + } + } + } + + // renormalize weights + if( sumW > FLT_EPSILON ) + { + sumW = 1./sumW; + for( int i = 0; i < n; ++i ) + weights->data.db[i] *= sumW; + } +} + +bool CvCascadeBoost::isErrDesired() +{ + int sCount = data->sample_count, + numPos = 0, numNeg = 0, numFalse = 0, numPosTrue = 0; + vector eval(sCount); + + for( int i = 0; i < sCount; i++ ) + if( ((CvCascadeBoostTrainData*)data)->featureEvaluator->getCls( i ) == 1.0F ) + eval[numPos++] = predict( i, true ); + + std::sort(&eval[0], &eval[0] + numPos); + + int thresholdIdx = (int)((1.0F - minHitRate) * numPos); + + threshold = eval[ thresholdIdx ]; + numPosTrue = numPos - thresholdIdx; + for( int i = thresholdIdx - 1; i >= 0; i--) + if ( abs( eval[i] - threshold) < FLT_EPSILON ) + numPosTrue++; + float hitRate = ((float) numPosTrue) / ((float) numPos); + + for( int i = 0; i < sCount; i++ ) + { + if( ((CvCascadeBoostTrainData*)data)->featureEvaluator->getCls( i ) == 0.0F ) + { + numNeg++; + if( predict( i ) ) + numFalse++; + } + } + float falseAlarm = ((float) numFalse) / ((float) numNeg); + + cout << "|"; cout.width(4); cout << right << weak->total; + cout << "|"; cout.width(9); cout << right << hitRate; + cout << "|"; cout.width(9); cout << right << falseAlarm; + cout << "|" << endl; + cout << "+----+---------+---------+" << endl; + + return falseAlarm <= maxFalseAlarm; +} + +void CvCascadeBoost::write( FileStorage &fs, const Mat& featureMap ) const +{ +// char cmnt[30]; + CvCascadeBoostTree* weakTree; + fs << CC_WEAK_COUNT << weak->total; + fs << CC_STAGE_THRESHOLD << threshold; + fs << CC_WEAK_CLASSIFIERS << "["; + for( int wi = 0; wi < weak->total; wi++) + { + /*sprintf( cmnt, "tree %i", wi ); + cvWriteComment( fs, cmnt, 0 );*/ + weakTree = *((CvCascadeBoostTree**) cvGetSeqElem( weak, wi )); + weakTree->write( fs, featureMap ); + } + fs << "]"; +} + +bool CvCascadeBoost::read( const FileNode &node, + const CvFeatureEvaluator* _featureEvaluator, + const CvCascadeBoostParams& _params ) +{ + CvMemStorage* storage; + clear(); + data = new CvCascadeBoostTrainData( _featureEvaluator, _params ); + set_params( _params ); + + node[CC_STAGE_THRESHOLD] >> threshold; + FileNode rnode = node[CC_WEAK_CLASSIFIERS]; + + storage = cvCreateMemStorage(); + weak = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvBoostTree*), storage ); + for( FileNodeIterator it = rnode.begin(); it != rnode.end(); it++ ) + { + CvCascadeBoostTree* tree = new CvCascadeBoostTree(); + tree->read( *it, this, data ); + cvSeqPush( weak, &tree ); + } + return true; +} + +void CvCascadeBoost::markUsedFeaturesInMap( Mat& featureMap ) +{ + for( int wi = 0; wi < weak->total; wi++ ) + { + CvCascadeBoostTree* weakTree = *((CvCascadeBoostTree**) cvGetSeqElem( weak, wi )); + weakTree->markFeaturesInMap( featureMap ); + } +} diff --git a/opencv-apps/traincascade/boost.h b/opencv-apps/traincascade/boost.h new file mode 100644 index 0000000..48d4789 --- /dev/null +++ b/opencv-apps/traincascade/boost.h @@ -0,0 +1,86 @@ +#ifndef _OPENCV_BOOST_H_ +#define _OPENCV_BOOST_H_ + +#include "traincascade_features.h" +#include "old_ml.hpp" + +struct CvCascadeBoostParams : CvBoostParams +{ + float minHitRate; + float maxFalseAlarm; + + CvCascadeBoostParams(); + CvCascadeBoostParams( int _boostType, float _minHitRate, float _maxFalseAlarm, + double _weightTrimRate, int _maxDepth, int _maxWeakCount ); + virtual ~CvCascadeBoostParams() {} + void write( cv::FileStorage &fs ) const; + bool read( const cv::FileNode &node ); + virtual void printDefaults() const; + virtual void printAttrs() const; + virtual bool scanAttr( const std::string prmName, const std::string val); +}; + +struct CvCascadeBoostTrainData : CvDTreeTrainData +{ + CvCascadeBoostTrainData( const CvFeatureEvaluator* _featureEvaluator, + const CvDTreeParams& _params ); + CvCascadeBoostTrainData( const CvFeatureEvaluator* _featureEvaluator, + int _numSamples, int _precalcValBufSize, int _precalcIdxBufSize, + const CvDTreeParams& _params = CvDTreeParams() ); + virtual void setData( const CvFeatureEvaluator* _featureEvaluator, + int _numSamples, int _precalcValBufSize, int _precalcIdxBufSize, + const CvDTreeParams& _params=CvDTreeParams() ); + void precalculate(); + + virtual CvDTreeNode* subsample_data( const CvMat* _subsample_idx ); + + virtual const int* get_class_labels( CvDTreeNode* n, int* labelsBuf ); + virtual const int* get_cv_labels( CvDTreeNode* n, int* labelsBuf); + virtual const int* get_sample_indices( CvDTreeNode* n, int* indicesBuf ); + + virtual void get_ord_var_data( CvDTreeNode* n, int vi, float* ordValuesBuf, int* sortedIndicesBuf, + const float** ordValues, const int** sortedIndices, int* sampleIndicesBuf ); + virtual const int* get_cat_var_data( CvDTreeNode* n, int vi, int* catValuesBuf ); + virtual float getVarValue( int vi, int si ); + virtual void free_train_data(); + + const CvFeatureEvaluator* featureEvaluator; + cv::Mat valCache; // precalculated feature values (CV_32FC1) + CvMat _resp; // for casting + int numPrecalcVal, numPrecalcIdx; +}; + +class CvCascadeBoostTree : public CvBoostTree +{ +public: + virtual CvDTreeNode* predict( int sampleIdx ) const; + void write( cv::FileStorage &fs, const cv::Mat& featureMap ); + void read( const cv::FileNode &node, CvBoost* _ensemble, CvDTreeTrainData* _data ); + void markFeaturesInMap( cv::Mat& featureMap ); +protected: + virtual void split_node_data( CvDTreeNode* n ); +}; + +class CvCascadeBoost : public CvBoost +{ +public: + virtual bool train( const CvFeatureEvaluator* _featureEvaluator, + int _numSamples, int _precalcValBufSize, int _precalcIdxBufSize, + const CvCascadeBoostParams& _params=CvCascadeBoostParams() ); + virtual float predict( int sampleIdx, bool returnSum = false ) const; + + float getThreshold() const { return threshold; } + void write( cv::FileStorage &fs, const cv::Mat& featureMap ) const; + bool read( const cv::FileNode &node, const CvFeatureEvaluator* _featureEvaluator, + const CvCascadeBoostParams& _params ); + void markUsedFeaturesInMap( cv::Mat& featureMap ); +protected: + virtual bool set_params( const CvBoostParams& _params ); + virtual void update_weights( CvBoostTree* tree ); + virtual bool isErrDesired(); + + float threshold; + float minHitRate, maxFalseAlarm; +}; + +#endif diff --git a/opencv-apps/traincascade/cascadeclassifier.cpp b/opencv-apps/traincascade/cascadeclassifier.cpp new file mode 100644 index 0000000..3d7b383 --- /dev/null +++ b/opencv-apps/traincascade/cascadeclassifier.cpp @@ -0,0 +1,571 @@ +#include "opencv2/core.hpp" + +#include "cascadeclassifier.h" +#include + +using namespace std; +using namespace cv; + +static const char* stageTypes[] = { CC_BOOST }; +static const char* featureTypes[] = { CC_HAAR, CC_LBP, CC_HOG }; + +CvCascadeParams::CvCascadeParams() : stageType( defaultStageType ), + featureType( defaultFeatureType ), winSize( cvSize(24, 24) ) +{ + name = CC_CASCADE_PARAMS; +} +CvCascadeParams::CvCascadeParams( int _stageType, int _featureType ) : stageType( _stageType ), + featureType( _featureType ), winSize( cvSize(24, 24) ) +{ + name = CC_CASCADE_PARAMS; +} + +//---------------------------- CascadeParams -------------------------------------- + +void CvCascadeParams::write( FileStorage &fs ) const +{ + string stageTypeStr = stageType == BOOST ? CC_BOOST : string(); + CV_Assert( !stageTypeStr.empty() ); + fs << CC_STAGE_TYPE << stageTypeStr; + string featureTypeStr = featureType == CvFeatureParams::HAAR ? CC_HAAR : + featureType == CvFeatureParams::LBP ? CC_LBP : + featureType == CvFeatureParams::HOG ? CC_HOG : + 0; + CV_Assert( !stageTypeStr.empty() ); + fs << CC_FEATURE_TYPE << featureTypeStr; + fs << CC_HEIGHT << winSize.height; + fs << CC_WIDTH << winSize.width; +} + +bool CvCascadeParams::read( const FileNode &node ) +{ + if ( node.empty() ) + return false; + string stageTypeStr, featureTypeStr; + FileNode rnode = node[CC_STAGE_TYPE]; + if ( !rnode.isString() ) + return false; + rnode >> stageTypeStr; + stageType = !stageTypeStr.compare( CC_BOOST ) ? BOOST : -1; + if (stageType == -1) + return false; + rnode = node[CC_FEATURE_TYPE]; + if ( !rnode.isString() ) + return false; + rnode >> featureTypeStr; + featureType = !featureTypeStr.compare( CC_HAAR ) ? CvFeatureParams::HAAR : + !featureTypeStr.compare( CC_LBP ) ? CvFeatureParams::LBP : + !featureTypeStr.compare( CC_HOG ) ? CvFeatureParams::HOG : + -1; + if (featureType == -1) + return false; + node[CC_HEIGHT] >> winSize.height; + node[CC_WIDTH] >> winSize.width; + return winSize.height > 0 && winSize.width > 0; +} + +void CvCascadeParams::printDefaults() const +{ + CvParams::printDefaults(); + cout << " [-stageType <"; + for( int i = 0; i < (int)(sizeof(stageTypes)/sizeof(stageTypes[0])); i++ ) + { + cout << (i ? " | " : "") << stageTypes[i]; + if ( i == defaultStageType ) + cout << "(default)"; + } + cout << ">]" << endl; + + cout << " [-featureType <{"; + for( int i = 0; i < (int)(sizeof(featureTypes)/sizeof(featureTypes[0])); i++ ) + { + cout << (i ? ", " : "") << featureTypes[i]; + if ( i == defaultStageType ) + cout << "(default)"; + } + cout << "}>]" << endl; + cout << " [-w ]" << endl; + cout << " [-h ]" << endl; +} + +void CvCascadeParams::printAttrs() const +{ + cout << "stageType: " << stageTypes[stageType] << endl; + cout << "featureType: " << featureTypes[featureType] << endl; + cout << "sampleWidth: " << winSize.width << endl; + cout << "sampleHeight: " << winSize.height << endl; +} + +bool CvCascadeParams::scanAttr( const string prmName, const string val ) +{ + bool res = true; + if( !prmName.compare( "-stageType" ) ) + { + for( int i = 0; i < (int)(sizeof(stageTypes)/sizeof(stageTypes[0])); i++ ) + if( !val.compare( stageTypes[i] ) ) + stageType = i; + } + else if( !prmName.compare( "-featureType" ) ) + { + for( int i = 0; i < (int)(sizeof(featureTypes)/sizeof(featureTypes[0])); i++ ) + if( !val.compare( featureTypes[i] ) ) + featureType = i; + } + else if( !prmName.compare( "-w" ) ) + { + winSize.width = atoi( val.c_str() ); + } + else if( !prmName.compare( "-h" ) ) + { + winSize.height = atoi( val.c_str() ); + } + else + res = false; + return res; +} + +//---------------------------- CascadeClassifier -------------------------------------- + +bool CvCascadeClassifier::train( const string _cascadeDirName, + const string _posFilename, + const string _negFilename, + int _numPos, int _numNeg, + int _precalcValBufSize, int _precalcIdxBufSize, + int _numStages, + const CvCascadeParams& _cascadeParams, + const CvFeatureParams& _featureParams, + const CvCascadeBoostParams& _stageParams, + bool baseFormatSave, + double acceptanceRatioBreakValue ) +{ + // Start recording clock ticks for training time output + double time = (double)getTickCount(); + + if( _cascadeDirName.empty() || _posFilename.empty() || _negFilename.empty() ) + CV_Error( CV_StsBadArg, "_cascadeDirName or _bgfileName or _vecFileName is NULL" ); + + string dirName; + if (_cascadeDirName.find_last_of("/\\") == (_cascadeDirName.length() - 1) ) + dirName = _cascadeDirName; + else + dirName = _cascadeDirName + '/'; + + numPos = _numPos; + numNeg = _numNeg; + numStages = _numStages; + if ( !imgReader.create( _posFilename, _negFilename, _cascadeParams.winSize ) ) + { + cout << "Image reader can not be created from -vec " << _posFilename + << " and -bg " << _negFilename << "." << endl; + return false; + } + if ( !load( dirName ) ) + { + cascadeParams = _cascadeParams; + featureParams = CvFeatureParams::create(cascadeParams.featureType); + featureParams->init(_featureParams); + stageParams = makePtr(); + *stageParams = _stageParams; + featureEvaluator = CvFeatureEvaluator::create(cascadeParams.featureType); + featureEvaluator->init( featureParams, numPos + numNeg, cascadeParams.winSize ); + stageClassifiers.reserve( numStages ); + }else{ + // Make sure that if model parameters are preloaded, that people are aware of this, + // even when passing other parameters to the training command + cout << "---------------------------------------------------------------------------------" << endl; + cout << "Training parameters are pre-loaded from the parameter file in data folder!" << endl; + cout << "Please empty this folder if you want to use a NEW set of training parameters." << endl; + cout << "---------------------------------------------------------------------------------" << endl; + } + cout << "PARAMETERS:" << endl; + cout << "cascadeDirName: " << _cascadeDirName << endl; + cout << "vecFileName: " << _posFilename << endl; + cout << "bgFileName: " << _negFilename << endl; + cout << "numPos: " << _numPos << endl; + cout << "numNeg: " << _numNeg << endl; + cout << "numStages: " << numStages << endl; + cout << "precalcValBufSize[Mb] : " << _precalcValBufSize << endl; + cout << "precalcIdxBufSize[Mb] : " << _precalcIdxBufSize << endl; + cout << "acceptanceRatioBreakValue : " << acceptanceRatioBreakValue << endl; + cascadeParams.printAttrs(); + stageParams->printAttrs(); + featureParams->printAttrs(); + cout << "Number of unique features given windowSize [" << _cascadeParams.winSize.width << "," << _cascadeParams.winSize.height << "] : " << featureEvaluator->getNumFeatures() << "" << endl; + + int startNumStages = (int)stageClassifiers.size(); + if ( startNumStages > 1 ) + cout << endl << "Stages 0-" << startNumStages-1 << " are loaded" << endl; + else if ( startNumStages == 1) + cout << endl << "Stage 0 is loaded" << endl; + + double requiredLeafFARate = pow( (double) stageParams->maxFalseAlarm, (double) numStages ) / + (double)stageParams->max_depth; + double tempLeafFARate; + + for( int i = startNumStages; i < numStages; i++ ) + { + cout << endl << "===== TRAINING " << i << "-stage =====" << endl; + cout << "= 0) ){ + cout << "The required acceptanceRatio for the model has been reached to avoid overfitting of trainingdata. " + "Branch training terminated." << endl; + break; + } + + Ptr tempStage = makePtr(); + bool isStageTrained = tempStage->train( featureEvaluator, + curNumSamples, _precalcValBufSize, _precalcIdxBufSize, + *stageParams ); + cout << "END>" << endl; + + if(!isStageTrained) + break; + + stageClassifiers.push_back( tempStage ); + + // save params + if( i == 0) + { + std::string paramsFilename = dirName + CC_PARAMS_FILENAME; + FileStorage fs( paramsFilename, FileStorage::WRITE); + if ( !fs.isOpened() ) + { + cout << "Parameters can not be written, because file " << paramsFilename + << " can not be opened." << endl; + return false; + } + fs << FileStorage::getDefaultObjectName(paramsFilename) << "{"; + writeParams( fs ); + fs << "}"; + } + // save current stage + char buf[10]; + sprintf(buf, "%s%d", "stage", i ); + string stageFilename = dirName + buf + ".xml"; + FileStorage fs( stageFilename, FileStorage::WRITE ); + if ( !fs.isOpened() ) + { + cout << "Current stage can not be written, because file " << stageFilename + << " can not be opened." << endl; + return false; + } + fs << FileStorage::getDefaultObjectName(stageFilename) << "{"; + tempStage->write( fs, Mat() ); + fs << "}"; + + // Output training time up till now + double seconds = ( (double)getTickCount() - time)/ getTickFrequency(); + int days = int(seconds) / 60 / 60 / 24; + int hours = (int(seconds) / 60 / 60) % 24; + int minutes = (int(seconds) / 60) % 60; + int seconds_left = int(seconds) % 60; + cout << "Training until now has taken " << days << " days " << hours << " hours " << minutes << " minutes " << seconds_left <<" seconds." << endl; + } + + if(stageClassifiers.size() == 0) + { + cout << "Cascade classifier can't be trained. Check the used training parameters." << endl; + return false; + } + + save( dirName + CC_CASCADE_FILENAME, baseFormatSave ); + + return true; +} + +int CvCascadeClassifier::predict( int sampleIdx ) +{ + CV_DbgAssert( sampleIdx < numPos + numNeg ); + for (vector< Ptr >::iterator it = stageClassifiers.begin(); + it != stageClassifiers.end();++it ) + { + if ( (*it)->predict( sampleIdx ) == 0.f ) + return 0; + } + return 1; +} + +bool CvCascadeClassifier::updateTrainingSet( double minimumAcceptanceRatio, double& acceptanceRatio) +{ + int64 posConsumed = 0, negConsumed = 0; + imgReader.restart(); + int posCount = fillPassedSamples( 0, numPos, true, 0, posConsumed ); + if( !posCount ) + return false; + cout << "POS count : consumed " << posCount << " : " << (int)posConsumed << endl; + + int proNumNeg = cvRound( ( ((double)numNeg) * ((double)posCount) ) / numPos ); // apply only a fraction of negative samples. double is required since overflow is possible + int negCount = fillPassedSamples( posCount, proNumNeg, false, minimumAcceptanceRatio, negConsumed ); + if ( !negCount ) + if ( !(negConsumed > 0 && ((double)negCount+1)/(double)negConsumed <= minimumAcceptanceRatio) ) + return false; + + curNumSamples = posCount + negCount; + acceptanceRatio = negConsumed == 0 ? 0 : ( (double)negCount/(double)(int64)negConsumed ); + cout << "NEG count : acceptanceRatio " << negCount << " : " << acceptanceRatio << endl; + return true; +} + +int CvCascadeClassifier::fillPassedSamples( int first, int count, bool isPositive, double minimumAcceptanceRatio, int64& consumed ) +{ + int getcount = 0; + Mat img(cascadeParams.winSize, CV_8UC1); + for( int i = first; i < first + count; i++ ) + { + for( ; ; ) + { + if( consumed != 0 && ((double)getcount+1)/(double)(int64)consumed <= minimumAcceptanceRatio ) + return getcount; + + bool isGetImg = isPositive ? imgReader.getPos( img ) : + imgReader.getNeg( img ); + if( !isGetImg ) + return getcount; + consumed++; + + featureEvaluator->setImage( img, isPositive ? 1 : 0, i ); + if( predict( i ) == 1 ) + { + getcount++; + printf("%s current samples: %d\r", isPositive ? "POS":"NEG", getcount); + fflush(stdout); + break; + } + } + } + return getcount; +} + +void CvCascadeClassifier::writeParams( FileStorage &fs ) const +{ + cascadeParams.write( fs ); + fs << CC_STAGE_PARAMS << "{"; stageParams->write( fs ); fs << "}"; + fs << CC_FEATURE_PARAMS << "{"; featureParams->write( fs ); fs << "}"; +} + +void CvCascadeClassifier::writeFeatures( FileStorage &fs, const Mat& featureMap ) const +{ + featureEvaluator->writeFeatures( fs, featureMap ); +} + +void CvCascadeClassifier::writeStages( FileStorage &fs, const Mat& featureMap ) const +{ + char cmnt[30]; + int i = 0; + fs << CC_STAGES << "["; + for( vector< Ptr >::const_iterator it = stageClassifiers.begin(); + it != stageClassifiers.end();++it, ++i ) + { + sprintf( cmnt, "stage %d", i ); + cvWriteComment( fs.fs, cmnt, 0 ); + fs << "{"; + (*it)->write( fs, featureMap ); + fs << "}"; + } + fs << "]"; +} + +bool CvCascadeClassifier::readParams( const FileNode &node ) +{ + if ( !node.isMap() || !cascadeParams.read( node ) ) + return false; + + stageParams = makePtr(); + FileNode rnode = node[CC_STAGE_PARAMS]; + if ( !stageParams->read( rnode ) ) + return false; + + featureParams = CvFeatureParams::create(cascadeParams.featureType); + rnode = node[CC_FEATURE_PARAMS]; + if ( !featureParams->read( rnode ) ) + return false; + return true; +} + +bool CvCascadeClassifier::readStages( const FileNode &node) +{ + FileNode rnode = node[CC_STAGES]; + if (!rnode.empty() || !rnode.isSeq()) + return false; + stageClassifiers.reserve(numStages); + FileNodeIterator it = rnode.begin(); + for( int i = 0; i < min( (int)rnode.size(), numStages ); i++, it++ ) + { + Ptr tempStage = makePtr(); + if ( !tempStage->read( *it, featureEvaluator, *stageParams) ) + return false; + stageClassifiers.push_back(tempStage); + } + return true; +} + +// For old Haar Classifier file saving +#define ICV_HAAR_TYPE_ID "opencv-haar-classifier" +#define ICV_HAAR_SIZE_NAME "size" +#define ICV_HAAR_STAGES_NAME "stages" +#define ICV_HAAR_TREES_NAME "trees" +#define ICV_HAAR_FEATURE_NAME "feature" +#define ICV_HAAR_RECTS_NAME "rects" +#define ICV_HAAR_TILTED_NAME "tilted" +#define ICV_HAAR_THRESHOLD_NAME "threshold" +#define ICV_HAAR_LEFT_NODE_NAME "left_node" +#define ICV_HAAR_LEFT_VAL_NAME "left_val" +#define ICV_HAAR_RIGHT_NODE_NAME "right_node" +#define ICV_HAAR_RIGHT_VAL_NAME "right_val" +#define ICV_HAAR_STAGE_THRESHOLD_NAME "stage_threshold" +#define ICV_HAAR_PARENT_NAME "parent" +#define ICV_HAAR_NEXT_NAME "next" + +void CvCascadeClassifier::save( const string filename, bool baseFormat ) +{ + FileStorage fs( filename, FileStorage::WRITE ); + + if ( !fs.isOpened() ) + return; + + fs << FileStorage::getDefaultObjectName(filename); + if ( !baseFormat ) + { + Mat featureMap; + getUsedFeaturesIdxMap( featureMap ); + fs << "{"; + writeParams( fs ); + fs << CC_STAGE_NUM << (int)stageClassifiers.size(); + writeStages( fs, featureMap ); + writeFeatures( fs, featureMap ); + } + else + { + //char buf[256]; + CvSeq* weak; + if ( cascadeParams.featureType != CvFeatureParams::HAAR ) + CV_Error( CV_StsBadFunc, "old file format is used for Haar-like features only"); + fs << "{:" ICV_HAAR_TYPE_ID; + fs << ICV_HAAR_SIZE_NAME << "[:" << cascadeParams.winSize.width << + cascadeParams.winSize.height << "]"; + fs << ICV_HAAR_STAGES_NAME << "["; + for( size_t si = 0; si < stageClassifiers.size(); si++ ) + { + fs << "{"; //stage + /*sprintf( buf, "stage %d", si ); + CV_CALL( cvWriteComment( fs, buf, 1 ) );*/ + weak = stageClassifiers[si]->get_weak_predictors(); + fs << ICV_HAAR_TREES_NAME << "["; + for( int wi = 0; wi < weak->total; wi++ ) + { + int inner_node_idx = -1, total_inner_node_idx = -1; + queue inner_nodes_queue; + CvCascadeBoostTree* tree = *((CvCascadeBoostTree**) cvGetSeqElem( weak, wi )); + + fs << "["; + /*sprintf( buf, "tree %d", wi ); + CV_CALL( cvWriteComment( fs, buf, 1 ) );*/ + + const CvDTreeNode* tempNode; + + inner_nodes_queue.push( tree->get_root() ); + total_inner_node_idx++; + + while (!inner_nodes_queue.empty()) + { + tempNode = inner_nodes_queue.front(); + inner_node_idx++; + + fs << "{"; + fs << ICV_HAAR_FEATURE_NAME << "{"; + ((CvHaarEvaluator*)featureEvaluator.get())->writeFeature( fs, tempNode->split->var_idx ); + fs << "}"; + + fs << ICV_HAAR_THRESHOLD_NAME << tempNode->split->ord.c; + + if( tempNode->left->left || tempNode->left->right ) + { + inner_nodes_queue.push( tempNode->left ); + total_inner_node_idx++; + fs << ICV_HAAR_LEFT_NODE_NAME << total_inner_node_idx; + } + else + fs << ICV_HAAR_LEFT_VAL_NAME << tempNode->left->value; + + if( tempNode->right->left || tempNode->right->right ) + { + inner_nodes_queue.push( tempNode->right ); + total_inner_node_idx++; + fs << ICV_HAAR_RIGHT_NODE_NAME << total_inner_node_idx; + } + else + fs << ICV_HAAR_RIGHT_VAL_NAME << tempNode->right->value; + fs << "}"; // ICV_HAAR_FEATURE_NAME + inner_nodes_queue.pop(); + } + fs << "]"; + } + fs << "]"; //ICV_HAAR_TREES_NAME + fs << ICV_HAAR_STAGE_THRESHOLD_NAME << stageClassifiers[si]->getThreshold(); + fs << ICV_HAAR_PARENT_NAME << (int)si-1 << ICV_HAAR_NEXT_NAME << -1; + fs << "}"; //stage + } /* for each stage */ + fs << "]"; //ICV_HAAR_STAGES_NAME + } + fs << "}"; +} + +bool CvCascadeClassifier::load( const string cascadeDirName ) +{ + FileStorage fs( cascadeDirName + CC_PARAMS_FILENAME, FileStorage::READ ); + if ( !fs.isOpened() ) + return false; + FileNode node = fs.getFirstTopLevelNode(); + if ( !readParams( node ) ) + return false; + featureEvaluator = CvFeatureEvaluator::create(cascadeParams.featureType); + featureEvaluator->init( featureParams, numPos + numNeg, cascadeParams.winSize ); + fs.release(); + + char buf[16] = {0}; + for ( int si = 0; si < numStages; si++ ) + { + sprintf( buf, "%s%d", "stage", si); + fs.open( cascadeDirName + buf + ".xml", FileStorage::READ ); + node = fs.getFirstTopLevelNode(); + if ( !fs.isOpened() ) + break; + Ptr tempStage = makePtr(); + + if ( !tempStage->read( node, featureEvaluator, *stageParams )) + { + fs.release(); + break; + } + stageClassifiers.push_back(tempStage); + } + return true; +} + +void CvCascadeClassifier::getUsedFeaturesIdxMap( Mat& featureMap ) +{ + int varCount = featureEvaluator->getNumFeatures() * featureEvaluator->getFeatureSize(); + featureMap.create( 1, varCount, CV_32SC1 ); + featureMap.setTo(Scalar(-1)); + + for( vector< Ptr >::const_iterator it = stageClassifiers.begin(); + it != stageClassifiers.end();++it ) + (*it)->markUsedFeaturesInMap( featureMap ); + + for( int fi = 0, idx = 0; fi < varCount; fi++ ) + if ( featureMap.at(0, fi) >= 0 ) + featureMap.ptr(0)[fi] = idx++; +} diff --git a/opencv-apps/traincascade/cascadeclassifier.h b/opencv-apps/traincascade/cascadeclassifier.h new file mode 100644 index 0000000..8f37ee0 --- /dev/null +++ b/opencv-apps/traincascade/cascadeclassifier.h @@ -0,0 +1,125 @@ +#ifndef _OPENCV_CASCADECLASSIFIER_H_ +#define _OPENCV_CASCADECLASSIFIER_H_ + +#include +#include "traincascade_features.h" +#include "haarfeatures.h" +#include "lbpfeatures.h" +#include "HOGfeatures.h" //new +#include "boost.h" + +#define CC_CASCADE_FILENAME "cascade.xml" +#define CC_PARAMS_FILENAME "params.xml" + +#define CC_CASCADE_PARAMS "cascadeParams" +#define CC_STAGE_TYPE "stageType" +#define CC_FEATURE_TYPE "featureType" +#define CC_HEIGHT "height" +#define CC_WIDTH "width" + +#define CC_STAGE_NUM "stageNum" +#define CC_STAGES "stages" +#define CC_STAGE_PARAMS "stageParams" + +#define CC_BOOST "BOOST" +#define CC_BOOST_TYPE "boostType" +#define CC_DISCRETE_BOOST "DAB" +#define CC_REAL_BOOST "RAB" +#define CC_LOGIT_BOOST "LB" +#define CC_GENTLE_BOOST "GAB" +#define CC_MINHITRATE "minHitRate" +#define CC_MAXFALSEALARM "maxFalseAlarm" +#define CC_TRIM_RATE "weightTrimRate" +#define CC_MAX_DEPTH "maxDepth" +#define CC_WEAK_COUNT "maxWeakCount" +#define CC_STAGE_THRESHOLD "stageThreshold" +#define CC_WEAK_CLASSIFIERS "weakClassifiers" +#define CC_INTERNAL_NODES "internalNodes" +#define CC_LEAF_VALUES "leafValues" + +#define CC_FEATURES FEATURES +#define CC_FEATURE_PARAMS "featureParams" +#define CC_MAX_CAT_COUNT "maxCatCount" +#define CC_FEATURE_SIZE "featSize" + +#define CC_HAAR "HAAR" +#define CC_MODE "mode" +#define CC_MODE_BASIC "BASIC" +#define CC_MODE_CORE "CORE" +#define CC_MODE_ALL "ALL" +#define CC_RECTS "rects" +#define CC_TILTED "tilted" + +#define CC_LBP "LBP" +#define CC_RECT "rect" + +#define CC_HOG "HOG" + +#ifdef _WIN32 +#define TIME( arg ) (((double) clock()) / CLOCKS_PER_SEC) +#else +#define TIME( arg ) (time( arg )) +#endif + +class CvCascadeParams : public CvParams +{ +public: + enum { BOOST = 0 }; + static const int defaultStageType = BOOST; + static const int defaultFeatureType = CvFeatureParams::HAAR; + + CvCascadeParams(); + CvCascadeParams( int _stageType, int _featureType ); + void write( cv::FileStorage &fs ) const; + bool read( const cv::FileNode &node ); + + void printDefaults() const; + void printAttrs() const; + bool scanAttr( const std::string prmName, const std::string val ); + + int stageType; + int featureType; + cv::Size winSize; +}; + +class CvCascadeClassifier +{ +public: + bool train( const std::string _cascadeDirName, + const std::string _posFilename, + const std::string _negFilename, + int _numPos, int _numNeg, + int _precalcValBufSize, int _precalcIdxBufSize, + int _numStages, + const CvCascadeParams& _cascadeParams, + const CvFeatureParams& _featureParams, + const CvCascadeBoostParams& _stageParams, + bool baseFormatSave = false, + double acceptanceRatioBreakValue = -1.0 ); +private: + int predict( int sampleIdx ); + void save( const std::string cascadeDirName, bool baseFormat = false ); + bool load( const std::string cascadeDirName ); + bool updateTrainingSet( double minimumAcceptanceRatio, double& acceptanceRatio ); + int fillPassedSamples( int first, int count, bool isPositive, double requiredAcceptanceRatio, int64& consumed ); + + void writeParams( cv::FileStorage &fs ) const; + void writeStages( cv::FileStorage &fs, const cv::Mat& featureMap ) const; + void writeFeatures( cv::FileStorage &fs, const cv::Mat& featureMap ) const; + bool readParams( const cv::FileNode &node ); + bool readStages( const cv::FileNode &node ); + + void getUsedFeaturesIdxMap( cv::Mat& featureMap ); + + CvCascadeParams cascadeParams; + cv::Ptr featureParams; + cv::Ptr stageParams; + + cv::Ptr featureEvaluator; + std::vector< cv::Ptr > stageClassifiers; + CvCascadeImageReader imgReader; + int numStages, curNumSamples; + int numPos, numNeg; +}; + +#endif diff --git a/opencv-apps/traincascade/features.cpp b/opencv-apps/traincascade/features.cpp new file mode 100644 index 0000000..09e08ac --- /dev/null +++ b/opencv-apps/traincascade/features.cpp @@ -0,0 +1,93 @@ +#include "opencv2/core.hpp" + +#include "traincascade_features.h" +#include "cascadeclassifier.h" + +using namespace std; +using namespace cv; + +float calcNormFactor( const Mat& sum, const Mat& sqSum ) +{ + CV_DbgAssert( sum.cols > 3 && sqSum.rows > 3 ); + Rect normrect( 1, 1, sum.cols - 3, sum.rows - 3 ); + size_t p0, p1, p2, p3; + CV_SUM_OFFSETS( p0, p1, p2, p3, normrect, sum.step1() ) + double area = normrect.width * normrect.height; + const int *sp = sum.ptr(); + int valSum = sp[p0] - sp[p1] - sp[p2] + sp[p3]; + const double *sqp = sqSum.ptr(); + double valSqSum = sqp[p0] - sqp[p1] - sqp[p2] + sqp[p3]; + return (float) sqrt( (double) (area * valSqSum - (double)valSum * valSum) ); +} + +CvParams::CvParams() : name( "params" ) {} +void CvParams::printDefaults() const +{ cout << "--" << name << "--" << endl; } +void CvParams::printAttrs() const {} +bool CvParams::scanAttr( const string, const string ) { return false; } + + +//---------------------------- FeatureParams -------------------------------------- + +CvFeatureParams::CvFeatureParams() : maxCatCount( 0 ), featSize( 1 ) +{ + name = CC_FEATURE_PARAMS; +} + +void CvFeatureParams::init( const CvFeatureParams& fp ) +{ + maxCatCount = fp.maxCatCount; + featSize = fp.featSize; +} + +void CvFeatureParams::write( FileStorage &fs ) const +{ + fs << CC_MAX_CAT_COUNT << maxCatCount; + fs << CC_FEATURE_SIZE << featSize; +} + +bool CvFeatureParams::read( const FileNode &node ) +{ + if ( node.empty() ) + return false; + maxCatCount = node[CC_MAX_CAT_COUNT]; + featSize = node[CC_FEATURE_SIZE]; + return ( maxCatCount >= 0 && featSize >= 1 ); +} + +Ptr CvFeatureParams::create( int featureType ) +{ + return featureType == HAAR ? Ptr(new CvHaarFeatureParams) : + featureType == LBP ? Ptr(new CvLBPFeatureParams) : + featureType == HOG ? Ptr(new CvHOGFeatureParams) : + Ptr(); +} + +//------------------------------------- FeatureEvaluator --------------------------------------- + +void CvFeatureEvaluator::init(const CvFeatureParams *_featureParams, + int _maxSampleCount, Size _winSize ) +{ + CV_Assert(_maxSampleCount > 0); + featureParams = (CvFeatureParams *)_featureParams; + winSize = _winSize; + numFeatures = 0; + cls.create( (int)_maxSampleCount, 1, CV_32FC1 ); + generateFeatures(); +} + +void CvFeatureEvaluator::setImage(const Mat &img, uchar clsLabel, int idx) +{ + CV_Assert(img.cols == winSize.width); + CV_Assert(img.rows == winSize.height); + CV_Assert(idx < cls.rows); + cls.ptr(idx)[0] = clsLabel; +} + +Ptr CvFeatureEvaluator::create(int type) +{ + return type == CvFeatureParams::HAAR ? Ptr(new CvHaarEvaluator) : + type == CvFeatureParams::LBP ? Ptr(new CvLBPEvaluator) : + type == CvFeatureParams::HOG ? Ptr(new CvHOGEvaluator) : + Ptr(); +} diff --git a/opencv-apps/traincascade/haarfeatures.cpp b/opencv-apps/traincascade/haarfeatures.cpp new file mode 100644 index 0000000..c151ee7 --- /dev/null +++ b/opencv-apps/traincascade/haarfeatures.cpp @@ -0,0 +1,312 @@ +#include "opencv2/core.hpp" +#include "opencv2/imgproc.hpp" + +#include "haarfeatures.h" +#include "cascadeclassifier.h" + +using namespace std; +using namespace cv; + +CvHaarFeatureParams::CvHaarFeatureParams() : mode(BASIC) +{ + name = HFP_NAME; +} + +CvHaarFeatureParams::CvHaarFeatureParams( int _mode ) : mode( _mode ) +{ + name = HFP_NAME; +} + +void CvHaarFeatureParams::init( const CvFeatureParams& fp ) +{ + CvFeatureParams::init( fp ); + mode = ((const CvHaarFeatureParams&)fp).mode; +} + +void CvHaarFeatureParams::write( FileStorage &fs ) const +{ + CvFeatureParams::write( fs ); + string modeStr = mode == BASIC ? CC_MODE_BASIC : + mode == CORE ? CC_MODE_CORE : + mode == ALL ? CC_MODE_ALL : string(); + CV_Assert( !modeStr.empty() ); + fs << CC_MODE << modeStr; +} + +bool CvHaarFeatureParams::read( const FileNode &node ) +{ + if( !CvFeatureParams::read( node ) ) + return false; + + FileNode rnode = node[CC_MODE]; + if( !rnode.isString() ) + return false; + string modeStr; + rnode >> modeStr; + mode = !modeStr.compare( CC_MODE_BASIC ) ? BASIC : + !modeStr.compare( CC_MODE_CORE ) ? CORE : + !modeStr.compare( CC_MODE_ALL ) ? ALL : -1; + return (mode >= 0); +} + +void CvHaarFeatureParams::printDefaults() const +{ + CvFeatureParams::printDefaults(); + cout << " [-mode <" CC_MODE_BASIC << "(default) | " + << CC_MODE_CORE <<" | " << CC_MODE_ALL << endl; +} + +void CvHaarFeatureParams::printAttrs() const +{ + CvFeatureParams::printAttrs(); + string mode_str = mode == BASIC ? CC_MODE_BASIC : + mode == CORE ? CC_MODE_CORE : + mode == ALL ? CC_MODE_ALL : 0; + cout << "mode: " << mode_str << endl; +} + +bool CvHaarFeatureParams::scanAttr( const string prmName, const string val) +{ + if ( !CvFeatureParams::scanAttr( prmName, val ) ) + { + if( !prmName.compare("-mode") ) + { + mode = !val.compare( CC_MODE_CORE ) ? CORE : + !val.compare( CC_MODE_ALL ) ? ALL : + !val.compare( CC_MODE_BASIC ) ? BASIC : -1; + if (mode == -1) + return false; + } + return false; + } + return true; +} + +//--------------------- HaarFeatureEvaluator ---------------- + +void CvHaarEvaluator::init(const CvFeatureParams *_featureParams, + int _maxSampleCount, Size _winSize ) +{ + CV_Assert(_maxSampleCount > 0); + int cols = (_winSize.width + 1) * (_winSize.height + 1); + sum.create((int)_maxSampleCount, cols, CV_32SC1); + tilted.create((int)_maxSampleCount, cols, CV_32SC1); + normfactor.create(1, (int)_maxSampleCount, CV_32FC1); + CvFeatureEvaluator::init( _featureParams, _maxSampleCount, _winSize ); +} + +void CvHaarEvaluator::setImage(const Mat& img, uchar clsLabel, int idx) +{ + CV_DbgAssert( !sum.empty() && !tilted.empty() && !normfactor.empty() ); + CvFeatureEvaluator::setImage( img, clsLabel, idx); + Mat innSum(winSize.height + 1, winSize.width + 1, sum.type(), sum.ptr((int)idx)); + Mat innSqSum; + if (((const CvHaarFeatureParams*)featureParams)->mode == CvHaarFeatureParams::ALL) + { + Mat innTilted(winSize.height + 1, winSize.width + 1, tilted.type(), tilted.ptr((int)idx)); + integral(img, innSum, innSqSum, innTilted); + } + else + integral(img, innSum, innSqSum); + normfactor.ptr(0)[idx] = calcNormFactor( innSum, innSqSum ); +} + +void CvHaarEvaluator::writeFeatures( FileStorage &fs, const Mat& featureMap ) const +{ + _writeFeatures( features, fs, featureMap ); +} + +void CvHaarEvaluator::writeFeature(FileStorage &fs, int fi) const +{ + CV_DbgAssert( fi < (int)features.size() ); + features[fi].write(fs); +} + +void CvHaarEvaluator::generateFeatures() +{ + int mode = ((const CvHaarFeatureParams*)((CvFeatureParams*)featureParams))->mode; + int offset = winSize.width + 1; + for( int x = 0; x < winSize.width; x++ ) + { + for( int y = 0; y < winSize.height; y++ ) + { + for( int dx = 1; dx <= winSize.width; dx++ ) + { + for( int dy = 1; dy <= winSize.height; dy++ ) + { + // haar_x2 + if ( (x+dx*2 <= winSize.width) && (y+dy <= winSize.height) ) + { + features.push_back( Feature( offset, false, + x, y, dx*2, dy, -1, + x+dx, y, dx , dy, +2 ) ); + } + // haar_y2 + if ( (x+dx <= winSize.width) && (y+dy*2 <= winSize.height) ) + { + features.push_back( Feature( offset, false, + x, y, dx, dy*2, -1, + x, y+dy, dx, dy, +2 ) ); + } + // haar_x3 + if ( (x+dx*3 <= winSize.width) && (y+dy <= winSize.height) ) + { + features.push_back( Feature( offset, false, + x, y, dx*3, dy, -1, + x+dx, y, dx , dy, +2 ) ); + } + // haar_y3 + if ( (x+dx <= winSize.width) && (y+dy*3 <= winSize.height) ) + { + features.push_back( Feature( offset, false, + x, y, dx, dy*3, -1, + x, y+dy, dx, dy, +2 ) ); + } + if( mode != CvHaarFeatureParams::BASIC ) + { + // haar_x4 + if ( (x+dx*4 <= winSize.width) && (y+dy <= winSize.height) ) + { + features.push_back( Feature( offset, false, + x, y, dx*4, dy, -1, + x+dx, y, dx*2, dy, +2 ) ); + } + // haar_y4 + if ( (x+dx <= winSize.width ) && (y+dy*4 <= winSize.height) ) + { + features.push_back( Feature( offset, false, + x, y, dx, dy*4, -1, + x, y+dy, dx, dy*2, +2 ) ); + } + } + // x2_y2 + if ( (x+dx*2 <= winSize.width) && (y+dy*2 <= winSize.height) ) + { + features.push_back( Feature( offset, false, + x, y, dx*2, dy*2, -1, + x, y, dx, dy, +2, + x+dx, y+dy, dx, dy, +2 ) ); + } + if (mode != CvHaarFeatureParams::BASIC) + { + if ( (x+dx*3 <= winSize.width) && (y+dy*3 <= winSize.height) ) + { + features.push_back( Feature( offset, false, + x , y , dx*3, dy*3, -1, + x+dx, y+dy, dx , dy , +9) ); + } + } + if (mode == CvHaarFeatureParams::ALL) + { + // tilted haar_x2 + if ( (x+2*dx <= winSize.width) && (y+2*dx+dy <= winSize.height) && (x-dy>= 0) ) + { + features.push_back( Feature( offset, true, + x, y, dx*2, dy, -1, + x, y, dx, dy, +2 ) ); + } + // tilted haar_y2 + if ( (x+dx <= winSize.width) && (y+dx+2*dy <= winSize.height) && (x-2*dy>= 0) ) + { + features.push_back( Feature( offset, true, + x, y, dx, 2*dy, -1, + x, y, dx, dy, +2 ) ); + } + // tilted haar_x3 + if ( (x+3*dx <= winSize.width) && (y+3*dx+dy <= winSize.height) && (x-dy>= 0) ) + { + features.push_back( Feature( offset, true, + x, y, dx*3, dy, -1, + x+dx, y+dx, dx, dy, +3 ) ); + } + // tilted haar_y3 + if ( (x+dx <= winSize.width) && (y+dx+3*dy <= winSize.height) && (x-3*dy>= 0) ) + { + features.push_back( Feature( offset, true, + x, y, dx, 3*dy, -1, + x-dy, y+dy, dx, dy, +3 ) ); + } + // tilted haar_x4 + if ( (x+4*dx <= winSize.width) && (y+4*dx+dy <= winSize.height) && (x-dy>= 0) ) + { + features.push_back( Feature( offset, true, + x, y, dx*4, dy, -1, + x+dx, y+dx, dx*2, dy, +2 ) ); + } + // tilted haar_y4 + if ( (x+dx <= winSize.width) && (y+dx+4*dy <= winSize.height) && (x-4*dy>= 0) ) + { + features.push_back( Feature( offset, true, + x, y, dx, 4*dy, -1, + x-dy, y+dy, dx, 2*dy, +2 ) ); + } + } + } + } + } + } + numFeatures = (int)features.size(); +} + +CvHaarEvaluator::Feature::Feature() +{ + tilted = false; + rect[0].r = rect[1].r = rect[2].r = Rect(0,0,0,0); + rect[0].weight = rect[1].weight = rect[2].weight = 0; +} + +CvHaarEvaluator::Feature::Feature( int offset, bool _tilted, + int x0, int y0, int w0, int h0, float wt0, + int x1, int y1, int w1, int h1, float wt1, + int x2, int y2, int w2, int h2, float wt2 ) +{ + tilted = _tilted; + + rect[0].r.x = x0; + rect[0].r.y = y0; + rect[0].r.width = w0; + rect[0].r.height = h0; + rect[0].weight = wt0; + + rect[1].r.x = x1; + rect[1].r.y = y1; + rect[1].r.width = w1; + rect[1].r.height = h1; + rect[1].weight = wt1; + + rect[2].r.x = x2; + rect[2].r.y = y2; + rect[2].r.width = w2; + rect[2].r.height = h2; + rect[2].weight = wt2; + + if( !tilted ) + { + for( int j = 0; j < CV_HAAR_FEATURE_MAX; j++ ) + { + if( rect[j].weight == 0.0F ) + break; + CV_SUM_OFFSETS( fastRect[j].p0, fastRect[j].p1, fastRect[j].p2, fastRect[j].p3, rect[j].r, offset ) + } + } + else + { + for( int j = 0; j < CV_HAAR_FEATURE_MAX; j++ ) + { + if( rect[j].weight == 0.0F ) + break; + CV_TILTED_OFFSETS( fastRect[j].p0, fastRect[j].p1, fastRect[j].p2, fastRect[j].p3, rect[j].r, offset ) + } + } +} + +void CvHaarEvaluator::Feature::write( FileStorage &fs ) const +{ + fs << CC_RECTS << "["; + for( int ri = 0; ri < CV_HAAR_FEATURE_MAX && rect[ri].r.width != 0; ++ri ) + { + fs << "[:" << rect[ri].r.x << rect[ri].r.y << + rect[ri].r.width << rect[ri].r.height << rect[ri].weight << "]"; + } + fs << "]" << CC_TILTED << tilted; +} diff --git a/opencv-apps/traincascade/haarfeatures.h b/opencv-apps/traincascade/haarfeatures.h new file mode 100644 index 0000000..0894d09 --- /dev/null +++ b/opencv-apps/traincascade/haarfeatures.h @@ -0,0 +1,89 @@ +#ifndef _OPENCV_HAARFEATURES_H_ +#define _OPENCV_HAARFEATURES_H_ + +#include "traincascade_features.h" + +#define CV_HAAR_FEATURE_MAX 3 + +#define HFP_NAME "haarFeatureParams" +class CvHaarFeatureParams : public CvFeatureParams +{ +public: + enum { BASIC = 0, CORE = 1, ALL = 2 }; + /* 0 - BASIC = Viola + * 1 - CORE = All upright + * 2 - ALL = All features */ + + CvHaarFeatureParams(); + CvHaarFeatureParams( int _mode ); + + virtual void init( const CvFeatureParams& fp ); + virtual void write( cv::FileStorage &fs ) const; + virtual bool read( const cv::FileNode &node ); + + virtual void printDefaults() const; + virtual void printAttrs() const; + virtual bool scanAttr( const std::string prm, const std::string val); + + int mode; +}; + +class CvHaarEvaluator : public CvFeatureEvaluator +{ +public: + virtual void init(const CvFeatureParams *_featureParams, + int _maxSampleCount, cv::Size _winSize ); + virtual void setImage(const cv::Mat& img, uchar clsLabel, int idx); + virtual float operator()(int featureIdx, int sampleIdx) const; + virtual void writeFeatures( cv::FileStorage &fs, const cv::Mat& featureMap ) const; + void writeFeature( cv::FileStorage &fs, int fi ) const; // for old file fornat +protected: + virtual void generateFeatures(); + + class Feature + { + public: + Feature(); + Feature( int offset, bool _tilted, + int x0, int y0, int w0, int h0, float wt0, + int x1, int y1, int w1, int h1, float wt1, + int x2 = 0, int y2 = 0, int w2 = 0, int h2 = 0, float wt2 = 0.0F ); + float calc( const cv::Mat &sum, const cv::Mat &tilted, size_t y) const; + void write( cv::FileStorage &fs ) const; + + bool tilted; + struct + { + cv::Rect r; + float weight; + } rect[CV_HAAR_FEATURE_MAX]; + + struct + { + int p0, p1, p2, p3; + } fastRect[CV_HAAR_FEATURE_MAX]; + }; + + std::vector features; + cv::Mat sum; /* sum images (each row represents image) */ + cv::Mat tilted; /* tilted sum images (each row represents image) */ + cv::Mat normfactor; /* normalization factor */ +}; + +inline float CvHaarEvaluator::operator()(int featureIdx, int sampleIdx) const +{ + float nf = normfactor.at(0, sampleIdx); + return !nf ? 0.0f : (features[featureIdx].calc( sum, tilted, sampleIdx)/nf); +} + +inline float CvHaarEvaluator::Feature::calc( const cv::Mat &_sum, const cv::Mat &_tilted, size_t y) const +{ + const int* img = tilted ? _tilted.ptr((int)y) : _sum.ptr((int)y); + float ret = rect[0].weight * (img[fastRect[0].p0] - img[fastRect[0].p1] - img[fastRect[0].p2] + img[fastRect[0].p3] ) + + rect[1].weight * (img[fastRect[1].p0] - img[fastRect[1].p1] - img[fastRect[1].p2] + img[fastRect[1].p3] ); + if( rect[2].weight != 0.0f ) + ret += rect[2].weight * (img[fastRect[2].p0] - img[fastRect[2].p1] - img[fastRect[2].p2] + img[fastRect[2].p3] ); + return ret; +} + +#endif diff --git a/opencv-apps/traincascade/imagestorage.cpp b/opencv-apps/traincascade/imagestorage.cpp new file mode 100644 index 0000000..a133ccc --- /dev/null +++ b/opencv-apps/traincascade/imagestorage.cpp @@ -0,0 +1,186 @@ +#include "opencv2/core.hpp" +#include "opencv2/core/core_c.h" +#include "opencv2/imgproc.hpp" +#include "opencv2/imgcodecs.hpp" + +#include "imagestorage.h" +#include +#include +#include + +using namespace std; +using namespace cv; + +bool CvCascadeImageReader::create( const string _posFilename, const string _negFilename, Size _winSize ) +{ + return posReader.create(_posFilename) && negReader.create(_negFilename, _winSize); +} + +CvCascadeImageReader::NegReader::NegReader() +{ + src.create( 0, 0 , CV_8UC1 ); + img.create( 0, 0, CV_8UC1 ); + point = offset = Point( 0, 0 ); + scale = 1.0F; + scaleFactor = 1.4142135623730950488016887242097F; + stepFactor = 0.5F; +} + +bool CvCascadeImageReader::NegReader::create( const string _filename, Size _winSize ) +{ + string str; + std::ifstream file(_filename.c_str()); + if ( !file.is_open() ) + return false; + + while( !file.eof() ) + { + std::getline(file, str); + str.erase(str.find_last_not_of(" \n\r\t")+1); + if (str.empty()) break; + if (str.at(0) == '#' ) continue; /* comment */ + imgFilenames.push_back(str); + } + file.close(); + + winSize = _winSize; + last = round = 0; + return true; +} + +bool CvCascadeImageReader::NegReader::nextImg() +{ + Point _offset = Point(0,0); + size_t count = imgFilenames.size(); + for( size_t i = 0; i < count; i++ ) + { + src = imread( imgFilenames[last++], 0 ); + if( src.empty() ){ + last %= count; + continue; + } + round += last / count; + round = round % (winSize.width * winSize.height); + last %= count; + + _offset.x = std::min( (int)round % winSize.width, src.cols - winSize.width ); + _offset.y = std::min( (int)round / winSize.width, src.rows - winSize.height ); + if( !src.empty() && src.type() == CV_8UC1 + && _offset.x >= 0 && _offset.y >= 0 ) + break; + } + + if( src.empty() ) + return false; // no appropriate image + point = offset = _offset; + scale = max( ((float)winSize.width + point.x) / ((float)src.cols), + ((float)winSize.height + point.y) / ((float)src.rows) ); + + Size sz( (int)(scale*src.cols + 0.5F), (int)(scale*src.rows + 0.5F) ); + resize( src, img, sz, 0, 0, INTER_LINEAR_EXACT ); + return true; +} + +bool CvCascadeImageReader::NegReader::get( Mat& _img ) +{ + CV_Assert( !_img.empty() ); + CV_Assert( _img.type() == CV_8UC1 ); + CV_Assert( _img.cols == winSize.width ); + CV_Assert( _img.rows == winSize.height ); + + if( img.empty() ) + if ( !nextImg() ) + return false; + + Mat mat( winSize.height, winSize.width, CV_8UC1, + (void*)(img.ptr(point.y) + point.x * img.elemSize()), img.step ); + mat.copyTo(_img); + + if( (int)( point.x + (1.0F + stepFactor ) * winSize.width ) < img.cols ) + point.x += (int)(stepFactor * winSize.width); + else + { + point.x = offset.x; + if( (int)( point.y + (1.0F + stepFactor ) * winSize.height ) < img.rows ) + point.y += (int)(stepFactor * winSize.height); + else + { + point.y = offset.y; + scale *= scaleFactor; + if( scale <= 1.0F ) + resize( src, img, Size( (int)(scale*src.cols), (int)(scale*src.rows) ), 0, 0, INTER_LINEAR_EXACT ); + else + { + if ( !nextImg() ) + return false; + } + } + } + return true; +} + +CvCascadeImageReader::PosReader::PosReader() +{ + file = 0; + vec = 0; +} + +bool CvCascadeImageReader::PosReader::create( const string _filename ) +{ + if ( file ) + fclose( file ); + file = fopen( _filename.c_str(), "rb" ); + + if( !file ) + return false; + short tmp = 0; + if( fread( &count, sizeof( count ), 1, file ) != 1 || + fread( &vecSize, sizeof( vecSize ), 1, file ) != 1 || + fread( &tmp, sizeof( tmp ), 1, file ) != 1 || + fread( &tmp, sizeof( tmp ), 1, file ) != 1 ) + CV_Error_( CV_StsParseError, ("wrong file format for %s\n", _filename.c_str()) ); + base = sizeof( count ) + sizeof( vecSize ) + 2*sizeof( tmp ); + if( feof( file ) ) + return false; + last = 0; + vec = (short*) cvAlloc( sizeof( *vec ) * vecSize ); + CV_Assert( vec ); + return true; +} + +bool CvCascadeImageReader::PosReader::get( Mat &_img ) +{ + CV_Assert( _img.rows * _img.cols == vecSize ); + uchar tmp = 0; + size_t elements_read = fread( &tmp, sizeof( tmp ), 1, file ); + if( elements_read != 1 ) + CV_Error( CV_StsBadArg, "Can not get new positive sample. The most possible reason is " + "insufficient count of samples in given vec-file.\n"); + elements_read = fread( vec, sizeof( vec[0] ), vecSize, file ); + if( elements_read != (size_t)(vecSize) ) + CV_Error( CV_StsBadArg, "Can not get new positive sample. Seems that vec-file has incorrect structure.\n"); + + if( feof( file ) || last++ >= count ) + CV_Error( CV_StsBadArg, "Can not get new positive sample. vec-file is over.\n"); + + for( int r = 0; r < _img.rows; r++ ) + { + for( int c = 0; c < _img.cols; c++ ) + _img.ptr(r)[c] = (uchar)vec[r * _img.cols + c]; + } + return true; +} + +void CvCascadeImageReader::PosReader::restart() +{ + CV_Assert( file ); + last = 0; + fseek( file, base, SEEK_SET ); +} + +CvCascadeImageReader::PosReader::~PosReader() +{ + if (file) + fclose( file ); + cvFree( &vec ); +} diff --git a/opencv-apps/traincascade/imagestorage.h b/opencv-apps/traincascade/imagestorage.h new file mode 100644 index 0000000..38ca52a --- /dev/null +++ b/opencv-apps/traincascade/imagestorage.h @@ -0,0 +1,50 @@ +#ifndef _OPENCV_IMAGESTORAGE_H_ +#define _OPENCV_IMAGESTORAGE_H_ + + +class CvCascadeImageReader +{ +public: + bool create( const std::string _posFilename, const std::string _negFilename, cv::Size _winSize ); + void restart() { posReader.restart(); } + bool getNeg(cv::Mat &_img) { return negReader.get( _img ); } + bool getPos(cv::Mat &_img) { return posReader.get( _img ); } + +private: + class PosReader + { + public: + PosReader(); + virtual ~PosReader(); + bool create( const std::string _filename ); + bool get( cv::Mat &_img ); + void restart(); + + short* vec; + FILE* file; + int count; + int vecSize; + int last; + int base; + } posReader; + + class NegReader + { + public: + NegReader(); + bool create( const std::string _filename, cv::Size _winSize ); + bool get( cv::Mat& _img ); + bool nextImg(); + + cv::Mat src, img; + std::vector imgFilenames; + cv::Point offset, point; + float scale; + float scaleFactor; + float stepFactor; + size_t last, round; + cv::Size winSize; + } negReader; +}; + +#endif diff --git a/opencv-apps/traincascade/lbpfeatures.cpp b/opencv-apps/traincascade/lbpfeatures.cpp new file mode 100644 index 0000000..e99c6b2 --- /dev/null +++ b/opencv-apps/traincascade/lbpfeatures.cpp @@ -0,0 +1,67 @@ +#include "opencv2/core.hpp" +#include "opencv2/imgproc.hpp" + +#include "lbpfeatures.h" +#include "cascadeclassifier.h" + +using namespace cv; + +CvLBPFeatureParams::CvLBPFeatureParams() +{ + maxCatCount = 256; + name = LBPF_NAME; +} + +void CvLBPEvaluator::init(const CvFeatureParams *_featureParams, int _maxSampleCount, Size _winSize) +{ + CV_Assert( _maxSampleCount > 0); + sum.create((int)_maxSampleCount, (_winSize.width + 1) * (_winSize.height + 1), CV_32SC1); + CvFeatureEvaluator::init( _featureParams, _maxSampleCount, _winSize ); +} + +void CvLBPEvaluator::setImage(const Mat &img, uchar clsLabel, int idx) +{ + CV_DbgAssert( !sum.empty() ); + CvFeatureEvaluator::setImage( img, clsLabel, idx ); + Mat innSum(winSize.height + 1, winSize.width + 1, sum.type(), sum.ptr((int)idx)); + integral( img, innSum ); +} + +void CvLBPEvaluator::writeFeatures( FileStorage &fs, const Mat& featureMap ) const +{ + _writeFeatures( features, fs, featureMap ); +} + +void CvLBPEvaluator::generateFeatures() +{ + int offset = winSize.width + 1; + for( int x = 0; x < winSize.width; x++ ) + for( int y = 0; y < winSize.height; y++ ) + for( int w = 1; w <= winSize.width / 3; w++ ) + for( int h = 1; h <= winSize.height / 3; h++ ) + if ( (x+3*w <= winSize.width) && (y+3*h <= winSize.height) ) + features.push_back( Feature(offset, x, y, w, h ) ); + numFeatures = (int)features.size(); +} + +CvLBPEvaluator::Feature::Feature() +{ + rect = cvRect(0, 0, 0, 0); +} + +CvLBPEvaluator::Feature::Feature( int offset, int x, int y, int _blockWidth, int _blockHeight ) +{ + Rect tr = rect = cvRect(x, y, _blockWidth, _blockHeight); + CV_SUM_OFFSETS( p[0], p[1], p[4], p[5], tr, offset ) + tr.x += 2*rect.width; + CV_SUM_OFFSETS( p[2], p[3], p[6], p[7], tr, offset ) + tr.y +=2*rect.height; + CV_SUM_OFFSETS( p[10], p[11], p[14], p[15], tr, offset ) + tr.x -= 2*rect.width; + CV_SUM_OFFSETS( p[8], p[9], p[12], p[13], tr, offset ) +} + +void CvLBPEvaluator::Feature::write(FileStorage &fs) const +{ + fs << CC_RECT << "[:" << rect.x << rect.y << rect.width << rect.height << "]"; +} diff --git a/opencv-apps/traincascade/lbpfeatures.h b/opencv-apps/traincascade/lbpfeatures.h new file mode 100644 index 0000000..3e36a58 --- /dev/null +++ b/opencv-apps/traincascade/lbpfeatures.h @@ -0,0 +1,57 @@ +#ifndef _OPENCV_LBPFEATURES_H_ +#define _OPENCV_LBPFEATURES_H_ + +#include "traincascade_features.h" + +#define LBPF_NAME "lbpFeatureParams" +struct CvLBPFeatureParams : CvFeatureParams +{ + CvLBPFeatureParams(); + +}; + +class CvLBPEvaluator : public CvFeatureEvaluator +{ +public: + virtual ~CvLBPEvaluator() {} + virtual void init(const CvFeatureParams *_featureParams, + int _maxSampleCount, cv::Size _winSize ); + virtual void setImage(const cv::Mat& img, uchar clsLabel, int idx); + virtual float operator()(int featureIdx, int sampleIdx) const + { return (float)features[featureIdx].calc( sum, sampleIdx); } + virtual void writeFeatures( cv::FileStorage &fs, const cv::Mat& featureMap ) const; +protected: + virtual void generateFeatures(); + + class Feature + { + public: + Feature(); + Feature( int offset, int x, int y, int _block_w, int _block_h ); + uchar calc( const cv::Mat& _sum, size_t y ) const; + void write( cv::FileStorage &fs ) const; + + cv::Rect rect; + int p[16]; + }; + std::vector features; + + cv::Mat sum; +}; + +inline uchar CvLBPEvaluator::Feature::calc(const cv::Mat &_sum, size_t y) const +{ + const int* psum = _sum.ptr((int)y); + int cval = psum[p[5]] - psum[p[6]] - psum[p[9]] + psum[p[10]]; + + return (uchar)((psum[p[0]] - psum[p[1]] - psum[p[4]] + psum[p[5]] >= cval ? 128 : 0) | // 0 + (psum[p[1]] - psum[p[2]] - psum[p[5]] + psum[p[6]] >= cval ? 64 : 0) | // 1 + (psum[p[2]] - psum[p[3]] - psum[p[6]] + psum[p[7]] >= cval ? 32 : 0) | // 2 + (psum[p[6]] - psum[p[7]] - psum[p[10]] + psum[p[11]] >= cval ? 16 : 0) | // 5 + (psum[p[10]] - psum[p[11]] - psum[p[14]] + psum[p[15]] >= cval ? 8 : 0) | // 8 + (psum[p[9]] - psum[p[10]] - psum[p[13]] + psum[p[14]] >= cval ? 4 : 0) | // 7 + (psum[p[8]] - psum[p[9]] - psum[p[12]] + psum[p[13]] >= cval ? 2 : 0) | // 6 + (psum[p[4]] - psum[p[5]] - psum[p[8]] + psum[p[9]] >= cval ? 1 : 0)); // 3 +} + +#endif diff --git a/opencv-apps/traincascade/old_ml.hpp b/opencv-apps/traincascade/old_ml.hpp new file mode 100644 index 0000000..d831fc0 --- /dev/null +++ b/opencv-apps/traincascade/old_ml.hpp @@ -0,0 +1,2043 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#ifndef OPENCV_OLD_ML_HPP +#define OPENCV_OLD_ML_HPP + +#ifdef __cplusplus +# include "opencv2/core.hpp" +#endif + +#include "opencv2/core/core_c.h" +#include + +#ifdef __cplusplus + +#include +#include + +// Apple defines a check() macro somewhere in the debug headers +// that interferes with a method definition in this header +#undef check + +/****************************************************************************************\ +* Main struct definitions * +\****************************************************************************************/ + +/* log(2*PI) */ +#define CV_LOG2PI (1.8378770664093454835606594728112) + +/* columns of matrix are training samples */ +#define CV_COL_SAMPLE 0 + +/* rows of matrix are training samples */ +#define CV_ROW_SAMPLE 1 + +#define CV_IS_ROW_SAMPLE(flags) ((flags) & CV_ROW_SAMPLE) + +struct CvVectors +{ + int type; + int dims, count; + CvVectors* next; + union + { + uchar** ptr; + float** fl; + double** db; + } data; +}; + +#if 0 +/* A structure, representing the lattice range of statmodel parameters. + It is used for optimizing statmodel parameters by cross-validation method. + The lattice is logarithmic, so must be greater than 1. */ +typedef struct CvParamLattice +{ + double min_val; + double max_val; + double step; +} +CvParamLattice; + +CV_INLINE CvParamLattice cvParamLattice( double min_val, double max_val, + double log_step ) +{ + CvParamLattice pl; + pl.min_val = MIN( min_val, max_val ); + pl.max_val = MAX( min_val, max_val ); + pl.step = MAX( log_step, 1. ); + return pl; +} + +CV_INLINE CvParamLattice cvDefaultParamLattice( void ) +{ + CvParamLattice pl = {0,0,0}; + return pl; +} +#endif + +/* Variable type */ +#define CV_VAR_NUMERICAL 0 +#define CV_VAR_ORDERED 0 +#define CV_VAR_CATEGORICAL 1 + +#define CV_TYPE_NAME_ML_SVM "opencv-ml-svm" +#define CV_TYPE_NAME_ML_KNN "opencv-ml-knn" +#define CV_TYPE_NAME_ML_NBAYES "opencv-ml-bayesian" +#define CV_TYPE_NAME_ML_BOOSTING "opencv-ml-boost-tree" +#define CV_TYPE_NAME_ML_TREE "opencv-ml-tree" +#define CV_TYPE_NAME_ML_ANN_MLP "opencv-ml-ann-mlp" +#define CV_TYPE_NAME_ML_CNN "opencv-ml-cnn" +#define CV_TYPE_NAME_ML_RTREES "opencv-ml-random-trees" +#define CV_TYPE_NAME_ML_ERTREES "opencv-ml-extremely-randomized-trees" +#define CV_TYPE_NAME_ML_GBT "opencv-ml-gradient-boosting-trees" + +#define CV_TRAIN_ERROR 0 +#define CV_TEST_ERROR 1 + +class CvStatModel +{ +public: + CvStatModel(); + virtual ~CvStatModel(); + + virtual void clear(); + + CV_WRAP virtual void save( const char* filename, const char* name=0 ) const; + CV_WRAP virtual void load( const char* filename, const char* name=0 ); + + virtual void write( cv::FileStorage& storage, const char* name ) const; + virtual void read( const cv::FileNode& node ); + +protected: + const char* default_model_name; +}; + +/****************************************************************************************\ +* Normal Bayes Classifier * +\****************************************************************************************/ + +/* The structure, representing the grid range of statmodel parameters. + It is used for optimizing statmodel accuracy by varying model parameters, + the accuracy estimate being computed by cross-validation. + The grid is logarithmic, so must be greater than 1. */ + +class CvMLData; + +struct CvParamGrid +{ + // SVM params type + enum { SVM_C=0, SVM_GAMMA=1, SVM_P=2, SVM_NU=3, SVM_COEF=4, SVM_DEGREE=5 }; + + CvParamGrid() + { + min_val = max_val = step = 0; + } + + CvParamGrid( double min_val, double max_val, double log_step ); + //CvParamGrid( int param_id ); + bool check() const; + + CV_PROP_RW double min_val; + CV_PROP_RW double max_val; + CV_PROP_RW double step; +}; + +inline CvParamGrid::CvParamGrid( double _min_val, double _max_val, double _log_step ) +{ + min_val = _min_val; + max_val = _max_val; + step = _log_step; +} + +class CvNormalBayesClassifier : public CvStatModel +{ +public: + CV_WRAP CvNormalBayesClassifier(); + virtual ~CvNormalBayesClassifier(); + + CvNormalBayesClassifier( const CvMat* trainData, const CvMat* responses, + const CvMat* varIdx=0, const CvMat* sampleIdx=0 ); + + virtual bool train( const CvMat* trainData, const CvMat* responses, + const CvMat* varIdx = 0, const CvMat* sampleIdx=0, bool update=false ); + + virtual float predict( const CvMat* samples, CV_OUT CvMat* results=0, CV_OUT CvMat* results_prob=0 ) const; + CV_WRAP virtual void clear(); + + CV_WRAP CvNormalBayesClassifier( const cv::Mat& trainData, const cv::Mat& responses, + const cv::Mat& varIdx=cv::Mat(), const cv::Mat& sampleIdx=cv::Mat() ); + CV_WRAP virtual bool train( const cv::Mat& trainData, const cv::Mat& responses, + const cv::Mat& varIdx = cv::Mat(), const cv::Mat& sampleIdx=cv::Mat(), + bool update=false ); + CV_WRAP virtual float predict( const cv::Mat& samples, CV_OUT cv::Mat* results=0, CV_OUT cv::Mat* results_prob=0 ) const; + + virtual void write( cv::FileStorage& storage, const char* name ) const; + virtual void read( const cv::FileNode& node ); + +protected: + int var_count, var_all; + CvMat* var_idx; + CvMat* cls_labels; + CvMat** count; + CvMat** sum; + CvMat** productsum; + CvMat** avg; + CvMat** inv_eigen_values; + CvMat** cov_rotate_mats; + CvMat* c; +}; + + +/****************************************************************************************\ +* K-Nearest Neighbour Classifier * +\****************************************************************************************/ + +// k Nearest Neighbors +class CvKNearest : public CvStatModel +{ +public: + + CV_WRAP CvKNearest(); + virtual ~CvKNearest(); + + CvKNearest( const CvMat* trainData, const CvMat* responses, + const CvMat* sampleIdx=0, bool isRegression=false, int max_k=32 ); + + virtual bool train( const CvMat* trainData, const CvMat* responses, + const CvMat* sampleIdx=0, bool is_regression=false, + int maxK=32, bool updateBase=false ); + + virtual float find_nearest( const CvMat* samples, int k, CV_OUT CvMat* results=0, + const float** neighbors=0, CV_OUT CvMat* neighborResponses=0, CV_OUT CvMat* dist=0 ) const; + + CV_WRAP CvKNearest( const cv::Mat& trainData, const cv::Mat& responses, + const cv::Mat& sampleIdx=cv::Mat(), bool isRegression=false, int max_k=32 ); + + CV_WRAP virtual bool train( const cv::Mat& trainData, const cv::Mat& responses, + const cv::Mat& sampleIdx=cv::Mat(), bool isRegression=false, + int maxK=32, bool updateBase=false ); + + virtual float find_nearest( const cv::Mat& samples, int k, cv::Mat* results=0, + const float** neighbors=0, cv::Mat* neighborResponses=0, + cv::Mat* dist=0 ) const; + CV_WRAP virtual float find_nearest( const cv::Mat& samples, int k, CV_OUT cv::Mat& results, + CV_OUT cv::Mat& neighborResponses, CV_OUT cv::Mat& dists) const; + + virtual void clear(); + int get_max_k() const; + int get_var_count() const; + int get_sample_count() const; + bool is_regression() const; + + virtual float write_results( int k, int k1, int start, int end, + const float* neighbor_responses, const float* dist, CvMat* _results, + CvMat* _neighbor_responses, CvMat* _dist, Cv32suf* sort_buf ) const; + + virtual void find_neighbors_direct( const CvMat* _samples, int k, int start, int end, + float* neighbor_responses, const float** neighbors, float* dist ) const; + +protected: + + int max_k, var_count; + int total; + bool regression; + CvVectors* samples; +}; + +/****************************************************************************************\ +* Support Vector Machines * +\****************************************************************************************/ + +// SVM training parameters +struct CvSVMParams +{ + CvSVMParams(); + CvSVMParams( int svm_type, int kernel_type, + double degree, double gamma, double coef0, + double Cvalue, double nu, double p, + CvMat* class_weights, CvTermCriteria term_crit ); + + CV_PROP_RW int svm_type; + CV_PROP_RW int kernel_type; + CV_PROP_RW double degree; // for poly + CV_PROP_RW double gamma; // for poly/rbf/sigmoid/chi2 + CV_PROP_RW double coef0; // for poly/sigmoid + + CV_PROP_RW double C; // for CV_SVM_C_SVC, CV_SVM_EPS_SVR and CV_SVM_NU_SVR + CV_PROP_RW double nu; // for CV_SVM_NU_SVC, CV_SVM_ONE_CLASS, and CV_SVM_NU_SVR + CV_PROP_RW double p; // for CV_SVM_EPS_SVR + CvMat* class_weights; // for CV_SVM_C_SVC + CV_PROP_RW CvTermCriteria term_crit; // termination criteria +}; + + +struct CvSVMKernel +{ + typedef void (CvSVMKernel::*Calc)( int vec_count, int vec_size, const float** vecs, + const float* another, float* results ); + CvSVMKernel(); + CvSVMKernel( const CvSVMParams* params, Calc _calc_func ); + virtual bool create( const CvSVMParams* params, Calc _calc_func ); + virtual ~CvSVMKernel(); + + virtual void clear(); + virtual void calc( int vcount, int n, const float** vecs, const float* another, float* results ); + + const CvSVMParams* params; + Calc calc_func; + + virtual void calc_non_rbf_base( int vec_count, int vec_size, const float** vecs, + const float* another, float* results, + double alpha, double beta ); + virtual void calc_intersec( int vcount, int var_count, const float** vecs, + const float* another, float* results ); + virtual void calc_chi2( int vec_count, int vec_size, const float** vecs, + const float* another, float* results ); + virtual void calc_linear( int vec_count, int vec_size, const float** vecs, + const float* another, float* results ); + virtual void calc_rbf( int vec_count, int vec_size, const float** vecs, + const float* another, float* results ); + virtual void calc_poly( int vec_count, int vec_size, const float** vecs, + const float* another, float* results ); + virtual void calc_sigmoid( int vec_count, int vec_size, const float** vecs, + const float* another, float* results ); +}; + + +struct CvSVMKernelRow +{ + CvSVMKernelRow* prev; + CvSVMKernelRow* next; + float* data; +}; + + +struct CvSVMSolutionInfo +{ + double obj; + double rho; + double upper_bound_p; + double upper_bound_n; + double r; // for Solver_NU +}; + +class CvSVMSolver +{ +public: + typedef bool (CvSVMSolver::*SelectWorkingSet)( int& i, int& j ); + typedef float* (CvSVMSolver::*GetRow)( int i, float* row, float* dst, bool existed ); + typedef void (CvSVMSolver::*CalcRho)( double& rho, double& r ); + + CvSVMSolver(); + + CvSVMSolver( int count, int var_count, const float** samples, schar* y, + int alpha_count, double* alpha, double Cp, double Cn, + CvMemStorage* storage, CvSVMKernel* kernel, GetRow get_row, + SelectWorkingSet select_working_set, CalcRho calc_rho ); + virtual bool create( int count, int var_count, const float** samples, schar* y, + int alpha_count, double* alpha, double Cp, double Cn, + CvMemStorage* storage, CvSVMKernel* kernel, GetRow get_row, + SelectWorkingSet select_working_set, CalcRho calc_rho ); + virtual ~CvSVMSolver(); + + virtual void clear(); + virtual bool solve_generic( CvSVMSolutionInfo& si ); + + virtual bool solve_c_svc( int count, int var_count, const float** samples, schar* y, + double Cp, double Cn, CvMemStorage* storage, + CvSVMKernel* kernel, double* alpha, CvSVMSolutionInfo& si ); + virtual bool solve_nu_svc( int count, int var_count, const float** samples, schar* y, + CvMemStorage* storage, CvSVMKernel* kernel, + double* alpha, CvSVMSolutionInfo& si ); + virtual bool solve_one_class( int count, int var_count, const float** samples, + CvMemStorage* storage, CvSVMKernel* kernel, + double* alpha, CvSVMSolutionInfo& si ); + + virtual bool solve_eps_svr( int count, int var_count, const float** samples, const float* y, + CvMemStorage* storage, CvSVMKernel* kernel, + double* alpha, CvSVMSolutionInfo& si ); + + virtual bool solve_nu_svr( int count, int var_count, const float** samples, const float* y, + CvMemStorage* storage, CvSVMKernel* kernel, + double* alpha, CvSVMSolutionInfo& si ); + + virtual float* get_row_base( int i, bool* _existed ); + virtual float* get_row( int i, float* dst ); + + int sample_count; + int var_count; + int cache_size; + int cache_line_size; + const float** samples; + const CvSVMParams* params; + CvMemStorage* storage; + CvSVMKernelRow lru_list; + CvSVMKernelRow* rows; + + int alpha_count; + + double* G; + double* alpha; + + // -1 - lower bound, 0 - free, 1 - upper bound + schar* alpha_status; + + schar* y; + double* b; + float* buf[2]; + double eps; + int max_iter; + double C[2]; // C[0] == Cn, C[1] == Cp + CvSVMKernel* kernel; + + SelectWorkingSet select_working_set_func; + CalcRho calc_rho_func; + GetRow get_row_func; + + virtual bool select_working_set( int& i, int& j ); + virtual bool select_working_set_nu_svm( int& i, int& j ); + virtual void calc_rho( double& rho, double& r ); + virtual void calc_rho_nu_svm( double& rho, double& r ); + + virtual float* get_row_svc( int i, float* row, float* dst, bool existed ); + virtual float* get_row_one_class( int i, float* row, float* dst, bool existed ); + virtual float* get_row_svr( int i, float* row, float* dst, bool existed ); +}; + + +struct CvSVMDecisionFunc +{ + double rho; + int sv_count; + double* alpha; + int* sv_index; +}; + + +// SVM model +class CvSVM : public CvStatModel +{ +public: + // SVM type + enum { C_SVC=100, NU_SVC=101, ONE_CLASS=102, EPS_SVR=103, NU_SVR=104 }; + + // SVM kernel type + enum { LINEAR=0, POLY=1, RBF=2, SIGMOID=3, CHI2=4, INTER=5 }; + + // SVM params type + enum { C=0, GAMMA=1, P=2, NU=3, COEF=4, DEGREE=5 }; + + CV_WRAP CvSVM(); + virtual ~CvSVM(); + + CvSVM( const CvMat* trainData, const CvMat* responses, + const CvMat* varIdx=0, const CvMat* sampleIdx=0, + CvSVMParams params=CvSVMParams() ); + + virtual bool train( const CvMat* trainData, const CvMat* responses, + const CvMat* varIdx=0, const CvMat* sampleIdx=0, + CvSVMParams params=CvSVMParams() ); + + virtual bool train_auto( const CvMat* trainData, const CvMat* responses, + const CvMat* varIdx, const CvMat* sampleIdx, CvSVMParams params, + int kfold = 10, + CvParamGrid Cgrid = get_default_grid(CvSVM::C), + CvParamGrid gammaGrid = get_default_grid(CvSVM::GAMMA), + CvParamGrid pGrid = get_default_grid(CvSVM::P), + CvParamGrid nuGrid = get_default_grid(CvSVM::NU), + CvParamGrid coeffGrid = get_default_grid(CvSVM::COEF), + CvParamGrid degreeGrid = get_default_grid(CvSVM::DEGREE), + bool balanced=false ); + + virtual float predict( const CvMat* sample, bool returnDFVal=false ) const; + virtual float predict( const CvMat* samples, CV_OUT CvMat* results, bool returnDFVal=false ) const; + + CV_WRAP CvSVM( const cv::Mat& trainData, const cv::Mat& responses, + const cv::Mat& varIdx=cv::Mat(), const cv::Mat& sampleIdx=cv::Mat(), + CvSVMParams params=CvSVMParams() ); + + CV_WRAP virtual bool train( const cv::Mat& trainData, const cv::Mat& responses, + const cv::Mat& varIdx=cv::Mat(), const cv::Mat& sampleIdx=cv::Mat(), + CvSVMParams params=CvSVMParams() ); + + CV_WRAP virtual bool train_auto( const cv::Mat& trainData, const cv::Mat& responses, + const cv::Mat& varIdx, const cv::Mat& sampleIdx, CvSVMParams params, + int k_fold = 10, + CvParamGrid Cgrid = CvSVM::get_default_grid(CvSVM::C), + CvParamGrid gammaGrid = CvSVM::get_default_grid(CvSVM::GAMMA), + CvParamGrid pGrid = CvSVM::get_default_grid(CvSVM::P), + CvParamGrid nuGrid = CvSVM::get_default_grid(CvSVM::NU), + CvParamGrid coeffGrid = CvSVM::get_default_grid(CvSVM::COEF), + CvParamGrid degreeGrid = CvSVM::get_default_grid(CvSVM::DEGREE), + bool balanced=false); + CV_WRAP virtual float predict( const cv::Mat& sample, bool returnDFVal=false ) const; + CV_WRAP_AS(predict_all) virtual void predict( cv::InputArray samples, cv::OutputArray results ) const; + + CV_WRAP virtual int get_support_vector_count() const; + virtual const float* get_support_vector(int i) const; + virtual CvSVMParams get_params() const { return params; } + CV_WRAP virtual void clear(); + + virtual const CvSVMDecisionFunc* get_decision_function() const { return decision_func; } + + static CvParamGrid get_default_grid( int param_id ); + + virtual void write( cv::FileStorage& storage, const char* name ) const; + virtual void read( const cv::FileNode& node ); + CV_WRAP int get_var_count() const { return var_idx ? var_idx->cols : var_all; } + +protected: + + virtual bool set_params( const CvSVMParams& params ); + virtual bool train1( int sample_count, int var_count, const float** samples, + const void* responses, double Cp, double Cn, + CvMemStorage* _storage, double* alpha, double& rho ); + virtual bool do_train( int svm_type, int sample_count, int var_count, const float** samples, + const CvMat* responses, CvMemStorage* _storage, double* alpha ); + virtual void create_kernel(); + virtual void create_solver(); + + virtual float predict( const float* row_sample, int row_len, bool returnDFVal=false ) const; + + virtual void write_params( cv::FileStorage& fs ) const; + virtual void read_params( const cv::FileNode& node ); + + void optimize_linear_svm(); + + CvSVMParams params; + CvMat* class_labels; + int var_all; + float** sv; + int sv_total; + CvMat* var_idx; + CvMat* class_weights; + CvSVMDecisionFunc* decision_func; + CvMemStorage* storage; + + CvSVMSolver* solver; + CvSVMKernel* kernel; + +private: + CvSVM(const CvSVM&); + CvSVM& operator = (const CvSVM&); +}; + +/****************************************************************************************\ +* Decision Tree * +\****************************************************************************************/\ +struct CvPair16u32s +{ + unsigned short* u; + int* i; +}; + + +#define CV_DTREE_CAT_DIR(idx,subset) \ + (2*((subset[(idx)>>5]&(1 << ((idx) & 31)))==0)-1) + +struct CvDTreeSplit +{ + int var_idx; + int condensed_idx; + int inversed; + float quality; + CvDTreeSplit* next; + union + { + int subset[2]; + struct + { + float c; + int split_point; + } + ord; + }; +}; + +struct CvDTreeNode +{ + int class_idx; + int Tn; + double value; + + CvDTreeNode* parent; + CvDTreeNode* left; + CvDTreeNode* right; + + CvDTreeSplit* split; + + int sample_count; + int depth; + int* num_valid; + int offset; + int buf_idx; + double maxlr; + + // global pruning data + int complexity; + double alpha; + double node_risk, tree_risk, tree_error; + + // cross-validation pruning data + int* cv_Tn; + double* cv_node_risk; + double* cv_node_error; + + int get_num_valid(int vi) { return num_valid ? num_valid[vi] : sample_count; } + void set_num_valid(int vi, int n) { if( num_valid ) num_valid[vi] = n; } +}; + + +struct CvDTreeParams +{ + CV_PROP_RW int max_categories; + CV_PROP_RW int max_depth; + CV_PROP_RW int min_sample_count; + CV_PROP_RW int cv_folds; + CV_PROP_RW bool use_surrogates; + CV_PROP_RW bool use_1se_rule; + CV_PROP_RW bool truncate_pruned_tree; + CV_PROP_RW float regression_accuracy; + const float* priors; + + CvDTreeParams(); + CvDTreeParams( int max_depth, int min_sample_count, + float regression_accuracy, bool use_surrogates, + int max_categories, int cv_folds, + bool use_1se_rule, bool truncate_pruned_tree, + const float* priors ); +}; + + +struct CvDTreeTrainData +{ + CvDTreeTrainData(); + CvDTreeTrainData( const CvMat* trainData, int tflag, + const CvMat* responses, const CvMat* varIdx=0, + const CvMat* sampleIdx=0, const CvMat* varType=0, + const CvMat* missingDataMask=0, + const CvDTreeParams& params=CvDTreeParams(), + bool _shared=false, bool _add_labels=false ); + virtual ~CvDTreeTrainData(); + + virtual void set_data( const CvMat* trainData, int tflag, + const CvMat* responses, const CvMat* varIdx=0, + const CvMat* sampleIdx=0, const CvMat* varType=0, + const CvMat* missingDataMask=0, + const CvDTreeParams& params=CvDTreeParams(), + bool _shared=false, bool _add_labels=false, + bool _update_data=false ); + virtual void do_responses_copy(); + + virtual void get_vectors( const CvMat* _subsample_idx, + float* values, uchar* missing, float* responses, bool get_class_idx=false ); + + virtual CvDTreeNode* subsample_data( const CvMat* _subsample_idx ); + + virtual void write_params( cv::FileStorage& fs ) const; + virtual void read_params( const cv::FileNode& node ); + + // release all the data + virtual void clear(); + + int get_num_classes() const; + int get_var_type(int vi) const; + int get_work_var_count() const {return work_var_count;} + + virtual const float* get_ord_responses( CvDTreeNode* n, float* values_buf, int* sample_indices_buf ); + virtual const int* get_class_labels( CvDTreeNode* n, int* labels_buf ); + virtual const int* get_cv_labels( CvDTreeNode* n, int* labels_buf ); + virtual const int* get_sample_indices( CvDTreeNode* n, int* indices_buf ); + virtual const int* get_cat_var_data( CvDTreeNode* n, int vi, int* cat_values_buf ); + virtual void get_ord_var_data( CvDTreeNode* n, int vi, float* ord_values_buf, int* sorted_indices_buf, + const float** ord_values, const int** sorted_indices, int* sample_indices_buf ); + virtual int get_child_buf_idx( CvDTreeNode* n ); + + //////////////////////////////////// + + virtual bool set_params( const CvDTreeParams& params ); + virtual CvDTreeNode* new_node( CvDTreeNode* parent, int count, + int storage_idx, int offset ); + + virtual CvDTreeSplit* new_split_ord( int vi, float cmp_val, + int split_point, int inversed, float quality ); + virtual CvDTreeSplit* new_split_cat( int vi, float quality ); + virtual void free_node_data( CvDTreeNode* node ); + virtual void free_train_data(); + virtual void free_node( CvDTreeNode* node ); + + int sample_count, var_all, var_count, max_c_count; + int ord_var_count, cat_var_count, work_var_count; + bool have_labels, have_priors; + bool is_classifier; + int tflag; + + const CvMat* train_data; + const CvMat* responses; + CvMat* responses_copy; // used in Boosting + + int buf_count, buf_size; // buf_size is obsolete, please do not use it, use expression ((int64)buf->rows * (int64)buf->cols / buf_count) instead + bool shared; + int is_buf_16u; + + CvMat* cat_count; + CvMat* cat_ofs; + CvMat* cat_map; + + CvMat* counts; + CvMat* buf; + inline size_t get_length_subbuf() const + { + size_t res = (size_t)(work_var_count + 1) * (size_t)sample_count; + return res; + } + + CvMat* direction; + CvMat* split_buf; + + CvMat* var_idx; + CvMat* var_type; // i-th element = + // k<0 - ordered + // k>=0 - categorical, see k-th element of cat_* arrays + CvMat* priors; + CvMat* priors_mult; + + CvDTreeParams params; + + CvMemStorage* tree_storage; + CvMemStorage* temp_storage; + + CvDTreeNode* data_root; + + CvSet* node_heap; + CvSet* split_heap; + CvSet* cv_heap; + CvSet* nv_heap; + + cv::RNG* rng; +}; + +class CvDTree; +class CvForestTree; + +namespace cv +{ + struct DTreeBestSplitFinder; + struct ForestTreeBestSplitFinder; +} + +class CvDTree : public CvStatModel +{ +public: + CV_WRAP CvDTree(); + virtual ~CvDTree(); + + virtual bool train( const CvMat* trainData, int tflag, + const CvMat* responses, const CvMat* varIdx=0, + const CvMat* sampleIdx=0, const CvMat* varType=0, + const CvMat* missingDataMask=0, + CvDTreeParams params=CvDTreeParams() ); + + virtual bool train( CvMLData* trainData, CvDTreeParams params=CvDTreeParams() ); + + // type in {CV_TRAIN_ERROR, CV_TEST_ERROR} + virtual float calc_error( CvMLData* trainData, int type, std::vector *resp = 0 ); + + virtual bool train( CvDTreeTrainData* trainData, const CvMat* subsampleIdx ); + + virtual CvDTreeNode* predict( const CvMat* sample, const CvMat* missingDataMask=0, + bool preprocessedInput=false ) const; + + CV_WRAP virtual bool train( const cv::Mat& trainData, int tflag, + const cv::Mat& responses, const cv::Mat& varIdx=cv::Mat(), + const cv::Mat& sampleIdx=cv::Mat(), const cv::Mat& varType=cv::Mat(), + const cv::Mat& missingDataMask=cv::Mat(), + CvDTreeParams params=CvDTreeParams() ); + + CV_WRAP virtual CvDTreeNode* predict( const cv::Mat& sample, const cv::Mat& missingDataMask=cv::Mat(), + bool preprocessedInput=false ) const; + CV_WRAP virtual cv::Mat getVarImportance(); + + virtual const CvMat* get_var_importance(); + CV_WRAP virtual void clear(); + + virtual void read( CvFileStorage* fs, CvFileNode* node ); + virtual void write( CvFileStorage* fs, const char* name ) const; + + // special read & write methods for trees in the tree ensembles + virtual void read( CvFileStorage* fs, CvFileNode* node, + CvDTreeTrainData* data ); + virtual void write( CvFileStorage* fs ) const; + + const CvDTreeNode* get_root() const; + int get_pruned_tree_idx() const; + CvDTreeTrainData* get_data(); + +protected: + friend struct cv::DTreeBestSplitFinder; + + virtual bool do_train( const CvMat* _subsample_idx ); + + virtual void try_split_node( CvDTreeNode* n ); + virtual void split_node_data( CvDTreeNode* n ); + virtual CvDTreeSplit* find_best_split( CvDTreeNode* n ); + virtual CvDTreeSplit* find_split_ord_class( CvDTreeNode* n, int vi, + float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); + virtual CvDTreeSplit* find_split_cat_class( CvDTreeNode* n, int vi, + float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); + virtual CvDTreeSplit* find_split_ord_reg( CvDTreeNode* n, int vi, + float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); + virtual CvDTreeSplit* find_split_cat_reg( CvDTreeNode* n, int vi, + float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); + virtual CvDTreeSplit* find_surrogate_split_ord( CvDTreeNode* n, int vi, uchar* ext_buf = 0 ); + virtual CvDTreeSplit* find_surrogate_split_cat( CvDTreeNode* n, int vi, uchar* ext_buf = 0 ); + virtual double calc_node_dir( CvDTreeNode* node ); + virtual void complete_node_dir( CvDTreeNode* node ); + virtual void cluster_categories( const int* vectors, int vector_count, + int var_count, int* sums, int k, int* cluster_labels ); + + virtual void calc_node_value( CvDTreeNode* node ); + + virtual void prune_cv(); + virtual double update_tree_rnc( int T, int fold ); + virtual int cut_tree( int T, int fold, double min_alpha ); + virtual void free_prune_data(bool cut_tree); + virtual void free_tree(); + + virtual void write_node( CvFileStorage* fs, CvDTreeNode* node ) const; + virtual void write_split( CvFileStorage* fs, CvDTreeSplit* split ) const; + virtual CvDTreeNode* read_node( CvFileStorage* fs, CvFileNode* node, CvDTreeNode* parent ); + virtual CvDTreeSplit* read_split( CvFileStorage* fs, CvFileNode* node ); + virtual void write_tree_nodes( CvFileStorage* fs ) const; + virtual void read_tree_nodes( CvFileStorage* fs, CvFileNode* node ); + + CvDTreeNode* root; + CvMat* var_importance; + CvDTreeTrainData* data; + CvMat train_data_hdr, responses_hdr; + cv::Mat train_data_mat, responses_mat; + +public: + int pruned_tree_idx; +}; + + +/****************************************************************************************\ +* Random Trees Classifier * +\****************************************************************************************/ + +class CvRTrees; + +class CvForestTree: public CvDTree +{ +public: + CvForestTree(); + virtual ~CvForestTree(); + + virtual bool train( CvDTreeTrainData* trainData, const CvMat* _subsample_idx, CvRTrees* forest ); + + virtual int get_var_count() const {return data ? data->var_count : 0;} + virtual void read( CvFileStorage* fs, CvFileNode* node, CvRTrees* forest, CvDTreeTrainData* _data ); + + /* dummy methods to avoid warnings: BEGIN */ + virtual bool train( const CvMat* trainData, int tflag, + const CvMat* responses, const CvMat* varIdx=0, + const CvMat* sampleIdx=0, const CvMat* varType=0, + const CvMat* missingDataMask=0, + CvDTreeParams params=CvDTreeParams() ); + + virtual bool train( CvDTreeTrainData* trainData, const CvMat* _subsample_idx ); + virtual void read( CvFileStorage* fs, CvFileNode* node ); + virtual void read( CvFileStorage* fs, CvFileNode* node, + CvDTreeTrainData* data ); + /* dummy methods to avoid warnings: END */ + +protected: + friend struct cv::ForestTreeBestSplitFinder; + + virtual CvDTreeSplit* find_best_split( CvDTreeNode* n ); + CvRTrees* forest; +}; + + +struct CvRTParams : public CvDTreeParams +{ + //Parameters for the forest + CV_PROP_RW bool calc_var_importance; // true <=> RF processes variable importance + CV_PROP_RW int nactive_vars; + CV_PROP_RW CvTermCriteria term_crit; + + CvRTParams(); + CvRTParams( int max_depth, int min_sample_count, + float regression_accuracy, bool use_surrogates, + int max_categories, const float* priors, bool calc_var_importance, + int nactive_vars, int max_num_of_trees_in_the_forest, + float forest_accuracy, int termcrit_type ); +}; + + +class CvRTrees : public CvStatModel +{ +public: + CV_WRAP CvRTrees(); + virtual ~CvRTrees(); + virtual bool train( const CvMat* trainData, int tflag, + const CvMat* responses, const CvMat* varIdx=0, + const CvMat* sampleIdx=0, const CvMat* varType=0, + const CvMat* missingDataMask=0, + CvRTParams params=CvRTParams() ); + + virtual bool train( CvMLData* data, CvRTParams params=CvRTParams() ); + virtual float predict( const CvMat* sample, const CvMat* missing = 0 ) const; + virtual float predict_prob( const CvMat* sample, const CvMat* missing = 0 ) const; + + CV_WRAP virtual bool train( const cv::Mat& trainData, int tflag, + const cv::Mat& responses, const cv::Mat& varIdx=cv::Mat(), + const cv::Mat& sampleIdx=cv::Mat(), const cv::Mat& varType=cv::Mat(), + const cv::Mat& missingDataMask=cv::Mat(), + CvRTParams params=CvRTParams() ); + CV_WRAP virtual float predict( const cv::Mat& sample, const cv::Mat& missing = cv::Mat() ) const; + CV_WRAP virtual float predict_prob( const cv::Mat& sample, const cv::Mat& missing = cv::Mat() ) const; + CV_WRAP virtual cv::Mat getVarImportance(); + + CV_WRAP virtual void clear(); + + virtual const CvMat* get_var_importance(); + virtual float get_proximity( const CvMat* sample1, const CvMat* sample2, + const CvMat* missing1 = 0, const CvMat* missing2 = 0 ) const; + + virtual float calc_error( CvMLData* data, int type , std::vector* resp = 0 ); // type in {CV_TRAIN_ERROR, CV_TEST_ERROR} + + virtual float get_train_error(); + + virtual void read( CvFileStorage* fs, CvFileNode* node ); + virtual void write( CvFileStorage* fs, const char* name ) const; + + CvMat* get_active_var_mask(); + CvRNG* get_rng(); + + int get_tree_count() const; + CvForestTree* get_tree(int i) const; + +protected: + virtual cv::String getName() const; + + virtual bool grow_forest( const CvTermCriteria term_crit ); + + // array of the trees of the forest + CvForestTree** trees; + CvDTreeTrainData* data; + CvMat train_data_hdr, responses_hdr; + cv::Mat train_data_mat, responses_mat; + int ntrees; + int nclasses; + double oob_error; + CvMat* var_importance; + int nsamples; + + cv::RNG* rng; + CvMat* active_var_mask; +}; + +/****************************************************************************************\ +* Extremely randomized trees Classifier * +\****************************************************************************************/ +struct CvERTreeTrainData : public CvDTreeTrainData +{ + virtual void set_data( const CvMat* trainData, int tflag, + const CvMat* responses, const CvMat* varIdx=0, + const CvMat* sampleIdx=0, const CvMat* varType=0, + const CvMat* missingDataMask=0, + const CvDTreeParams& params=CvDTreeParams(), + bool _shared=false, bool _add_labels=false, + bool _update_data=false ); + virtual void get_ord_var_data( CvDTreeNode* n, int vi, float* ord_values_buf, int* missing_buf, + const float** ord_values, const int** missing, int* sample_buf = 0 ); + virtual const int* get_sample_indices( CvDTreeNode* n, int* indices_buf ); + virtual const int* get_cv_labels( CvDTreeNode* n, int* labels_buf ); + virtual const int* get_cat_var_data( CvDTreeNode* n, int vi, int* cat_values_buf ); + virtual void get_vectors( const CvMat* _subsample_idx, float* values, uchar* missing, + float* responses, bool get_class_idx=false ); + virtual CvDTreeNode* subsample_data( const CvMat* _subsample_idx ); + const CvMat* missing_mask; +}; + +class CvForestERTree : public CvForestTree +{ +protected: + virtual double calc_node_dir( CvDTreeNode* node ); + virtual CvDTreeSplit* find_split_ord_class( CvDTreeNode* n, int vi, + float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); + virtual CvDTreeSplit* find_split_cat_class( CvDTreeNode* n, int vi, + float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); + virtual CvDTreeSplit* find_split_ord_reg( CvDTreeNode* n, int vi, + float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); + virtual CvDTreeSplit* find_split_cat_reg( CvDTreeNode* n, int vi, + float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); + virtual void split_node_data( CvDTreeNode* n ); +}; + +class CvERTrees : public CvRTrees +{ +public: + CV_WRAP CvERTrees(); + virtual ~CvERTrees(); + virtual bool train( const CvMat* trainData, int tflag, + const CvMat* responses, const CvMat* varIdx=0, + const CvMat* sampleIdx=0, const CvMat* varType=0, + const CvMat* missingDataMask=0, + CvRTParams params=CvRTParams()); + CV_WRAP virtual bool train( const cv::Mat& trainData, int tflag, + const cv::Mat& responses, const cv::Mat& varIdx=cv::Mat(), + const cv::Mat& sampleIdx=cv::Mat(), const cv::Mat& varType=cv::Mat(), + const cv::Mat& missingDataMask=cv::Mat(), + CvRTParams params=CvRTParams()); + virtual bool train( CvMLData* data, CvRTParams params=CvRTParams() ); +protected: + virtual cv::String getName() const; + virtual bool grow_forest( const CvTermCriteria term_crit ); +}; + + +/****************************************************************************************\ +* Boosted tree classifier * +\****************************************************************************************/ + +struct CvBoostParams : public CvDTreeParams +{ + CV_PROP_RW int boost_type; + CV_PROP_RW int weak_count; + CV_PROP_RW int split_criteria; + CV_PROP_RW double weight_trim_rate; + + CvBoostParams(); + CvBoostParams( int boost_type, int weak_count, double weight_trim_rate, + int max_depth, bool use_surrogates, const float* priors ); +}; + + +class CvBoost; + +class CvBoostTree: public CvDTree +{ +public: + CvBoostTree(); + virtual ~CvBoostTree(); + + virtual bool train( CvDTreeTrainData* trainData, + const CvMat* subsample_idx, CvBoost* ensemble ); + + virtual void scale( double s ); + virtual void read( CvFileStorage* fs, CvFileNode* node, + CvBoost* ensemble, CvDTreeTrainData* _data ); + virtual void clear(); + + /* dummy methods to avoid warnings: BEGIN */ + virtual bool train( const CvMat* trainData, int tflag, + const CvMat* responses, const CvMat* varIdx=0, + const CvMat* sampleIdx=0, const CvMat* varType=0, + const CvMat* missingDataMask=0, + CvDTreeParams params=CvDTreeParams() ); + virtual bool train( CvDTreeTrainData* trainData, const CvMat* _subsample_idx ); + + virtual void read( CvFileStorage* fs, CvFileNode* node ); + virtual void read( CvFileStorage* fs, CvFileNode* node, + CvDTreeTrainData* data ); + /* dummy methods to avoid warnings: END */ + +protected: + + virtual void try_split_node( CvDTreeNode* n ); + virtual CvDTreeSplit* find_surrogate_split_ord( CvDTreeNode* n, int vi, uchar* ext_buf = 0 ); + virtual CvDTreeSplit* find_surrogate_split_cat( CvDTreeNode* n, int vi, uchar* ext_buf = 0 ); + virtual CvDTreeSplit* find_split_ord_class( CvDTreeNode* n, int vi, + float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); + virtual CvDTreeSplit* find_split_cat_class( CvDTreeNode* n, int vi, + float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); + virtual CvDTreeSplit* find_split_ord_reg( CvDTreeNode* n, int vi, + float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); + virtual CvDTreeSplit* find_split_cat_reg( CvDTreeNode* n, int vi, + float init_quality = 0, CvDTreeSplit* _split = 0, uchar* ext_buf = 0 ); + virtual void calc_node_value( CvDTreeNode* n ); + virtual double calc_node_dir( CvDTreeNode* n ); + + CvBoost* ensemble; +}; + + +class CvBoost : public CvStatModel +{ +public: + // Boosting type + enum { DISCRETE=0, REAL=1, LOGIT=2, GENTLE=3 }; + + // Splitting criteria + enum { DEFAULT=0, GINI=1, MISCLASS=3, SQERR=4 }; + + CV_WRAP CvBoost(); + virtual ~CvBoost(); + + CvBoost( const CvMat* trainData, int tflag, + const CvMat* responses, const CvMat* varIdx=0, + const CvMat* sampleIdx=0, const CvMat* varType=0, + const CvMat* missingDataMask=0, + CvBoostParams params=CvBoostParams() ); + + virtual bool train( const CvMat* trainData, int tflag, + const CvMat* responses, const CvMat* varIdx=0, + const CvMat* sampleIdx=0, const CvMat* varType=0, + const CvMat* missingDataMask=0, + CvBoostParams params=CvBoostParams(), + bool update=false ); + + virtual bool train( CvMLData* data, + CvBoostParams params=CvBoostParams(), + bool update=false ); + + virtual float predict( const CvMat* sample, const CvMat* missing=0, + CvMat* weak_responses=0, CvSlice slice=CV_WHOLE_SEQ, + bool raw_mode=false, bool return_sum=false ) const; + + CV_WRAP CvBoost( const cv::Mat& trainData, int tflag, + const cv::Mat& responses, const cv::Mat& varIdx=cv::Mat(), + const cv::Mat& sampleIdx=cv::Mat(), const cv::Mat& varType=cv::Mat(), + const cv::Mat& missingDataMask=cv::Mat(), + CvBoostParams params=CvBoostParams() ); + + CV_WRAP virtual bool train( const cv::Mat& trainData, int tflag, + const cv::Mat& responses, const cv::Mat& varIdx=cv::Mat(), + const cv::Mat& sampleIdx=cv::Mat(), const cv::Mat& varType=cv::Mat(), + const cv::Mat& missingDataMask=cv::Mat(), + CvBoostParams params=CvBoostParams(), + bool update=false ); + + CV_WRAP virtual float predict( const cv::Mat& sample, const cv::Mat& missing=cv::Mat(), + const cv::Range& slice=cv::Range::all(), bool rawMode=false, + bool returnSum=false ) const; + + virtual float calc_error( CvMLData* _data, int type , std::vector *resp = 0 ); // type in {CV_TRAIN_ERROR, CV_TEST_ERROR} + + CV_WRAP virtual void prune( CvSlice slice ); + + CV_WRAP virtual void clear(); + + virtual void write( CvFileStorage* storage, const char* name ) const; + virtual void read( CvFileStorage* storage, CvFileNode* node ); + virtual const CvMat* get_active_vars(bool absolute_idx=true); + + CvSeq* get_weak_predictors(); + + CvMat* get_weights(); + CvMat* get_subtree_weights(); + CvMat* get_weak_response(); + const CvBoostParams& get_params() const; + const CvDTreeTrainData* get_data() const; + +protected: + + virtual bool set_params( const CvBoostParams& params ); + virtual void update_weights( CvBoostTree* tree ); + virtual void trim_weights(); + virtual void write_params( CvFileStorage* fs ) const; + virtual void read_params( CvFileStorage* fs, CvFileNode* node ); + + virtual void initialize_weights(double (&p)[2]); + + CvDTreeTrainData* data; + CvMat train_data_hdr, responses_hdr; + cv::Mat train_data_mat, responses_mat; + CvBoostParams params; + CvSeq* weak; + + CvMat* active_vars; + CvMat* active_vars_abs; + bool have_active_cat_vars; + + CvMat* orig_response; + CvMat* sum_response; + CvMat* weak_eval; + CvMat* subsample_mask; + CvMat* weights; + CvMat* subtree_weights; + bool have_subsample; +}; + + +/****************************************************************************************\ +* Gradient Boosted Trees * +\****************************************************************************************/ + +// DataType: STRUCT CvGBTreesParams +// Parameters of GBT (Gradient Boosted trees model), including single +// tree settings and ensemble parameters. +// +// weak_count - count of trees in the ensemble +// loss_function_type - loss function used for ensemble training +// subsample_portion - portion of whole training set used for +// every single tree training. +// subsample_portion value is in (0.0, 1.0]. +// subsample_portion == 1.0 when whole dataset is +// used on each step. Count of sample used on each +// step is computed as +// int(total_samples_count * subsample_portion). +// shrinkage - regularization parameter. +// Each tree prediction is multiplied on shrinkage value. + + +struct CvGBTreesParams : public CvDTreeParams +{ + CV_PROP_RW int weak_count; + CV_PROP_RW int loss_function_type; + CV_PROP_RW float subsample_portion; + CV_PROP_RW float shrinkage; + + CvGBTreesParams(); + CvGBTreesParams( int loss_function_type, int weak_count, float shrinkage, + float subsample_portion, int max_depth, bool use_surrogates ); +}; + +// DataType: CLASS CvGBTrees +// Gradient Boosting Trees (GBT) algorithm implementation. +// +// data - training dataset +// params - parameters of the CvGBTrees +// weak - array[0..(class_count-1)] of CvSeq +// for storing tree ensembles +// orig_response - original responses of the training set samples +// sum_response - predictions of the current model on the training dataset. +// this matrix is updated on every iteration. +// sum_response_tmp - predictions of the model on the training set on the next +// step. On every iteration values of sum_responses_tmp are +// computed via sum_responses values. When the current +// step is complete sum_response values become equal to +// sum_responses_tmp. +// sampleIdx - indices of samples used for training the ensemble. +// CvGBTrees training procedure takes a set of samples +// (train_data) and a set of responses (responses). +// Only pairs (train_data[i], responses[i]), where i is +// in sample_idx are used for training the ensemble. +// subsample_train - indices of samples used for training a single decision +// tree on the current step. This indices are countered +// relatively to the sample_idx, so that pairs +// (train_data[sample_idx[i]], responses[sample_idx[i]]) +// are used for training a decision tree. +// Training set is randomly splited +// in two parts (subsample_train and subsample_test) +// on every iteration accordingly to the portion parameter. +// subsample_test - relative indices of samples from the training set, +// which are not used for training a tree on the current +// step. +// missing - mask of the missing values in the training set. This +// matrix has the same size as train_data. 1 - missing +// value, 0 - not a missing value. +// class_labels - output class labels map. +// rng - random number generator. Used for splitting the +// training set. +// class_count - count of output classes. +// class_count == 1 in the case of regression, +// and > 1 in the case of classification. +// delta - Huber loss function parameter. +// base_value - start point of the gradient descent procedure. +// model prediction is +// f(x) = f_0 + sum_{i=1..weak_count-1}(f_i(x)), where +// f_0 is the base value. + + + +class CvGBTrees : public CvStatModel +{ +public: + + /* + // DataType: ENUM + // Loss functions implemented in CvGBTrees. + // + // SQUARED_LOSS + // problem: regression + // loss = (x - x')^2 + // + // ABSOLUTE_LOSS + // problem: regression + // loss = abs(x - x') + // + // HUBER_LOSS + // problem: regression + // loss = delta*( abs(x - x') - delta/2), if abs(x - x') > delta + // 1/2*(x - x')^2, if abs(x - x') <= delta, + // where delta is the alpha-quantile of pseudo responses from + // the training set. + // + // DEVIANCE_LOSS + // problem: classification + // + */ + enum {SQUARED_LOSS=0, ABSOLUTE_LOSS, HUBER_LOSS=3, DEVIANCE_LOSS}; + + + /* + // Default constructor. Creates a model only (without training). + // Should be followed by one form of the train(...) function. + // + // API + // CvGBTrees(); + + // INPUT + // OUTPUT + // RESULT + */ + CV_WRAP CvGBTrees(); + + + /* + // Full form constructor. Creates a gradient boosting model and does the + // train. + // + // API + // CvGBTrees( const CvMat* trainData, int tflag, + const CvMat* responses, const CvMat* varIdx=0, + const CvMat* sampleIdx=0, const CvMat* varType=0, + const CvMat* missingDataMask=0, + CvGBTreesParams params=CvGBTreesParams() ); + + // INPUT + // trainData - a set of input feature vectors. + // size of matrix is + // x + // or x + // depending on the tflag parameter. + // matrix values are float. + // tflag - a flag showing how do samples stored in the + // trainData matrix row by row (tflag=CV_ROW_SAMPLE) + // or column by column (tflag=CV_COL_SAMPLE). + // responses - a vector of responses corresponding to the samples + // in trainData. + // varIdx - indices of used variables. zero value means that all + // variables are active. + // sampleIdx - indices of used samples. zero value means that all + // samples from trainData are in the training set. + // varType - vector of length. gives every + // variable type CV_VAR_CATEGORICAL or CV_VAR_ORDERED. + // varType = 0 means all variables are numerical. + // missingDataMask - a mask of misiing values in trainData. + // missingDataMask = 0 means that there are no missing + // values. + // params - parameters of GTB algorithm. + // OUTPUT + // RESULT + */ + CvGBTrees( const CvMat* trainData, int tflag, + const CvMat* responses, const CvMat* varIdx=0, + const CvMat* sampleIdx=0, const CvMat* varType=0, + const CvMat* missingDataMask=0, + CvGBTreesParams params=CvGBTreesParams() ); + + + /* + // Destructor. + */ + virtual ~CvGBTrees(); + + + /* + // Gradient tree boosting model training + // + // API + // virtual bool train( const CvMat* trainData, int tflag, + const CvMat* responses, const CvMat* varIdx=0, + const CvMat* sampleIdx=0, const CvMat* varType=0, + const CvMat* missingDataMask=0, + CvGBTreesParams params=CvGBTreesParams(), + bool update=false ); + + // INPUT + // trainData - a set of input feature vectors. + // size of matrix is + // x + // or x + // depending on the tflag parameter. + // matrix values are float. + // tflag - a flag showing how do samples stored in the + // trainData matrix row by row (tflag=CV_ROW_SAMPLE) + // or column by column (tflag=CV_COL_SAMPLE). + // responses - a vector of responses corresponding to the samples + // in trainData. + // varIdx - indices of used variables. zero value means that all + // variables are active. + // sampleIdx - indices of used samples. zero value means that all + // samples from trainData are in the training set. + // varType - vector of length. gives every + // variable type CV_VAR_CATEGORICAL or CV_VAR_ORDERED. + // varType = 0 means all variables are numerical. + // missingDataMask - a mask of misiing values in trainData. + // missingDataMask = 0 means that there are no missing + // values. + // params - parameters of GTB algorithm. + // update - is not supported now. (!) + // OUTPUT + // RESULT + // Error state. + */ + virtual bool train( const CvMat* trainData, int tflag, + const CvMat* responses, const CvMat* varIdx=0, + const CvMat* sampleIdx=0, const CvMat* varType=0, + const CvMat* missingDataMask=0, + CvGBTreesParams params=CvGBTreesParams(), + bool update=false ); + + + /* + // Gradient tree boosting model training + // + // API + // virtual bool train( CvMLData* data, + CvGBTreesParams params=CvGBTreesParams(), + bool update=false ) {return false;} + + // INPUT + // data - training set. + // params - parameters of GTB algorithm. + // update - is not supported now. (!) + // OUTPUT + // RESULT + // Error state. + */ + virtual bool train( CvMLData* data, + CvGBTreesParams params=CvGBTreesParams(), + bool update=false ); + + + /* + // Response value prediction + // + // API + // virtual float predict_serial( const CvMat* sample, const CvMat* missing=0, + CvMat* weak_responses=0, CvSlice slice = CV_WHOLE_SEQ, + int k=-1 ) const; + + // INPUT + // sample - input sample of the same type as in the training set. + // missing - missing values mask. missing=0 if there are no + // missing values in sample vector. + // weak_responses - predictions of all of the trees. + // not implemented (!) + // slice - part of the ensemble used for prediction. + // slice = CV_WHOLE_SEQ when all trees are used. + // k - number of ensemble used. + // k is in {-1,0,1,..,}. + // in the case of classification problem + // ensembles are built. + // If k = -1 ordinary prediction is the result, + // otherwise function gives the prediction of the + // k-th ensemble only. + // OUTPUT + // RESULT + // Predicted value. + */ + virtual float predict_serial( const CvMat* sample, const CvMat* missing=0, + CvMat* weakResponses=0, CvSlice slice = CV_WHOLE_SEQ, + int k=-1 ) const; + + /* + // Response value prediction. + // Parallel version (in the case of TBB existence) + // + // API + // virtual float predict( const CvMat* sample, const CvMat* missing=0, + CvMat* weak_responses=0, CvSlice slice = CV_WHOLE_SEQ, + int k=-1 ) const; + + // INPUT + // sample - input sample of the same type as in the training set. + // missing - missing values mask. missing=0 if there are no + // missing values in sample vector. + // weak_responses - predictions of all of the trees. + // not implemented (!) + // slice - part of the ensemble used for prediction. + // slice = CV_WHOLE_SEQ when all trees are used. + // k - number of ensemble used. + // k is in {-1,0,1,..,}. + // in the case of classification problem + // ensembles are built. + // If k = -1 ordinary prediction is the result, + // otherwise function gives the prediction of the + // k-th ensemble only. + // OUTPUT + // RESULT + // Predicted value. + */ + virtual float predict( const CvMat* sample, const CvMat* missing=0, + CvMat* weakResponses=0, CvSlice slice = CV_WHOLE_SEQ, + int k=-1 ) const; + + /* + // Deletes all the data. + // + // API + // virtual void clear(); + + // INPUT + // OUTPUT + // delete data, weak, orig_response, sum_response, + // weak_eval, subsample_train, subsample_test, + // sample_idx, missing, lass_labels + // delta = 0.0 + // RESULT + */ + CV_WRAP virtual void clear(); + + /* + // Compute error on the train/test set. + // + // API + // virtual float calc_error( CvMLData* _data, int type, + // std::vector *resp = 0 ); + // + // INPUT + // data - dataset + // type - defines which error is to compute: train (CV_TRAIN_ERROR) or + // test (CV_TEST_ERROR). + // OUTPUT + // resp - vector of predictions + // RESULT + // Error value. + */ + virtual float calc_error( CvMLData* _data, int type, + std::vector *resp = 0 ); + + /* + // + // Write parameters of the gtb model and data. Write learned model. + // + // API + // virtual void write( CvFileStorage* fs, const char* name ) const; + // + // INPUT + // fs - file storage to read parameters from. + // name - model name. + // OUTPUT + // RESULT + */ + virtual void write( CvFileStorage* fs, const char* name ) const; + + + /* + // + // Read parameters of the gtb model and data. Read learned model. + // + // API + // virtual void read( CvFileStorage* fs, CvFileNode* node ); + // + // INPUT + // fs - file storage to read parameters from. + // node - file node. + // OUTPUT + // RESULT + */ + virtual void read( CvFileStorage* fs, CvFileNode* node ); + + + // new-style C++ interface + CV_WRAP CvGBTrees( const cv::Mat& trainData, int tflag, + const cv::Mat& responses, const cv::Mat& varIdx=cv::Mat(), + const cv::Mat& sampleIdx=cv::Mat(), const cv::Mat& varType=cv::Mat(), + const cv::Mat& missingDataMask=cv::Mat(), + CvGBTreesParams params=CvGBTreesParams() ); + + CV_WRAP virtual bool train( const cv::Mat& trainData, int tflag, + const cv::Mat& responses, const cv::Mat& varIdx=cv::Mat(), + const cv::Mat& sampleIdx=cv::Mat(), const cv::Mat& varType=cv::Mat(), + const cv::Mat& missingDataMask=cv::Mat(), + CvGBTreesParams params=CvGBTreesParams(), + bool update=false ); + + CV_WRAP virtual float predict( const cv::Mat& sample, const cv::Mat& missing=cv::Mat(), + const cv::Range& slice = cv::Range::all(), + int k=-1 ) const; + +protected: + + /* + // Compute the gradient vector components. + // + // API + // virtual void find_gradient( const int k = 0); + + // INPUT + // k - used for classification problem, determining current + // tree ensemble. + // OUTPUT + // changes components of data->responses + // which correspond to samples used for training + // on the current step. + // RESULT + */ + virtual void find_gradient( const int k = 0); + + + /* + // + // Change values in tree leaves according to the used loss function. + // + // API + // virtual void change_values(CvDTree* tree, const int k = 0); + // + // INPUT + // tree - decision tree to change. + // k - used for classification problem, determining current + // tree ensemble. + // OUTPUT + // changes 'value' fields of the trees' leaves. + // changes sum_response_tmp. + // RESULT + */ + virtual void change_values(CvDTree* tree, const int k = 0); + + + /* + // + // Find optimal constant prediction value according to the used loss + // function. + // The goal is to find a constant which gives the minimal summary loss + // on the _Idx samples. + // + // API + // virtual float find_optimal_value( const CvMat* _Idx ); + // + // INPUT + // _Idx - indices of the samples from the training set. + // OUTPUT + // RESULT + // optimal constant value. + */ + virtual float find_optimal_value( const CvMat* _Idx ); + + + /* + // + // Randomly split the whole training set in two parts according + // to params.portion. + // + // API + // virtual void do_subsample(); + // + // INPUT + // OUTPUT + // subsample_train - indices of samples used for training + // subsample_test - indices of samples used for test + // RESULT + */ + virtual void do_subsample(); + + + /* + // + // Internal recursive function giving an array of subtree tree leaves. + // + // API + // void leaves_get( CvDTreeNode** leaves, int& count, CvDTreeNode* node ); + // + // INPUT + // node - current leaf. + // OUTPUT + // count - count of leaves in the subtree. + // leaves - array of pointers to leaves. + // RESULT + */ + void leaves_get( CvDTreeNode** leaves, int& count, CvDTreeNode* node ); + + + /* + // + // Get leaves of the tree. + // + // API + // CvDTreeNode** GetLeaves( const CvDTree* dtree, int& len ); + // + // INPUT + // dtree - decision tree. + // OUTPUT + // len - count of the leaves. + // RESULT + // CvDTreeNode** - array of pointers to leaves. + */ + CvDTreeNode** GetLeaves( const CvDTree* dtree, int& len ); + + + /* + // + // Is it a regression or a classification. + // + // API + // bool problem_type(); + // + // INPUT + // OUTPUT + // RESULT + // false if it is a classification problem, + // true - if regression. + */ + virtual bool problem_type() const; + + + /* + // + // Write parameters of the gtb model. + // + // API + // virtual void write_params( CvFileStorage* fs ) const; + // + // INPUT + // fs - file storage to write parameters to. + // OUTPUT + // RESULT + */ + virtual void write_params( CvFileStorage* fs ) const; + + + /* + // + // Read parameters of the gtb model and data. + // + // API + // virtual void read_params( const cv::FileStorage& fs ); + // + // INPUT + // fs - file storage to read parameters from. + // OUTPUT + // params - parameters of the gtb model. + // data - contains information about the structure + // of the data set (count of variables, + // their types, etc.). + // class_labels - output class labels map. + // RESULT + */ + virtual void read_params( CvFileStorage* fs, CvFileNode* fnode ); + int get_len(const CvMat* mat) const; + + + CvDTreeTrainData* data; + CvGBTreesParams params; + + CvSeq** weak; + CvMat* orig_response; + CvMat* sum_response; + CvMat* sum_response_tmp; + CvMat* sample_idx; + CvMat* subsample_train; + CvMat* subsample_test; + CvMat* missing; + CvMat* class_labels; + + cv::RNG* rng; + + int class_count; + float delta; + float base_value; + +}; + + + +/****************************************************************************************\ +* Artificial Neural Networks (ANN) * +\****************************************************************************************/ + +/////////////////////////////////// Multi-Layer Perceptrons ////////////////////////////// + +struct CvANN_MLP_TrainParams +{ + CvANN_MLP_TrainParams(); + CvANN_MLP_TrainParams( CvTermCriteria term_crit, int train_method, + double param1, double param2=0 ); + ~CvANN_MLP_TrainParams(); + + enum { BACKPROP=0, RPROP=1 }; + + CV_PROP_RW CvTermCriteria term_crit; + CV_PROP_RW int train_method; + + // backpropagation parameters + CV_PROP_RW double bp_dw_scale, bp_moment_scale; + + // rprop parameters + CV_PROP_RW double rp_dw0, rp_dw_plus, rp_dw_minus, rp_dw_min, rp_dw_max; +}; + + +class CvANN_MLP : public CvStatModel +{ +public: + CV_WRAP CvANN_MLP(); + CvANN_MLP( const CvMat* layerSizes, + int activateFunc=CvANN_MLP::SIGMOID_SYM, + double fparam1=0, double fparam2=0 ); + + virtual ~CvANN_MLP(); + + virtual void create( const CvMat* layerSizes, + int activateFunc=CvANN_MLP::SIGMOID_SYM, + double fparam1=0, double fparam2=0 ); + + virtual int train( const CvMat* inputs, const CvMat* outputs, + const CvMat* sampleWeights, const CvMat* sampleIdx=0, + CvANN_MLP_TrainParams params = CvANN_MLP_TrainParams(), + int flags=0 ); + virtual float predict( const CvMat* inputs, CV_OUT CvMat* outputs ) const; + + CV_WRAP CvANN_MLP( const cv::Mat& layerSizes, + int activateFunc=CvANN_MLP::SIGMOID_SYM, + double fparam1=0, double fparam2=0 ); + + CV_WRAP virtual void create( const cv::Mat& layerSizes, + int activateFunc=CvANN_MLP::SIGMOID_SYM, + double fparam1=0, double fparam2=0 ); + + CV_WRAP virtual int train( const cv::Mat& inputs, const cv::Mat& outputs, + const cv::Mat& sampleWeights, const cv::Mat& sampleIdx=cv::Mat(), + CvANN_MLP_TrainParams params = CvANN_MLP_TrainParams(), + int flags=0 ); + + CV_WRAP virtual float predict( const cv::Mat& inputs, CV_OUT cv::Mat& outputs ) const; + + CV_WRAP virtual void clear(); + + // possible activation functions + enum { IDENTITY = 0, SIGMOID_SYM = 1, GAUSSIAN = 2 }; + + // available training flags + enum { UPDATE_WEIGHTS = 1, NO_INPUT_SCALE = 2, NO_OUTPUT_SCALE = 4 }; + + virtual void read( CvFileStorage* fs, CvFileNode* node ); + virtual void write( CvFileStorage* storage, const char* name ) const; + + int get_layer_count() { return layer_sizes ? layer_sizes->cols : 0; } + const CvMat* get_layer_sizes() { return layer_sizes; } + double* get_weights(int layer) + { + return layer_sizes && weights && + (unsigned)layer <= (unsigned)layer_sizes->cols ? weights[layer] : 0; + } + + virtual void calc_activ_func_deriv( CvMat* xf, CvMat* deriv, const double* bias ) const; + +protected: + + virtual bool prepare_to_train( const CvMat* _inputs, const CvMat* _outputs, + const CvMat* _sample_weights, const CvMat* sampleIdx, + CvVectors* _ivecs, CvVectors* _ovecs, double** _sw, int _flags ); + + // sequential random backpropagation + virtual int train_backprop( CvVectors _ivecs, CvVectors _ovecs, const double* _sw ); + + // RPROP algorithm + virtual int train_rprop( CvVectors _ivecs, CvVectors _ovecs, const double* _sw ); + + virtual void calc_activ_func( CvMat* xf, const double* bias ) const; + virtual void set_activ_func( int _activ_func=SIGMOID_SYM, + double _f_param1=0, double _f_param2=0 ); + virtual void init_weights(); + virtual void scale_input( const CvMat* _src, CvMat* _dst ) const; + virtual void scale_output( const CvMat* _src, CvMat* _dst ) const; + virtual void calc_input_scale( const CvVectors* vecs, int flags ); + virtual void calc_output_scale( const CvVectors* vecs, int flags ); + + virtual void write_params( CvFileStorage* fs ) const; + virtual void read_params( CvFileStorage* fs, CvFileNode* node ); + + CvMat* layer_sizes; + CvMat* wbuf; + CvMat* sample_weights; + double** weights; + double f_param1, f_param2; + double min_val, max_val, min_val1, max_val1; + int activ_func; + int max_count, max_buf_sz; + CvANN_MLP_TrainParams params; + cv::RNG* rng; +}; + +/****************************************************************************************\ +* Data * +\****************************************************************************************/ + +#define CV_COUNT 0 +#define CV_PORTION 1 + +struct CvTrainTestSplit +{ + CvTrainTestSplit(); + CvTrainTestSplit( int train_sample_count, bool mix = true); + CvTrainTestSplit( float train_sample_portion, bool mix = true); + + union + { + int count; + float portion; + } train_sample_part; + int train_sample_part_mode; + + bool mix; +}; + +class CvMLData +{ +public: + CvMLData(); + virtual ~CvMLData(); + + // returns: + // 0 - OK + // -1 - file can not be opened or is not correct + int read_csv( const char* filename ); + + const CvMat* get_values() const; + const CvMat* get_responses(); + const CvMat* get_missing() const; + + void set_header_lines_number( int n ); + int get_header_lines_number() const; + + void set_response_idx( int idx ); // old response become predictors, new response_idx = idx + // if idx < 0 there will be no response + int get_response_idx() const; + + void set_train_test_split( const CvTrainTestSplit * spl ); + const CvMat* get_train_sample_idx() const; + const CvMat* get_test_sample_idx() const; + void mix_train_and_test_idx(); + + const CvMat* get_var_idx(); + void chahge_var_idx( int vi, bool state ); // misspelled (saved for back compitability), + // use change_var_idx + void change_var_idx( int vi, bool state ); // state == true to set vi-variable as predictor + + const CvMat* get_var_types(); + int get_var_type( int var_idx ) const; + // following 2 methods enable to change vars type + // use these methods to assign CV_VAR_CATEGORICAL type for categorical variable + // with numerical labels; in the other cases var types are correctly determined automatically + void set_var_types( const char* str ); // str examples: + // "ord[0-17],cat[18]", "ord[0,2,4,10-12], cat[1,3,5-9,13,14]", + // "cat", "ord" (all vars are categorical/ordered) + void change_var_type( int var_idx, int type); // type in { CV_VAR_ORDERED, CV_VAR_CATEGORICAL } + + void set_delimiter( char ch ); + char get_delimiter() const; + + void set_miss_ch( char ch ); + char get_miss_ch() const; + + const std::map& get_class_labels_map() const; + +protected: + virtual void clear(); + + void str_to_flt_elem( const char* token, float& flt_elem, int& type); + void free_train_test_idx(); + + char delimiter; + char miss_ch; + //char flt_separator; + + CvMat* values; + CvMat* missing; + CvMat* var_types; + CvMat* var_idx_mask; + + CvMat* response_out; // header + CvMat* var_idx_out; // mat + CvMat* var_types_out; // mat + + int header_lines_number; + + int response_idx; + + int train_sample_count; + bool mix; + + int total_class_count; + std::map class_map; + + CvMat* train_sample_idx; + CvMat* test_sample_idx; + int* sample_idx; // data of train_sample_idx and test_sample_idx + + cv::RNG* rng; +}; + + +namespace cv +{ + +typedef CvStatModel StatModel; +typedef CvParamGrid ParamGrid; +typedef CvNormalBayesClassifier NormalBayesClassifier; +typedef CvKNearest KNearest; +typedef CvSVMParams SVMParams; +typedef CvSVMKernel SVMKernel; +typedef CvSVMSolver SVMSolver; +typedef CvSVM SVM; +typedef CvDTreeParams DTreeParams; +typedef CvMLData TrainData; +typedef CvDTree DecisionTree; +typedef CvForestTree ForestTree; +typedef CvRTParams RandomTreeParams; +typedef CvRTrees RandomTrees; +typedef CvERTreeTrainData ERTreeTRainData; +typedef CvForestERTree ERTree; +typedef CvERTrees ERTrees; +typedef CvBoostParams BoostParams; +typedef CvBoostTree BoostTree; +typedef CvBoost Boost; +typedef CvANN_MLP_TrainParams ANN_MLP_TrainParams; +typedef CvANN_MLP NeuralNet_MLP; +typedef CvGBTreesParams GradientBoostingTreeParams; +typedef CvGBTrees GradientBoostingTrees; + +template<> struct DefaultDeleter{ void operator ()(CvDTreeSplit* obj) const; }; + +} + +#endif // __cplusplus +#endif // OPENCV_OLD_ML_HPP + +/* End of file. */ diff --git a/opencv-apps/traincascade/old_ml_boost.cpp b/opencv-apps/traincascade/old_ml_boost.cpp new file mode 100644 index 0000000..a937ea6 --- /dev/null +++ b/opencv-apps/traincascade/old_ml_boost.cpp @@ -0,0 +1,2162 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#include "old_ml_precomp.hpp" + +static inline double +log_ratio( double val ) +{ + const double eps = 1e-5; + + val = MAX( val, eps ); + val = MIN( val, 1. - eps ); + return log( val/(1. - val) ); +} + + +CvBoostParams::CvBoostParams() +{ + boost_type = CvBoost::REAL; + weak_count = 100; + weight_trim_rate = 0.95; + cv_folds = 0; + max_depth = 1; +} + + +CvBoostParams::CvBoostParams( int _boost_type, int _weak_count, + double _weight_trim_rate, int _max_depth, + bool _use_surrogates, const float* _priors ) +{ + boost_type = _boost_type; + weak_count = _weak_count; + weight_trim_rate = _weight_trim_rate; + split_criteria = CvBoost::DEFAULT; + cv_folds = 0; + max_depth = _max_depth; + use_surrogates = _use_surrogates; + priors = _priors; +} + + + +///////////////////////////////// CvBoostTree /////////////////////////////////// + +CvBoostTree::CvBoostTree() +{ + ensemble = 0; +} + + +CvBoostTree::~CvBoostTree() +{ + clear(); +} + + +void +CvBoostTree::clear() +{ + CvDTree::clear(); + ensemble = 0; +} + + +bool +CvBoostTree::train( CvDTreeTrainData* _train_data, + const CvMat* _subsample_idx, CvBoost* _ensemble ) +{ + clear(); + ensemble = _ensemble; + data = _train_data; + data->shared = true; + return do_train( _subsample_idx ); +} + + +bool +CvBoostTree::train( const CvMat*, int, const CvMat*, const CvMat*, + const CvMat*, const CvMat*, const CvMat*, CvDTreeParams ) +{ + assert(0); + return false; +} + + +bool +CvBoostTree::train( CvDTreeTrainData*, const CvMat* ) +{ + assert(0); + return false; +} + + +void +CvBoostTree::scale( double _scale ) +{ + CvDTreeNode* node = root; + + // traverse the tree and scale all the node values + for(;;) + { + CvDTreeNode* parent; + for(;;) + { + node->value *= _scale; + if( !node->left ) + break; + node = node->left; + } + + for( parent = node->parent; parent && parent->right == node; + node = parent, parent = parent->parent ) + ; + + if( !parent ) + break; + + node = parent->right; + } +} + + +void +CvBoostTree::try_split_node( CvDTreeNode* node ) +{ + CvDTree::try_split_node( node ); + + if( !node->left ) + { + // if the node has not been split, + // store the responses for the corresponding training samples + double* weak_eval = ensemble->get_weak_response()->data.db; + cv::AutoBuffer inn_buf(node->sample_count); + const int* labels = data->get_cv_labels(node, inn_buf.data()); + int i, count = node->sample_count; + double value = node->value; + + for( i = 0; i < count; i++ ) + weak_eval[labels[i]] = value; + } +} + + +double +CvBoostTree::calc_node_dir( CvDTreeNode* node ) +{ + char* dir = (char*)data->direction->data.ptr; + const double* weights = ensemble->get_subtree_weights()->data.db; + int i, n = node->sample_count, vi = node->split->var_idx; + double L, R; + + assert( !node->split->inversed ); + + if( data->get_var_type(vi) >= 0 ) // split on categorical var + { + cv::AutoBuffer inn_buf(n); + const int* cat_labels = data->get_cat_var_data(node, vi, inn_buf.data()); + const int* subset = node->split->subset; + double sum = 0, sum_abs = 0; + + for( i = 0; i < n; i++ ) + { + int idx = ((cat_labels[i] == 65535) && data->is_buf_16u) ? -1 : cat_labels[i]; + double w = weights[i]; + int d = idx >= 0 ? CV_DTREE_CAT_DIR(idx,subset) : 0; + sum += d*w; sum_abs += (d & 1)*w; + dir[i] = (char)d; + } + + R = (sum_abs + sum) * 0.5; + L = (sum_abs - sum) * 0.5; + } + else // split on ordered var + { + cv::AutoBuffer inn_buf(2*n*sizeof(int)+n*sizeof(float)); + float* values_buf = (float*)inn_buf.data(); + int* sorted_indices_buf = (int*)(values_buf + n); + int* sample_indices_buf = sorted_indices_buf + n; + const float* values = 0; + const int* sorted_indices = 0; + data->get_ord_var_data( node, vi, values_buf, sorted_indices_buf, &values, &sorted_indices, sample_indices_buf ); + int split_point = node->split->ord.split_point; + int n1 = node->get_num_valid(vi); + + assert( 0 <= split_point && split_point < n1-1 ); + L = R = 0; + + for( i = 0; i <= split_point; i++ ) + { + int idx = sorted_indices[i]; + double w = weights[idx]; + dir[idx] = (char)-1; + L += w; + } + + for( ; i < n1; i++ ) + { + int idx = sorted_indices[i]; + double w = weights[idx]; + dir[idx] = (char)1; + R += w; + } + + for( ; i < n; i++ ) + dir[sorted_indices[i]] = (char)0; + } + + node->maxlr = MAX( L, R ); + return node->split->quality/(L + R); +} + + +CvDTreeSplit* +CvBoostTree::find_split_ord_class( CvDTreeNode* node, int vi, float init_quality, + CvDTreeSplit* _split, uchar* _ext_buf ) +{ + const float epsilon = FLT_EPSILON*2; + + const double* weights = ensemble->get_subtree_weights()->data.db; + int n = node->sample_count; + int n1 = node->get_num_valid(vi); + + cv::AutoBuffer inn_buf; + if( !_ext_buf ) + inn_buf.allocate(n*(3*sizeof(int)+sizeof(float))); + uchar* ext_buf = _ext_buf ? _ext_buf : inn_buf.data(); + float* values_buf = (float*)ext_buf; + int* sorted_indices_buf = (int*)(values_buf + n); + int* sample_indices_buf = sorted_indices_buf + n; + const float* values = 0; + const int* sorted_indices = 0; + data->get_ord_var_data( node, vi, values_buf, sorted_indices_buf, &values, &sorted_indices, sample_indices_buf ); + int* responses_buf = sorted_indices_buf + n; + const int* responses = data->get_class_labels( node, responses_buf ); + const double* rcw0 = weights + n; + double lcw[2] = {0,0}, rcw[2]; + int i, best_i = -1; + double best_val = init_quality; + int boost_type = ensemble->get_params().boost_type; + int split_criteria = ensemble->get_params().split_criteria; + + rcw[0] = rcw0[0]; rcw[1] = rcw0[1]; + for( i = n1; i < n; i++ ) + { + int idx = sorted_indices[i]; + double w = weights[idx]; + rcw[responses[idx]] -= w; + } + + if( split_criteria != CvBoost::GINI && split_criteria != CvBoost::MISCLASS ) + split_criteria = boost_type == CvBoost::DISCRETE ? CvBoost::MISCLASS : CvBoost::GINI; + + if( split_criteria == CvBoost::GINI ) + { + double L = 0, R = rcw[0] + rcw[1]; + double lsum2 = 0, rsum2 = rcw[0]*rcw[0] + rcw[1]*rcw[1]; + + for( i = 0; i < n1 - 1; i++ ) + { + int idx = sorted_indices[i]; + double w = weights[idx], w2 = w*w; + double lv, rv; + idx = responses[idx]; + L += w; R -= w; + lv = lcw[idx]; rv = rcw[idx]; + lsum2 += 2*lv*w + w2; + rsum2 -= 2*rv*w - w2; + lcw[idx] = lv + w; rcw[idx] = rv - w; + + if( values[i] + epsilon < values[i+1] ) + { + double val = (lsum2*R + rsum2*L)/(L*R); + if( best_val < val ) + { + best_val = val; + best_i = i; + } + } + } + } + else + { + for( i = 0; i < n1 - 1; i++ ) + { + int idx = sorted_indices[i]; + double w = weights[idx]; + idx = responses[idx]; + lcw[idx] += w; + rcw[idx] -= w; + + if( values[i] + epsilon < values[i+1] ) + { + double val = lcw[0] + rcw[1], val2 = lcw[1] + rcw[0]; + val = MAX(val, val2); + if( best_val < val ) + { + best_val = val; + best_i = i; + } + } + } + } + + CvDTreeSplit* split = 0; + if( best_i >= 0 ) + { + split = _split ? _split : data->new_split_ord( 0, 0.0f, 0, 0, 0.0f ); + split->var_idx = vi; + split->ord.c = (values[best_i] + values[best_i+1])*0.5f; + split->ord.split_point = best_i; + split->inversed = 0; + split->quality = (float)best_val; + } + return split; +} + +template +class LessThanPtr +{ +public: + bool operator()(T* a, T* b) const { return *a < *b; } +}; + +CvDTreeSplit* +CvBoostTree::find_split_cat_class( CvDTreeNode* node, int vi, float init_quality, CvDTreeSplit* _split, uchar* _ext_buf ) +{ + int ci = data->get_var_type(vi); + int n = node->sample_count; + int mi = data->cat_count->data.i[ci]; + + int base_size = (2*mi+3)*sizeof(double) + mi*sizeof(double*); + cv::AutoBuffer inn_buf((2*mi+3)*sizeof(double) + mi*sizeof(double*)); + if( !_ext_buf) + inn_buf.allocate( base_size + 2*n*sizeof(int) ); + uchar* base_buf = inn_buf.data(); + uchar* ext_buf = _ext_buf ? _ext_buf : base_buf + base_size; + + int* cat_labels_buf = (int*)ext_buf; + const int* cat_labels = data->get_cat_var_data(node, vi, cat_labels_buf); + int* responses_buf = cat_labels_buf + n; + const int* responses = data->get_class_labels(node, responses_buf); + double lcw[2]={0,0}, rcw[2]={0,0}; + + double* cjk = (double*)cv::alignPtr(base_buf,sizeof(double))+2; + const double* weights = ensemble->get_subtree_weights()->data.db; + double** dbl_ptr = (double**)(cjk + 2*mi); + int i, j, k, idx; + double L = 0, R; + double best_val = init_quality; + int best_subset = -1, subset_i; + int boost_type = ensemble->get_params().boost_type; + int split_criteria = ensemble->get_params().split_criteria; + + // init array of counters: + // c_{jk} - number of samples that have vi-th input variable = j and response = k. + for( j = -1; j < mi; j++ ) + cjk[j*2] = cjk[j*2+1] = 0; + + for( i = 0; i < n; i++ ) + { + double w = weights[i]; + j = ((cat_labels[i] == 65535) && data->is_buf_16u) ? -1 : cat_labels[i]; + k = responses[i]; + cjk[j*2 + k] += w; + } + + for( j = 0; j < mi; j++ ) + { + rcw[0] += cjk[j*2]; + rcw[1] += cjk[j*2+1]; + dbl_ptr[j] = cjk + j*2 + 1; + } + + R = rcw[0] + rcw[1]; + + if( split_criteria != CvBoost::GINI && split_criteria != CvBoost::MISCLASS ) + split_criteria = boost_type == CvBoost::DISCRETE ? CvBoost::MISCLASS : CvBoost::GINI; + + // sort rows of c_jk by increasing c_j,1 + // (i.e. by the weight of samples in j-th category that belong to class 1) + std::sort(dbl_ptr, dbl_ptr + mi, LessThanPtr()); + + for( subset_i = 0; subset_i < mi-1; subset_i++ ) + { + idx = (int)(dbl_ptr[subset_i] - cjk)/2; + const double* crow = cjk + idx*2; + double w0 = crow[0], w1 = crow[1]; + double weight = w0 + w1; + + if( weight < FLT_EPSILON ) + continue; + + lcw[0] += w0; rcw[0] -= w0; + lcw[1] += w1; rcw[1] -= w1; + + if( split_criteria == CvBoost::GINI ) + { + double lsum2 = lcw[0]*lcw[0] + lcw[1]*lcw[1]; + double rsum2 = rcw[0]*rcw[0] + rcw[1]*rcw[1]; + + L += weight; + R -= weight; + + if( L > FLT_EPSILON && R > FLT_EPSILON ) + { + double val = (lsum2*R + rsum2*L)/(L*R); + if( best_val < val ) + { + best_val = val; + best_subset = subset_i; + } + } + } + else + { + double val = lcw[0] + rcw[1]; + double val2 = lcw[1] + rcw[0]; + + val = MAX(val, val2); + if( best_val < val ) + { + best_val = val; + best_subset = subset_i; + } + } + } + + CvDTreeSplit* split = 0; + if( best_subset >= 0 ) + { + split = _split ? _split : data->new_split_cat( 0, -1.0f); + split->var_idx = vi; + split->quality = (float)best_val; + memset( split->subset, 0, (data->max_c_count + 31)/32 * sizeof(int)); + for( i = 0; i <= best_subset; i++ ) + { + idx = (int)(dbl_ptr[i] - cjk) >> 1; + split->subset[idx >> 5] |= 1 << (idx & 31); + } + } + return split; +} + + +CvDTreeSplit* +CvBoostTree::find_split_ord_reg( CvDTreeNode* node, int vi, float init_quality, CvDTreeSplit* _split, uchar* _ext_buf ) +{ + const float epsilon = FLT_EPSILON*2; + const double* weights = ensemble->get_subtree_weights()->data.db; + int n = node->sample_count; + int n1 = node->get_num_valid(vi); + + cv::AutoBuffer inn_buf; + if( !_ext_buf ) + inn_buf.allocate(2*n*(sizeof(int)+sizeof(float))); + uchar* ext_buf = _ext_buf ? _ext_buf : inn_buf.data(); + + float* values_buf = (float*)ext_buf; + int* indices_buf = (int*)(values_buf + n); + int* sample_indices_buf = indices_buf + n; + const float* values = 0; + const int* indices = 0; + data->get_ord_var_data( node, vi, values_buf, indices_buf, &values, &indices, sample_indices_buf ); + float* responses_buf = (float*)(indices_buf + n); + const float* responses = data->get_ord_responses( node, responses_buf, sample_indices_buf ); + + int i, best_i = -1; + double L = 0, R = weights[n]; + double best_val = init_quality, lsum = 0, rsum = node->value*R; + + // compensate for missing values + for( i = n1; i < n; i++ ) + { + int idx = indices[i]; + double w = weights[idx]; + rsum -= responses[idx]*w; + R -= w; + } + + // find the optimal split + for( i = 0; i < n1 - 1; i++ ) + { + int idx = indices[i]; + double w = weights[idx]; + double t = responses[idx]*w; + L += w; R -= w; + lsum += t; rsum -= t; + + if( values[i] + epsilon < values[i+1] ) + { + double val = (lsum*lsum*R + rsum*rsum*L)/(L*R); + if( best_val < val ) + { + best_val = val; + best_i = i; + } + } + } + + CvDTreeSplit* split = 0; + if( best_i >= 0 ) + { + split = _split ? _split : data->new_split_ord( 0, 0.0f, 0, 0, 0.0f ); + split->var_idx = vi; + split->ord.c = (values[best_i] + values[best_i+1])*0.5f; + split->ord.split_point = best_i; + split->inversed = 0; + split->quality = (float)best_val; + } + return split; +} + + +CvDTreeSplit* +CvBoostTree::find_split_cat_reg( CvDTreeNode* node, int vi, float init_quality, CvDTreeSplit* _split, uchar* _ext_buf ) +{ + const double* weights = ensemble->get_subtree_weights()->data.db; + int ci = data->get_var_type(vi); + int n = node->sample_count; + int mi = data->cat_count->data.i[ci]; + int base_size = (2*mi+3)*sizeof(double) + mi*sizeof(double*); + cv::AutoBuffer inn_buf(base_size); + if( !_ext_buf ) + inn_buf.allocate(base_size + n*(2*sizeof(int) + sizeof(float))); + uchar* base_buf = inn_buf.data(); + uchar* ext_buf = _ext_buf ? _ext_buf : base_buf + base_size; + + int* cat_labels_buf = (int*)ext_buf; + const int* cat_labels = data->get_cat_var_data(node, vi, cat_labels_buf); + float* responses_buf = (float*)(cat_labels_buf + n); + int* sample_indices_buf = (int*)(responses_buf + n); + const float* responses = data->get_ord_responses(node, responses_buf, sample_indices_buf); + + double* sum = (double*)cv::alignPtr(base_buf,sizeof(double)) + 1; + double* counts = sum + mi + 1; + double** sum_ptr = (double**)(counts + mi); + double L = 0, R = 0, best_val = init_quality, lsum = 0, rsum = 0; + int i, best_subset = -1, subset_i; + + for( i = -1; i < mi; i++ ) + sum[i] = counts[i] = 0; + + // calculate sum response and weight of each category of the input var + for( i = 0; i < n; i++ ) + { + int idx = ((cat_labels[i] == 65535) && data->is_buf_16u) ? -1 : cat_labels[i]; + double w = weights[i]; + double s = sum[idx] + responses[i]*w; + double nc = counts[idx] + w; + sum[idx] = s; + counts[idx] = nc; + } + + // calculate average response in each category + for( i = 0; i < mi; i++ ) + { + R += counts[i]; + rsum += sum[i]; + sum[i] = fabs(counts[i]) > DBL_EPSILON ? sum[i]/counts[i] : 0; + sum_ptr[i] = sum + i; + } + + std::sort(sum_ptr, sum_ptr + mi, LessThanPtr()); + + // revert back to unnormalized sums + // (there should be a very little loss in accuracy) + for( i = 0; i < mi; i++ ) + sum[i] *= counts[i]; + + for( subset_i = 0; subset_i < mi-1; subset_i++ ) + { + int idx = (int)(sum_ptr[subset_i] - sum); + double ni = counts[idx]; + + if( ni > FLT_EPSILON ) + { + double s = sum[idx]; + lsum += s; L += ni; + rsum -= s; R -= ni; + + if( L > FLT_EPSILON && R > FLT_EPSILON ) + { + double val = (lsum*lsum*R + rsum*rsum*L)/(L*R); + if( best_val < val ) + { + best_val = val; + best_subset = subset_i; + } + } + } + } + + CvDTreeSplit* split = 0; + if( best_subset >= 0 ) + { + split = _split ? _split : data->new_split_cat( 0, -1.0f); + split->var_idx = vi; + split->quality = (float)best_val; + memset( split->subset, 0, (data->max_c_count + 31)/32 * sizeof(int)); + for( i = 0; i <= best_subset; i++ ) + { + int idx = (int)(sum_ptr[i] - sum); + split->subset[idx >> 5] |= 1 << (idx & 31); + } + } + return split; +} + + +CvDTreeSplit* +CvBoostTree::find_surrogate_split_ord( CvDTreeNode* node, int vi, uchar* _ext_buf ) +{ + const float epsilon = FLT_EPSILON*2; + int n = node->sample_count; + cv::AutoBuffer inn_buf; + if( !_ext_buf ) + inn_buf.allocate(n*(2*sizeof(int)+sizeof(float))); + uchar* ext_buf = _ext_buf ? _ext_buf : inn_buf.data(); + float* values_buf = (float*)ext_buf; + int* indices_buf = (int*)(values_buf + n); + int* sample_indices_buf = indices_buf + n; + const float* values = 0; + const int* indices = 0; + data->get_ord_var_data( node, vi, values_buf, indices_buf, &values, &indices, sample_indices_buf ); + + const double* weights = ensemble->get_subtree_weights()->data.db; + const char* dir = (char*)data->direction->data.ptr; + int n1 = node->get_num_valid(vi); + // LL - number of samples that both the primary and the surrogate splits send to the left + // LR - ... primary split sends to the left and the surrogate split sends to the right + // RL - ... primary split sends to the right and the surrogate split sends to the left + // RR - ... both send to the right + int i, best_i = -1, best_inversed = 0; + double best_val; + double LL = 0, RL = 0, LR, RR; + double worst_val = node->maxlr; + double sum = 0, sum_abs = 0; + best_val = worst_val; + + for( i = 0; i < n1; i++ ) + { + int idx = indices[i]; + double w = weights[idx]; + int d = dir[idx]; + sum += d*w; sum_abs += (d & 1)*w; + } + + // sum_abs = R + L; sum = R - L + RR = (sum_abs + sum)*0.5; + LR = (sum_abs - sum)*0.5; + + // initially all the samples are sent to the right by the surrogate split, + // LR of them are sent to the left by primary split, and RR - to the right. + // now iteratively compute LL, LR, RL and RR for every possible surrogate split value. + for( i = 0; i < n1 - 1; i++ ) + { + int idx = indices[i]; + double w = weights[idx]; + int d = dir[idx]; + + if( d < 0 ) + { + LL += w; LR -= w; + if( LL + RR > best_val && values[i] + epsilon < values[i+1] ) + { + best_val = LL + RR; + best_i = i; best_inversed = 0; + } + } + else if( d > 0 ) + { + RL += w; RR -= w; + if( RL + LR > best_val && values[i] + epsilon < values[i+1] ) + { + best_val = RL + LR; + best_i = i; best_inversed = 1; + } + } + } + + return best_i >= 0 && best_val > node->maxlr ? data->new_split_ord( vi, + (values[best_i] + values[best_i+1])*0.5f, best_i, + best_inversed, (float)best_val ) : 0; +} + + +CvDTreeSplit* +CvBoostTree::find_surrogate_split_cat( CvDTreeNode* node, int vi, uchar* _ext_buf ) +{ + const char* dir = (char*)data->direction->data.ptr; + const double* weights = ensemble->get_subtree_weights()->data.db; + int n = node->sample_count; + int i, mi = data->cat_count->data.i[data->get_var_type(vi)]; + + int base_size = (2*mi+3)*sizeof(double); + cv::AutoBuffer inn_buf(base_size); + if( !_ext_buf ) + inn_buf.allocate(base_size + n*sizeof(int)); + uchar* ext_buf = _ext_buf ? _ext_buf : inn_buf.data(); + int* cat_labels_buf = (int*)ext_buf; + const int* cat_labels = data->get_cat_var_data(node, vi, cat_labels_buf); + + // LL - number of samples that both the primary and the surrogate splits send to the left + // LR - ... primary split sends to the left and the surrogate split sends to the right + // RL - ... primary split sends to the right and the surrogate split sends to the left + // RR - ... both send to the right + CvDTreeSplit* split = data->new_split_cat( vi, 0 ); + double best_val = 0; + double* lc = (double*)cv::alignPtr(cat_labels_buf + n, sizeof(double)) + 1; + double* rc = lc + mi + 1; + + for( i = -1; i < mi; i++ ) + lc[i] = rc[i] = 0; + + // 1. for each category calculate the weight of samples + // sent to the left (lc) and to the right (rc) by the primary split + for( i = 0; i < n; i++ ) + { + int idx = ((cat_labels[i] == 65535) && data->is_buf_16u) ? -1 : cat_labels[i]; + double w = weights[i]; + int d = dir[i]; + double sum = lc[idx] + d*w; + double sum_abs = rc[idx] + (d & 1)*w; + lc[idx] = sum; rc[idx] = sum_abs; + } + + for( i = 0; i < mi; i++ ) + { + double sum = lc[i]; + double sum_abs = rc[i]; + lc[i] = (sum_abs - sum) * 0.5; + rc[i] = (sum_abs + sum) * 0.5; + } + + // 2. now form the split. + // in each category send all the samples to the same direction as majority + for( i = 0; i < mi; i++ ) + { + double lval = lc[i], rval = rc[i]; + if( lval > rval ) + { + split->subset[i >> 5] |= 1 << (i & 31); + best_val += lval; + } + else + best_val += rval; + } + + split->quality = (float)best_val; + if( split->quality <= node->maxlr ) + cvSetRemoveByPtr( data->split_heap, split ), split = 0; + + return split; +} + + +void +CvBoostTree::calc_node_value( CvDTreeNode* node ) +{ + int i, n = node->sample_count; + const double* weights = ensemble->get_weights()->data.db; + cv::AutoBuffer inn_buf(n*(sizeof(int) + ( data->is_classifier ? sizeof(int) : sizeof(int) + sizeof(float)))); + int* labels_buf = (int*)inn_buf.data(); + const int* labels = data->get_cv_labels(node, labels_buf); + double* subtree_weights = ensemble->get_subtree_weights()->data.db; + double rcw[2] = {0,0}; + int boost_type = ensemble->get_params().boost_type; + + if( data->is_classifier ) + { + int* _responses_buf = labels_buf + n; + const int* _responses = data->get_class_labels(node, _responses_buf); + int m = data->get_num_classes(); + int* cls_count = data->counts->data.i; + for( int k = 0; k < m; k++ ) + cls_count[k] = 0; + + for( i = 0; i < n; i++ ) + { + int idx = labels[i]; + double w = weights[idx]; + int r = _responses[i]; + rcw[r] += w; + cls_count[r]++; + subtree_weights[i] = w; + } + + node->class_idx = rcw[1] > rcw[0]; + + if( boost_type == CvBoost::DISCRETE ) + { + // ignore cat_map for responses, and use {-1,1}, + // as the whole ensemble response is computes as sign(sum_i(weak_response_i) + node->value = node->class_idx*2 - 1; + } + else + { + double p = rcw[1]/(rcw[0] + rcw[1]); + assert( boost_type == CvBoost::REAL ); + + // store log-ratio of the probability + node->value = 0.5*log_ratio(p); + } + } + else + { + // in case of regression tree: + // * node value is 1/n*sum_i(Y_i), where Y_i is i-th response, + // n is the number of samples in the node. + // * node risk is the sum of squared errors: sum_i((Y_i - )^2) + double sum = 0, sum2 = 0, iw; + float* values_buf = (float*)(labels_buf + n); + int* sample_indices_buf = (int*)(values_buf + n); + const float* values = data->get_ord_responses(node, values_buf, sample_indices_buf); + + for( i = 0; i < n; i++ ) + { + int idx = labels[i]; + double w = weights[idx]/*priors[values[i] > 0]*/; + double t = values[i]; + rcw[0] += w; + subtree_weights[i] = w; + sum += t*w; + sum2 += t*t*w; + } + + iw = 1./rcw[0]; + node->value = sum*iw; + node->node_risk = sum2 - (sum*iw)*sum; + + // renormalize the risk, as in try_split_node the unweighted formula + // sqrt(risk)/n is used, rather than sqrt(risk)/sum(weights_i) + node->node_risk *= n*iw*n*iw; + } + + // store summary weights + subtree_weights[n] = rcw[0]; + subtree_weights[n+1] = rcw[1]; +} + + +void CvBoostTree::read( CvFileStorage* fs, CvFileNode* fnode, CvBoost* _ensemble, CvDTreeTrainData* _data ) +{ + CvDTree::read( fs, fnode, _data ); + ensemble = _ensemble; +} + +void CvBoostTree::read( CvFileStorage*, CvFileNode* ) +{ + assert(0); +} + +void CvBoostTree::read( CvFileStorage* _fs, CvFileNode* _node, + CvDTreeTrainData* _data ) +{ + CvDTree::read( _fs, _node, _data ); +} + + +/////////////////////////////////// CvBoost ///////////////////////////////////// + +CvBoost::CvBoost() +{ + data = 0; + weak = 0; + default_model_name = "my_boost_tree"; + + active_vars = active_vars_abs = orig_response = sum_response = weak_eval = + subsample_mask = weights = subtree_weights = 0; + have_active_cat_vars = have_subsample = false; + + clear(); +} + + +void CvBoost::prune( CvSlice slice ) +{ + if( weak && weak->total > 0 ) + { + CvSeqReader reader; + int i, count = cvSliceLength( slice, weak ); + + cvStartReadSeq( weak, &reader ); + cvSetSeqReaderPos( &reader, slice.start_index ); + + for( i = 0; i < count; i++ ) + { + CvBoostTree* w; + CV_READ_SEQ_ELEM( w, reader ); + delete w; + } + + cvSeqRemoveSlice( weak, slice ); + } +} + + +void CvBoost::clear() +{ + if( weak ) + { + prune( CV_WHOLE_SEQ ); + cvReleaseMemStorage( &weak->storage ); + } + if( data ) + delete data; + weak = 0; + data = 0; + cvReleaseMat( &active_vars ); + cvReleaseMat( &active_vars_abs ); + cvReleaseMat( &orig_response ); + cvReleaseMat( &sum_response ); + cvReleaseMat( &weak_eval ); + cvReleaseMat( &subsample_mask ); + cvReleaseMat( &weights ); + cvReleaseMat( &subtree_weights ); + + have_subsample = false; +} + + +CvBoost::~CvBoost() +{ + clear(); +} + + +CvBoost::CvBoost( const CvMat* _train_data, int _tflag, + const CvMat* _responses, const CvMat* _var_idx, + const CvMat* _sample_idx, const CvMat* _var_type, + const CvMat* _missing_mask, CvBoostParams _params ) +{ + weak = 0; + data = 0; + default_model_name = "my_boost_tree"; + + active_vars = active_vars_abs = orig_response = sum_response = weak_eval = + subsample_mask = weights = subtree_weights = 0; + + train( _train_data, _tflag, _responses, _var_idx, _sample_idx, + _var_type, _missing_mask, _params ); +} + + +bool +CvBoost::set_params( const CvBoostParams& _params ) +{ + bool ok = false; + + CV_FUNCNAME( "CvBoost::set_params" ); + + __BEGIN__; + + params = _params; + if( params.boost_type != DISCRETE && params.boost_type != REAL && + params.boost_type != LOGIT && params.boost_type != GENTLE ) + CV_ERROR( CV_StsBadArg, "Unknown/unsupported boosting type" ); + + params.weak_count = MAX( params.weak_count, 1 ); + params.weight_trim_rate = MAX( params.weight_trim_rate, 0. ); + params.weight_trim_rate = MIN( params.weight_trim_rate, 1. ); + if( params.weight_trim_rate < FLT_EPSILON ) + params.weight_trim_rate = 1.f; + + if( params.boost_type == DISCRETE && + params.split_criteria != GINI && params.split_criteria != MISCLASS ) + params.split_criteria = MISCLASS; + if( params.boost_type == REAL && + params.split_criteria != GINI && params.split_criteria != MISCLASS ) + params.split_criteria = GINI; + if( (params.boost_type == LOGIT || params.boost_type == GENTLE) && + params.split_criteria != SQERR ) + params.split_criteria = SQERR; + + ok = true; + + __END__; + + return ok; +} + + +bool +CvBoost::train( const CvMat* _train_data, int _tflag, + const CvMat* _responses, const CvMat* _var_idx, + const CvMat* _sample_idx, const CvMat* _var_type, + const CvMat* _missing_mask, + CvBoostParams _params, bool _update ) +{ + bool ok = false; + CvMemStorage* storage = 0; + + CV_FUNCNAME( "CvBoost::train" ); + + __BEGIN__; + + int i; + + set_params( _params ); + + cvReleaseMat( &active_vars ); + cvReleaseMat( &active_vars_abs ); + + if( !_update || !data ) + { + clear(); + data = new CvDTreeTrainData( _train_data, _tflag, _responses, _var_idx, + _sample_idx, _var_type, _missing_mask, _params, true, true ); + + if( data->get_num_classes() != 2 ) + CV_ERROR( CV_StsNotImplemented, + "Boosted trees can only be used for 2-class classification." ); + CV_CALL( storage = cvCreateMemStorage() ); + weak = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvBoostTree*), storage ); + storage = 0; + } + else + { + data->set_data( _train_data, _tflag, _responses, _var_idx, + _sample_idx, _var_type, _missing_mask, _params, true, true, true ); + } + + if ( (_params.boost_type == LOGIT) || (_params.boost_type == GENTLE) ) + data->do_responses_copy(); + + update_weights( 0 ); + + for( i = 0; i < params.weak_count; i++ ) + { + CvBoostTree* tree = new CvBoostTree; + if( !tree->train( data, subsample_mask, this ) ) + { + delete tree; + break; + } + //cvCheckArr( get_weak_response()); + cvSeqPush( weak, &tree ); + update_weights( tree ); + trim_weights(); + if( cvCountNonZero(subsample_mask) == 0 ) + break; + } + + if(weak->total > 0) + { + get_active_vars(); // recompute active_vars* maps and condensed_idx's in the splits. + data->is_classifier = true; + data->free_train_data(); + ok = true; + } + else + clear(); + + __END__; + + return ok; +} + +bool CvBoost::train( CvMLData* _data, + CvBoostParams _params, + bool update ) +{ + bool result = false; + + CV_FUNCNAME( "CvBoost::train" ); + + __BEGIN__; + + const CvMat* values = _data->get_values(); + const CvMat* response = _data->get_responses(); + const CvMat* missing = _data->get_missing(); + const CvMat* var_types = _data->get_var_types(); + const CvMat* train_sidx = _data->get_train_sample_idx(); + const CvMat* var_idx = _data->get_var_idx(); + + CV_CALL( result = train( values, CV_ROW_SAMPLE, response, var_idx, + train_sidx, var_types, missing, _params, update ) ); + + __END__; + + return result; +} + +void CvBoost::initialize_weights(double (&p)[2]) +{ + p[0] = 1.; + p[1] = 1.; +} + +void +CvBoost::update_weights( CvBoostTree* tree ) +{ + CV_FUNCNAME( "CvBoost::update_weights" ); + + __BEGIN__; + + int i, n = data->sample_count; + double sumw = 0.; + int step = 0; + float* fdata = 0; + int *sample_idx_buf; + const int* sample_idx = 0; + cv::AutoBuffer inn_buf; + size_t _buf_size = (params.boost_type == LOGIT) || (params.boost_type == GENTLE) ? (size_t)(data->sample_count)*sizeof(int) : 0; + if( !tree ) + _buf_size += n*sizeof(int); + else + { + if( have_subsample ) + _buf_size += data->get_length_subbuf()*(sizeof(float)+sizeof(uchar)); + } + inn_buf.allocate(_buf_size); + uchar* cur_buf_pos = inn_buf.data(); + + if ( (params.boost_type == LOGIT) || (params.boost_type == GENTLE) ) + { + step = CV_IS_MAT_CONT(data->responses_copy->type) ? + 1 : data->responses_copy->step / CV_ELEM_SIZE(data->responses_copy->type); + fdata = data->responses_copy->data.fl; + sample_idx_buf = (int*)cur_buf_pos; + cur_buf_pos = (uchar*)(sample_idx_buf + data->sample_count); + sample_idx = data->get_sample_indices( data->data_root, sample_idx_buf ); + } + CvMat* dtree_data_buf = data->buf; + size_t length_buf_row = data->get_length_subbuf(); + if( !tree ) // before training the first tree, initialize weights and other parameters + { + int* class_labels_buf = (int*)cur_buf_pos; + cur_buf_pos = (uchar*)(class_labels_buf + n); + const int* class_labels = data->get_class_labels(data->data_root, class_labels_buf); + // in case of logitboost and gentle adaboost each weak tree is a regression tree, + // so we need to convert class labels to floating-point values + + double w0 = 1./ n; + double p[2] = { 1., 1. }; + initialize_weights(p); + + cvReleaseMat( &orig_response ); + cvReleaseMat( &sum_response ); + cvReleaseMat( &weak_eval ); + cvReleaseMat( &subsample_mask ); + cvReleaseMat( &weights ); + cvReleaseMat( &subtree_weights ); + + CV_CALL( orig_response = cvCreateMat( 1, n, CV_32S )); + CV_CALL( weak_eval = cvCreateMat( 1, n, CV_64F )); + CV_CALL( subsample_mask = cvCreateMat( 1, n, CV_8U )); + CV_CALL( weights = cvCreateMat( 1, n, CV_64F )); + CV_CALL( subtree_weights = cvCreateMat( 1, n + 2, CV_64F )); + + if( data->have_priors ) + { + // compute weight scale for each class from their prior probabilities + int c1 = 0; + for( i = 0; i < n; i++ ) + c1 += class_labels[i]; + p[0] = data->priors->data.db[0]*(c1 < n ? 1./(n - c1) : 0.); + p[1] = data->priors->data.db[1]*(c1 > 0 ? 1./c1 : 0.); + p[0] /= p[0] + p[1]; + p[1] = 1. - p[0]; + } + + if (data->is_buf_16u) + { + unsigned short* labels = (unsigned short*)(dtree_data_buf->data.s + data->data_root->buf_idx*length_buf_row + + data->data_root->offset + (size_t)(data->work_var_count-1)*data->sample_count); + for( i = 0; i < n; i++ ) + { + // save original categorical responses {0,1}, convert them to {-1,1} + orig_response->data.i[i] = class_labels[i]*2 - 1; + // make all the samples active at start. + // later, in trim_weights() deactivate/reactive again some, if need + subsample_mask->data.ptr[i] = (uchar)1; + // make all the initial weights the same. + weights->data.db[i] = w0*p[class_labels[i]]; + // set the labels to find (from within weak tree learning proc) + // the particular sample weight, and where to store the response. + labels[i] = (unsigned short)i; + } + } + else + { + int* labels = dtree_data_buf->data.i + data->data_root->buf_idx*length_buf_row + + data->data_root->offset + (size_t)(data->work_var_count-1)*data->sample_count; + + for( i = 0; i < n; i++ ) + { + // save original categorical responses {0,1}, convert them to {-1,1} + orig_response->data.i[i] = class_labels[i]*2 - 1; + // make all the samples active at start. + // later, in trim_weights() deactivate/reactive again some, if need + subsample_mask->data.ptr[i] = (uchar)1; + // make all the initial weights the same. + weights->data.db[i] = w0*p[class_labels[i]]; + // set the labels to find (from within weak tree learning proc) + // the particular sample weight, and where to store the response. + labels[i] = i; + } + } + + if( params.boost_type == LOGIT ) + { + CV_CALL( sum_response = cvCreateMat( 1, n, CV_64F )); + + for( i = 0; i < n; i++ ) + { + sum_response->data.db[i] = 0; + fdata[sample_idx[i]*step] = orig_response->data.i[i] > 0 ? 2.f : -2.f; + } + + // in case of logitboost each weak tree is a regression tree. + // the target function values are recalculated for each of the trees + data->is_classifier = false; + } + else if( params.boost_type == GENTLE ) + { + for( i = 0; i < n; i++ ) + fdata[sample_idx[i]*step] = (float)orig_response->data.i[i]; + + data->is_classifier = false; + } + } + else + { + // at this moment, for all the samples that participated in the training of the most + // recent weak classifier we know the responses. For other samples we need to compute them + if( have_subsample ) + { + float* values = (float*)cur_buf_pos; + cur_buf_pos = (uchar*)(values + data->get_length_subbuf()); + uchar* missing = cur_buf_pos; + cur_buf_pos = missing + data->get_length_subbuf() * (size_t)CV_ELEM_SIZE(data->buf->type); + + CvMat _sample, _mask; + + // invert the subsample mask + cvXorS( subsample_mask, cvScalar(1.), subsample_mask ); + data->get_vectors( subsample_mask, values, missing, 0 ); + + _sample = cvMat( 1, data->var_count, CV_32F ); + _mask = cvMat( 1, data->var_count, CV_8U ); + + // run tree through all the non-processed samples + for( i = 0; i < n; i++ ) + if( subsample_mask->data.ptr[i] ) + { + _sample.data.fl = values; + _mask.data.ptr = missing; + values += _sample.cols; + missing += _mask.cols; + weak_eval->data.db[i] = tree->predict( &_sample, &_mask, true )->value; + } + } + + // now update weights and other parameters for each type of boosting + if( params.boost_type == DISCRETE ) + { + // Discrete AdaBoost: + // weak_eval[i] (=f(x_i)) is in {-1,1} + // err = sum(w_i*(f(x_i) != y_i))/sum(w_i) + // C = log((1-err)/err) + // w_i *= exp(C*(f(x_i) != y_i)) + + double C, err = 0.; + double scale[] = { 1., 0. }; + + for( i = 0; i < n; i++ ) + { + double w = weights->data.db[i]; + sumw += w; + err += w*(weak_eval->data.db[i] != orig_response->data.i[i]); + } + + if( sumw != 0 ) + err /= sumw; + C = err = -log_ratio( err ); + scale[1] = exp(err); + + sumw = 0; + for( i = 0; i < n; i++ ) + { + double w = weights->data.db[i]* + scale[weak_eval->data.db[i] != orig_response->data.i[i]]; + sumw += w; + weights->data.db[i] = w; + } + + tree->scale( C ); + } + else if( params.boost_type == REAL ) + { + // Real AdaBoost: + // weak_eval[i] = f(x_i) = 0.5*log(p(x_i)/(1-p(x_i))), p(x_i)=P(y=1|x_i) + // w_i *= exp(-y_i*f(x_i)) + + for( i = 0; i < n; i++ ) + weak_eval->data.db[i] *= -orig_response->data.i[i]; + + cvExp( weak_eval, weak_eval ); + + for( i = 0; i < n; i++ ) + { + double w = weights->data.db[i]*weak_eval->data.db[i]; + sumw += w; + weights->data.db[i] = w; + } + } + else if( params.boost_type == LOGIT ) + { + // LogitBoost: + // weak_eval[i] = f(x_i) in [-z_max,z_max] + // sum_response = F(x_i). + // F(x_i) += 0.5*f(x_i) + // p(x_i) = exp(F(x_i))/(exp(F(x_i)) + exp(-F(x_i))=1/(1+exp(-2*F(x_i))) + // reuse weak_eval: weak_eval[i] <- p(x_i) + // w_i = p(x_i)*1(1 - p(x_i)) + // z_i = ((y_i+1)/2 - p(x_i))/(p(x_i)*(1 - p(x_i))) + // store z_i to the data->data_root as the new target responses + + const double lb_weight_thresh = FLT_EPSILON; + const double lb_z_max = 10.; + /*float* responses_buf = data->get_resp_float_buf(); + const float* responses = 0; + data->get_ord_responses(data->data_root, responses_buf, &responses);*/ + + /*if( weak->total == 7 ) + putchar('*');*/ + + for( i = 0; i < n; i++ ) + { + double s = sum_response->data.db[i] + 0.5*weak_eval->data.db[i]; + sum_response->data.db[i] = s; + weak_eval->data.db[i] = -2*s; + } + + cvExp( weak_eval, weak_eval ); + + for( i = 0; i < n; i++ ) + { + double p = 1./(1. + weak_eval->data.db[i]); + double w = p*(1 - p), z; + w = MAX( w, lb_weight_thresh ); + weights->data.db[i] = w; + sumw += w; + if( orig_response->data.i[i] > 0 ) + { + z = 1./p; + fdata[sample_idx[i]*step] = (float)MIN(z, lb_z_max); + } + else + { + z = 1./(1-p); + fdata[sample_idx[i]*step] = (float)-MIN(z, lb_z_max); + } + } + } + else + { + // Gentle AdaBoost: + // weak_eval[i] = f(x_i) in [-1,1] + // w_i *= exp(-y_i*f(x_i)) + assert( params.boost_type == GENTLE ); + + for( i = 0; i < n; i++ ) + weak_eval->data.db[i] *= -orig_response->data.i[i]; + + cvExp( weak_eval, weak_eval ); + + for( i = 0; i < n; i++ ) + { + double w = weights->data.db[i] * weak_eval->data.db[i]; + weights->data.db[i] = w; + sumw += w; + } + } + } + + // renormalize weights + if( sumw > FLT_EPSILON ) + { + sumw = 1./sumw; + for( i = 0; i < n; ++i ) + weights->data.db[i] *= sumw; + } + + __END__; +} + + +void +CvBoost::trim_weights() +{ + //CV_FUNCNAME( "CvBoost::trim_weights" ); + + __BEGIN__; + + int i, count = data->sample_count, nz_count = 0; + double sum, threshold; + + if( params.weight_trim_rate <= 0. || params.weight_trim_rate >= 1. ) + EXIT; + + // use weak_eval as temporary buffer for sorted weights + cvCopy( weights, weak_eval ); + + std::sort(weak_eval->data.db, weak_eval->data.db + count); + + // as weight trimming occurs immediately after updating the weights, + // where they are renormalized, we assume that the weight sum = 1. + sum = 1. - params.weight_trim_rate; + + for( i = 0; i < count; i++ ) + { + double w = weak_eval->data.db[i]; + if( sum <= 0 ) + break; + sum -= w; + } + + threshold = i < count ? weak_eval->data.db[i] : DBL_MAX; + + for( i = 0; i < count; i++ ) + { + double w = weights->data.db[i]; + int f = w >= threshold; + subsample_mask->data.ptr[i] = (uchar)f; + nz_count += f; + } + + have_subsample = nz_count < count; + + __END__; +} + + +const CvMat* +CvBoost::get_active_vars( bool absolute_idx ) +{ + CvMat* mask = 0; + CvMat* inv_map = 0; + CvMat* result = 0; + + CV_FUNCNAME( "CvBoost::get_active_vars" ); + + __BEGIN__; + + if( !weak ) + CV_ERROR( CV_StsError, "The boosted tree ensemble has not been trained yet" ); + + if( !active_vars || !active_vars_abs ) + { + CvSeqReader reader; + int i, j, nactive_vars; + CvBoostTree* wtree; + const CvDTreeNode* node; + + assert(!active_vars && !active_vars_abs); + mask = cvCreateMat( 1, data->var_count, CV_8U ); + inv_map = cvCreateMat( 1, data->var_count, CV_32S ); + cvZero( mask ); + cvSet( inv_map, cvScalar(-1) ); + + // first pass: compute the mask of used variables + cvStartReadSeq( weak, &reader ); + for( i = 0; i < weak->total; i++ ) + { + CV_READ_SEQ_ELEM(wtree, reader); + + node = wtree->get_root(); + assert( node != 0 ); + for(;;) + { + const CvDTreeNode* parent; + for(;;) + { + CvDTreeSplit* split = node->split; + for( ; split != 0; split = split->next ) + mask->data.ptr[split->var_idx] = 1; + if( !node->left ) + break; + node = node->left; + } + + for( parent = node->parent; parent && parent->right == node; + node = parent, parent = parent->parent ) + ; + + if( !parent ) + break; + + node = parent->right; + } + } + + nactive_vars = cvCountNonZero(mask); + + //if ( nactive_vars > 0 ) + { + active_vars = cvCreateMat( 1, nactive_vars, CV_32S ); + active_vars_abs = cvCreateMat( 1, nactive_vars, CV_32S ); + + have_active_cat_vars = false; + + for( i = j = 0; i < data->var_count; i++ ) + { + if( mask->data.ptr[i] ) + { + active_vars->data.i[j] = i; + active_vars_abs->data.i[j] = data->var_idx ? data->var_idx->data.i[i] : i; + inv_map->data.i[i] = j; + if( data->var_type->data.i[i] >= 0 ) + have_active_cat_vars = true; + j++; + } + } + + + // second pass: now compute the condensed indices + cvStartReadSeq( weak, &reader ); + for( i = 0; i < weak->total; i++ ) + { + CV_READ_SEQ_ELEM(wtree, reader); + node = wtree->get_root(); + for(;;) + { + const CvDTreeNode* parent; + for(;;) + { + CvDTreeSplit* split = node->split; + for( ; split != 0; split = split->next ) + { + split->condensed_idx = inv_map->data.i[split->var_idx]; + assert( split->condensed_idx >= 0 ); + } + + if( !node->left ) + break; + node = node->left; + } + + for( parent = node->parent; parent && parent->right == node; + node = parent, parent = parent->parent ) + ; + + if( !parent ) + break; + + node = parent->right; + } + } + } + } + + result = absolute_idx ? active_vars_abs : active_vars; + + __END__; + + cvReleaseMat( &mask ); + cvReleaseMat( &inv_map ); + + return result; +} + + +float +CvBoost::predict( const CvMat* _sample, const CvMat* _missing, + CvMat* weak_responses, CvSlice slice, + bool raw_mode, bool return_sum ) const +{ + float value = -FLT_MAX; + + CvSeqReader reader; + double sum = 0; + int wstep = 0; + const float* sample_data; + + if( !weak ) + CV_Error( CV_StsError, "The boosted tree ensemble has not been trained yet" ); + + if( !CV_IS_MAT(_sample) || CV_MAT_TYPE(_sample->type) != CV_32FC1 || + (_sample->cols != 1 && _sample->rows != 1) || + (_sample->cols + _sample->rows - 1 != data->var_all && !raw_mode) || + (active_vars && _sample->cols + _sample->rows - 1 != active_vars->cols && raw_mode) ) + CV_Error( CV_StsBadArg, + "the input sample must be 1d floating-point vector with the same " + "number of elements as the total number of variables or " + "as the number of variables used for training" ); + + if( _missing ) + { + if( !CV_IS_MAT(_missing) || !CV_IS_MASK_ARR(_missing) || + !CV_ARE_SIZES_EQ(_missing, _sample) ) + CV_Error( CV_StsBadArg, + "the missing data mask must be 8-bit vector of the same size as input sample" ); + } + + int i, weak_count = cvSliceLength( slice, weak ); + if( weak_count >= weak->total ) + { + weak_count = weak->total; + slice.start_index = 0; + } + + if( weak_responses ) + { + if( !CV_IS_MAT(weak_responses) || + CV_MAT_TYPE(weak_responses->type) != CV_32FC1 || + (weak_responses->cols != 1 && weak_responses->rows != 1) || + weak_responses->cols + weak_responses->rows - 1 != weak_count ) + CV_Error( CV_StsBadArg, + "The output matrix of weak classifier responses must be valid " + "floating-point vector of the same number of components as the length of input slice" ); + wstep = CV_IS_MAT_CONT(weak_responses->type) ? 1 : weak_responses->step/sizeof(float); + } + + int var_count = active_vars->cols; + const int* vtype = data->var_type->data.i; + const int* cmap = data->cat_map->data.i; + const int* cofs = data->cat_ofs->data.i; + + cv::Mat sample = cv::cvarrToMat(_sample); + cv::Mat missing; + if(!_missing) + missing = cv::cvarrToMat(_missing); + + // if need, preprocess the input vector + if( !raw_mode ) + { + int sstep, mstep = 0; + const float* src_sample; + const uchar* src_mask = 0; + float* dst_sample; + uchar* dst_mask; + const int* vidx = active_vars->data.i; + const int* vidx_abs = active_vars_abs->data.i; + bool have_mask = _missing != 0; + + sample = cv::Mat(1, var_count, CV_32FC1); + missing = cv::Mat(1, var_count, CV_8UC1); + + dst_sample = sample.ptr(); + dst_mask = missing.ptr(); + + src_sample = _sample->data.fl; + sstep = CV_IS_MAT_CONT(_sample->type) ? 1 : _sample->step/sizeof(src_sample[0]); + + if( _missing ) + { + src_mask = _missing->data.ptr; + mstep = CV_IS_MAT_CONT(_missing->type) ? 1 : _missing->step; + } + + for( i = 0; i < var_count; i++ ) + { + int idx = vidx[i], idx_abs = vidx_abs[i]; + float val = src_sample[idx_abs*sstep]; + int ci = vtype[idx]; + uchar m = src_mask ? src_mask[idx_abs*mstep] : (uchar)0; + + if( ci >= 0 ) + { + int a = cofs[ci], b = (ci+1 >= data->cat_ofs->cols) ? data->cat_map->cols : cofs[ci+1], + c = a; + int ival = cvRound(val); + if ( (ival != val) && (!m) ) + CV_Error( CV_StsBadArg, + "one of input categorical variable is not an integer" ); + + while( a < b ) + { + c = (a + b) >> 1; + if( ival < cmap[c] ) + b = c; + else if( ival > cmap[c] ) + a = c+1; + else + break; + } + + if( c < 0 || ival != cmap[c] ) + { + m = 1; + have_mask = true; + } + else + { + val = (float)(c - cofs[ci]); + } + } + + dst_sample[i] = val; + dst_mask[i] = m; + } + + if( !have_mask ) + missing.release(); + } + else + { + if( !CV_IS_MAT_CONT(_sample->type & (_missing ? _missing->type : -1)) ) + CV_Error( CV_StsBadArg, "In raw mode the input vectors must be continuous" ); + } + + cvStartReadSeq( weak, &reader ); + cvSetSeqReaderPos( &reader, slice.start_index ); + + sample_data = sample.ptr(); + + if( !have_active_cat_vars && missing.empty() && !weak_responses ) + { + for( i = 0; i < weak_count; i++ ) + { + CvBoostTree* wtree; + const CvDTreeNode* node; + CV_READ_SEQ_ELEM( wtree, reader ); + + node = wtree->get_root(); + while( node->left ) + { + CvDTreeSplit* split = node->split; + int vi = split->condensed_idx; + float val = sample_data[vi]; + int dir = val <= split->ord.c ? -1 : 1; + if( split->inversed ) + dir = -dir; + node = dir < 0 ? node->left : node->right; + } + sum += node->value; + } + } + else + { + const int* avars = active_vars->data.i; + const uchar* m = !missing.empty() ? missing.ptr() : 0; + + // full-featured version + for( i = 0; i < weak_count; i++ ) + { + CvBoostTree* wtree; + const CvDTreeNode* node; + CV_READ_SEQ_ELEM( wtree, reader ); + + node = wtree->get_root(); + while( node->left ) + { + const CvDTreeSplit* split = node->split; + int dir = 0; + for( ; !dir && split != 0; split = split->next ) + { + int vi = split->condensed_idx; + int ci = vtype[avars[vi]]; + float val = sample_data[vi]; + if( m && m[vi] ) + continue; + if( ci < 0 ) // ordered + dir = val <= split->ord.c ? -1 : 1; + else // categorical + { + int c = cvRound(val); + dir = CV_DTREE_CAT_DIR(c, split->subset); + } + if( split->inversed ) + dir = -dir; + } + + if( !dir ) + { + int diff = node->right->sample_count - node->left->sample_count; + dir = diff < 0 ? -1 : 1; + } + node = dir < 0 ? node->left : node->right; + } + if( weak_responses ) + weak_responses->data.fl[i*wstep] = (float)node->value; + sum += node->value; + } + } + + if( return_sum ) + value = (float)sum; + else + { + int cls_idx = sum >= 0; + if( raw_mode ) + value = (float)cls_idx; + else + value = (float)cmap[cofs[vtype[data->var_count]] + cls_idx]; + } + + return value; +} + +float CvBoost::calc_error( CvMLData* _data, int type, std::vector *resp ) +{ + float err = 0; + const CvMat* values = _data->get_values(); + const CvMat* response = _data->get_responses(); + const CvMat* missing = _data->get_missing(); + const CvMat* sample_idx = (type == CV_TEST_ERROR) ? _data->get_test_sample_idx() : _data->get_train_sample_idx(); + const CvMat* var_types = _data->get_var_types(); + int* sidx = sample_idx ? sample_idx->data.i : 0; + int r_step = CV_IS_MAT_CONT(response->type) ? + 1 : response->step / CV_ELEM_SIZE(response->type); + bool is_classifier = var_types->data.ptr[var_types->cols-1] == CV_VAR_CATEGORICAL; + int sample_count = sample_idx ? sample_idx->cols : 0; + sample_count = (type == CV_TRAIN_ERROR && sample_count == 0) ? values->rows : sample_count; + float* pred_resp = 0; + if( resp && (sample_count > 0) ) + { + resp->resize( sample_count ); + pred_resp = &((*resp)[0]); + } + if ( is_classifier ) + { + for( int i = 0; i < sample_count; i++ ) + { + CvMat sample, miss; + int si = sidx ? sidx[i] : i; + cvGetRow( values, &sample, si ); + if( missing ) + cvGetRow( missing, &miss, si ); + float r = (float)predict( &sample, missing ? &miss : 0 ); + if( pred_resp ) + pred_resp[i] = r; + int d = fabs((double)r - response->data.fl[si*r_step]) <= FLT_EPSILON ? 0 : 1; + err += d; + } + err = sample_count ? err / (float)sample_count * 100 : -FLT_MAX; + } + else + { + for( int i = 0; i < sample_count; i++ ) + { + CvMat sample, miss; + int si = sidx ? sidx[i] : i; + cvGetRow( values, &sample, si ); + if( missing ) + cvGetRow( missing, &miss, si ); + float r = (float)predict( &sample, missing ? &miss : 0 ); + if( pred_resp ) + pred_resp[i] = r; + float d = r - response->data.fl[si*r_step]; + err += d*d; + } + err = sample_count ? err / (float)sample_count : -FLT_MAX; + } + return err; +} + +void CvBoost::write_params( CvFileStorage* fs ) const +{ + const char* boost_type_str = + params.boost_type == DISCRETE ? "DiscreteAdaboost" : + params.boost_type == REAL ? "RealAdaboost" : + params.boost_type == LOGIT ? "LogitBoost" : + params.boost_type == GENTLE ? "GentleAdaboost" : 0; + + const char* split_crit_str = + params.split_criteria == DEFAULT ? "Default" : + params.split_criteria == GINI ? "Gini" : + params.boost_type == MISCLASS ? "Misclassification" : + params.boost_type == SQERR ? "SquaredErr" : 0; + + if( boost_type_str ) + cvWriteString( fs, "boosting_type", boost_type_str ); + else + cvWriteInt( fs, "boosting_type", params.boost_type ); + + if( split_crit_str ) + cvWriteString( fs, "splitting_criteria", split_crit_str ); + else + cvWriteInt( fs, "splitting_criteria", params.split_criteria ); + + cvWriteInt( fs, "ntrees", weak->total ); + cvWriteReal( fs, "weight_trimming_rate", params.weight_trim_rate ); + + data->write_params( fs ); +} + + +void CvBoost::read_params( CvFileStorage* fs, CvFileNode* fnode ) +{ + CV_FUNCNAME( "CvBoost::read_params" ); + + __BEGIN__; + + CvFileNode* temp; + + if( !fnode || !CV_NODE_IS_MAP(fnode->tag) ) + return; + + data = new CvDTreeTrainData(); + CV_CALL( data->read_params(fs, fnode)); + data->shared = true; + + params.max_depth = data->params.max_depth; + params.min_sample_count = data->params.min_sample_count; + params.max_categories = data->params.max_categories; + params.priors = data->params.priors; + params.regression_accuracy = data->params.regression_accuracy; + params.use_surrogates = data->params.use_surrogates; + + temp = cvGetFileNodeByName( fs, fnode, "boosting_type" ); + if( !temp ) + return; + + if( temp && CV_NODE_IS_STRING(temp->tag) ) + { + const char* boost_type_str = cvReadString( temp, "" ); + params.boost_type = strcmp( boost_type_str, "DiscreteAdaboost" ) == 0 ? DISCRETE : + strcmp( boost_type_str, "RealAdaboost" ) == 0 ? REAL : + strcmp( boost_type_str, "LogitBoost" ) == 0 ? LOGIT : + strcmp( boost_type_str, "GentleAdaboost" ) == 0 ? GENTLE : -1; + } + else + params.boost_type = cvReadInt( temp, -1 ); + + if( params.boost_type < DISCRETE || params.boost_type > GENTLE ) + CV_ERROR( CV_StsBadArg, "Unknown boosting type" ); + + temp = cvGetFileNodeByName( fs, fnode, "splitting_criteria" ); + if( temp && CV_NODE_IS_STRING(temp->tag) ) + { + const char* split_crit_str = cvReadString( temp, "" ); + params.split_criteria = strcmp( split_crit_str, "Default" ) == 0 ? DEFAULT : + strcmp( split_crit_str, "Gini" ) == 0 ? GINI : + strcmp( split_crit_str, "Misclassification" ) == 0 ? MISCLASS : + strcmp( split_crit_str, "SquaredErr" ) == 0 ? SQERR : -1; + } + else + params.split_criteria = cvReadInt( temp, -1 ); + + if( params.split_criteria < DEFAULT || params.boost_type > SQERR ) + CV_ERROR( CV_StsBadArg, "Unknown boosting type" ); + + params.weak_count = cvReadIntByName( fs, fnode, "ntrees" ); + params.weight_trim_rate = cvReadRealByName( fs, fnode, "weight_trimming_rate", 0. ); + + __END__; +} + + + +void +CvBoost::read( CvFileStorage* fs, CvFileNode* node ) +{ + CV_FUNCNAME( "CvBoost::read" ); + + __BEGIN__; + + CvSeqReader reader; + CvFileNode* trees_fnode; + CvMemStorage* storage; + int i, ntrees; + + clear(); + read_params( fs, node ); + + if( !data ) + EXIT; + + trees_fnode = cvGetFileNodeByName( fs, node, "trees" ); + if( !trees_fnode || !CV_NODE_IS_SEQ(trees_fnode->tag) ) + CV_ERROR( CV_StsParseError, " tag is missing" ); + + cvStartReadSeq( trees_fnode->data.seq, &reader ); + ntrees = trees_fnode->data.seq->total; + + if( ntrees != params.weak_count ) + CV_ERROR( CV_StsUnmatchedSizes, + "The number of trees stored does not match tag value" ); + + CV_CALL( storage = cvCreateMemStorage() ); + weak = cvCreateSeq( 0, sizeof(CvSeq), sizeof(CvBoostTree*), storage ); + + for( i = 0; i < ntrees; i++ ) + { + CvBoostTree* tree = new CvBoostTree(); + CV_CALL(tree->read( fs, (CvFileNode*)reader.ptr, this, data )); + CV_NEXT_SEQ_ELEM( reader.seq->elem_size, reader ); + cvSeqPush( weak, &tree ); + } + get_active_vars(); + + __END__; +} + + +void +CvBoost::write( CvFileStorage* fs, const char* name ) const +{ + CV_FUNCNAME( "CvBoost::write" ); + + __BEGIN__; + + CvSeqReader reader; + int i; + + cvStartWriteStruct( fs, name, CV_NODE_MAP, CV_TYPE_NAME_ML_BOOSTING ); + + if( !weak ) + CV_ERROR( CV_StsBadArg, "The classifier has not been trained yet" ); + + write_params( fs ); + cvStartWriteStruct( fs, "trees", CV_NODE_SEQ ); + + cvStartReadSeq( weak, &reader ); + + for( i = 0; i < weak->total; i++ ) + { + CvBoostTree* tree; + CV_READ_SEQ_ELEM( tree, reader ); + cvStartWriteStruct( fs, 0, CV_NODE_MAP ); + tree->write( fs ); + cvEndWriteStruct( fs ); + } + + cvEndWriteStruct( fs ); + cvEndWriteStruct( fs ); + + __END__; +} + + +CvMat* +CvBoost::get_weights() +{ + return weights; +} + + +CvMat* +CvBoost::get_subtree_weights() +{ + return subtree_weights; +} + + +CvMat* +CvBoost::get_weak_response() +{ + return weak_eval; +} + + +const CvBoostParams& +CvBoost::get_params() const +{ + return params; +} + +CvSeq* CvBoost::get_weak_predictors() +{ + return weak; +} + +const CvDTreeTrainData* CvBoost::get_data() const +{ + return data; +} + +using namespace cv; + +CvBoost::CvBoost( const Mat& _train_data, int _tflag, + const Mat& _responses, const Mat& _var_idx, + const Mat& _sample_idx, const Mat& _var_type, + const Mat& _missing_mask, + CvBoostParams _params ) +{ + weak = 0; + data = 0; + default_model_name = "my_boost_tree"; + active_vars = active_vars_abs = orig_response = sum_response = weak_eval = + subsample_mask = weights = subtree_weights = 0; + + train( _train_data, _tflag, _responses, _var_idx, _sample_idx, + _var_type, _missing_mask, _params ); +} + + +bool +CvBoost::train( const Mat& _train_data, int _tflag, + const Mat& _responses, const Mat& _var_idx, + const Mat& _sample_idx, const Mat& _var_type, + const Mat& _missing_mask, + CvBoostParams _params, bool _update ) +{ + train_data_hdr = cvMat(_train_data); + train_data_mat = _train_data; + responses_hdr = cvMat(_responses); + responses_mat = _responses; + + CvMat vidx = cvMat(_var_idx), sidx = cvMat(_sample_idx), vtype = cvMat(_var_type), mmask = cvMat(_missing_mask); + + return train(&train_data_hdr, _tflag, &responses_hdr, vidx.data.ptr ? &vidx : 0, + sidx.data.ptr ? &sidx : 0, vtype.data.ptr ? &vtype : 0, + mmask.data.ptr ? &mmask : 0, _params, _update); +} + +float +CvBoost::predict( const Mat& _sample, const Mat& _missing, + const Range& slice, bool raw_mode, bool return_sum ) const +{ + CvMat sample = cvMat(_sample), mmask = cvMat(_missing); + /*if( weak_responses ) + { + int weak_count = cvSliceLength( slice, weak ); + if( weak_count >= weak->total ) + { + weak_count = weak->total; + slice.start_index = 0; + } + + if( !(weak_responses->data && weak_responses->type() == CV_32FC1 && + (weak_responses->cols == 1 || weak_responses->rows == 1) && + weak_responses->cols + weak_responses->rows - 1 == weak_count) ) + weak_responses->create(weak_count, 1, CV_32FC1); + pwr = &(wr = *weak_responses); + }*/ + return predict(&sample, _missing.empty() ? 0 : &mmask, 0, + slice == Range::all() ? CV_WHOLE_SEQ : cvSlice(slice.start, slice.end), + raw_mode, return_sum); +} + +/* End of file. */ diff --git a/opencv-apps/traincascade/old_ml_data.cpp b/opencv-apps/traincascade/old_ml_data.cpp new file mode 100644 index 0000000..d221dcb --- /dev/null +++ b/opencv-apps/traincascade/old_ml_data.cpp @@ -0,0 +1,792 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#include "old_ml_precomp.hpp" +#include + +#define MISS_VAL FLT_MAX +#define CV_VAR_MISS 0 + +CvTrainTestSplit::CvTrainTestSplit() +{ + train_sample_part_mode = CV_COUNT; + train_sample_part.count = -1; + mix = false; +} + +CvTrainTestSplit::CvTrainTestSplit( int _train_sample_count, bool _mix ) +{ + train_sample_part_mode = CV_COUNT; + train_sample_part.count = _train_sample_count; + mix = _mix; +} + +CvTrainTestSplit::CvTrainTestSplit( float _train_sample_portion, bool _mix ) +{ + train_sample_part_mode = CV_PORTION; + train_sample_part.portion = _train_sample_portion; + mix = _mix; +} + +//////////////// + +CvMLData::CvMLData() +{ + values = missing = var_types = var_idx_mask = response_out = var_idx_out = var_types_out = 0; + train_sample_idx = test_sample_idx = 0; + header_lines_number = 0; + sample_idx = 0; + response_idx = -1; + + train_sample_count = -1; + + delimiter = ','; + miss_ch = '?'; + //flt_separator = '.'; + + rng = &cv::theRNG(); +} + +CvMLData::~CvMLData() +{ + clear(); +} + +void CvMLData::free_train_test_idx() +{ + cvReleaseMat( &train_sample_idx ); + cvReleaseMat( &test_sample_idx ); + sample_idx = 0; +} + +void CvMLData::clear() +{ + class_map.clear(); + + cvReleaseMat( &values ); + cvReleaseMat( &missing ); + cvReleaseMat( &var_types ); + cvReleaseMat( &var_idx_mask ); + + cvReleaseMat( &response_out ); + cvReleaseMat( &var_idx_out ); + cvReleaseMat( &var_types_out ); + + free_train_test_idx(); + + total_class_count = 0; + + response_idx = -1; + + train_sample_count = -1; +} + + +void CvMLData::set_header_lines_number( int idx ) +{ + header_lines_number = std::max(0, idx); +} + +int CvMLData::get_header_lines_number() const +{ + return header_lines_number; +} + +static char *fgets_chomp(char *str, int n, FILE *stream) +{ + char *head = fgets(str, n, stream); + if( head ) + { + for(char *tail = head + strlen(head) - 1; tail >= head; --tail) + { + if( *tail != '\r' && *tail != '\n' ) + break; + *tail = '\0'; + } + } + return head; +} + + +int CvMLData::read_csv(const char* filename) +{ + const int M = 1000000; + const char str_delimiter[3] = { ' ', delimiter, '\0' }; + FILE* file = 0; + CvMemStorage* storage; + CvSeq* seq; + char *ptr; + float* el_ptr; + CvSeqReader reader; + int cols_count = 0; + uchar *var_types_ptr = 0; + + clear(); + + file = fopen( filename, "rt" ); + + if( !file ) + return -1; + + std::vector _buf(M); + char* buf = &_buf[0]; + + // skip header lines + for( int i = 0; i < header_lines_number; i++ ) + { + if( fgets( buf, M, file ) == 0 ) + { + fclose(file); + return -1; + } + } + + // read the first data line and determine the number of variables + if( !fgets_chomp( buf, M, file )) + { + fclose(file); + return -1; + } + + ptr = buf; + while( *ptr == ' ' ) + ptr++; + for( ; *ptr != '\0'; ) + { + if(*ptr == delimiter || *ptr == ' ') + { + cols_count++; + ptr++; + while( *ptr == ' ' ) ptr++; + } + else + ptr++; + } + + cols_count++; + + if ( cols_count == 0) + { + fclose(file); + return -1; + } + + // create temporary memory storage to store the whole database + el_ptr = new float[cols_count]; + storage = cvCreateMemStorage(); + seq = cvCreateSeq( 0, sizeof(*seq), cols_count*sizeof(float), storage ); + + var_types = cvCreateMat( 1, cols_count, CV_8U ); + cvZero( var_types ); + var_types_ptr = var_types->data.ptr; + + for(;;) + { + char *token = NULL; + int type; + token = strtok(buf, str_delimiter); + if (!token) + break; + for (int i = 0; i < cols_count-1; i++) + { + str_to_flt_elem( token, el_ptr[i], type); + var_types_ptr[i] |= type; + token = strtok(NULL, str_delimiter); + if (!token) + { + fclose(file); + delete [] el_ptr; + return -1; + } + } + str_to_flt_elem( token, el_ptr[cols_count-1], type); + var_types_ptr[cols_count-1] |= type; + cvSeqPush( seq, el_ptr ); + if( !fgets_chomp( buf, M, file ) ) + break; + } + fclose(file); + + values = cvCreateMat( seq->total, cols_count, CV_32FC1 ); + missing = cvCreateMat( seq->total, cols_count, CV_8U ); + var_idx_mask = cvCreateMat( 1, values->cols, CV_8UC1 ); + cvSet( var_idx_mask, cvRealScalar(1) ); + train_sample_count = seq->total; + + cvStartReadSeq( seq, &reader ); + for(int i = 0; i < seq->total; i++ ) + { + const float* sdata = (float*)reader.ptr; + float* ddata = values->data.fl + cols_count*i; + uchar* dm = missing->data.ptr + cols_count*i; + + for( int j = 0; j < cols_count; j++ ) + { + ddata[j] = sdata[j]; + dm[j] = ( fabs( MISS_VAL - sdata[j] ) <= FLT_EPSILON ); + } + CV_NEXT_SEQ_ELEM( seq->elem_size, reader ); + } + + if ( cvNorm( missing, 0, CV_L1 ) <= FLT_EPSILON ) + cvReleaseMat( &missing ); + + cvReleaseMemStorage( &storage ); + delete []el_ptr; + return 0; +} + +const CvMat* CvMLData::get_values() const +{ + return values; +} + +const CvMat* CvMLData::get_missing() const +{ + CV_FUNCNAME( "CvMLData::get_missing" ); + __BEGIN__; + + if ( !values ) + CV_ERROR( CV_StsInternal, "data is empty" ); + + __END__; + + return missing; +} + +const std::map& CvMLData::get_class_labels_map() const +{ + return class_map; +} + +void CvMLData::str_to_flt_elem( const char* token, float& flt_elem, int& type) +{ + + char* stopstring = NULL; + flt_elem = (float)strtod( token, &stopstring ); + assert( stopstring ); + type = CV_VAR_ORDERED; + if ( *stopstring == miss_ch && strlen(stopstring) == 1 ) // missed value + { + flt_elem = MISS_VAL; + type = CV_VAR_MISS; + } + else + { + if ( (*stopstring != 0) && (*stopstring != '\n') && (strcmp(stopstring, "\r\n") != 0) ) // class label + { + int idx = class_map[token]; + if ( idx == 0) + { + total_class_count++; + idx = total_class_count; + class_map[token] = idx; + } + flt_elem = (float)idx; + type = CV_VAR_CATEGORICAL; + } + } +} + +void CvMLData::set_delimiter(char ch) +{ + CV_FUNCNAME( "CvMLData::set_delimited" ); + __BEGIN__; + + if (ch == miss_ch /*|| ch == flt_separator*/) + CV_ERROR(CV_StsBadArg, "delimited, miss_character and flt_separator must be different"); + + delimiter = ch; + + __END__; +} + +char CvMLData::get_delimiter() const +{ + return delimiter; +} + +void CvMLData::set_miss_ch(char ch) +{ + CV_FUNCNAME( "CvMLData::set_miss_ch" ); + __BEGIN__; + + if (ch == delimiter/* || ch == flt_separator*/) + CV_ERROR(CV_StsBadArg, "delimited, miss_character and flt_separator must be different"); + + miss_ch = ch; + + __END__; +} + +char CvMLData::get_miss_ch() const +{ + return miss_ch; +} + +void CvMLData::set_response_idx( int idx ) +{ + CV_FUNCNAME( "CvMLData::set_response_idx" ); + __BEGIN__; + + if ( !values ) + CV_ERROR( CV_StsInternal, "data is empty" ); + + if ( idx >= values->cols) + CV_ERROR( CV_StsBadArg, "idx value is not correct" ); + + if ( response_idx >= 0 ) + chahge_var_idx( response_idx, true ); + if ( idx >= 0 ) + chahge_var_idx( idx, false ); + response_idx = idx; + + __END__; +} + +int CvMLData::get_response_idx() const +{ + CV_FUNCNAME( "CvMLData::get_response_idx" ); + __BEGIN__; + + if ( !values ) + CV_ERROR( CV_StsInternal, "data is empty" ); + __END__; + return response_idx; +} + +void CvMLData::change_var_type( int var_idx, int type ) +{ + CV_FUNCNAME( "CvMLData::change_var_type" ); + __BEGIN__; + + int var_count = 0; + + if ( !values ) + CV_ERROR( CV_StsInternal, "data is empty" ); + + var_count = values->cols; + + if ( var_idx < 0 || var_idx >= var_count) + CV_ERROR( CV_StsBadArg, "var_idx is not correct" ); + + if ( type != CV_VAR_ORDERED && type != CV_VAR_CATEGORICAL) + CV_ERROR( CV_StsBadArg, "type is not correct" ); + + assert( var_types ); + if ( var_types->data.ptr[var_idx] == CV_VAR_CATEGORICAL && type == CV_VAR_ORDERED) + CV_ERROR( CV_StsBadArg, "it`s impossible to assign CV_VAR_ORDERED type to categorical variable" ); + var_types->data.ptr[var_idx] = (uchar)type; + + __END__; + + return; +} + +void CvMLData::set_var_types( const char* str ) +{ + CV_FUNCNAME( "CvMLData::set_var_types" ); + __BEGIN__; + + const char* ord = 0, *cat = 0; + int var_count = 0, set_var_type_count = 0; + if ( !values ) + CV_ERROR( CV_StsInternal, "data is empty" ); + + var_count = values->cols; + + assert( var_types ); + + ord = strstr( str, "ord" ); + cat = strstr( str, "cat" ); + if ( !ord && !cat ) + CV_ERROR( CV_StsBadArg, "types string is not correct" ); + + if ( !ord && strlen(cat) == 3 ) // str == "cat" + { + cvSet( var_types, cvScalarAll(CV_VAR_CATEGORICAL) ); + return; + } + + if ( !cat && strlen(ord) == 3 ) // str == "ord" + { + cvSet( var_types, cvScalarAll(CV_VAR_ORDERED) ); + return; + } + + if ( ord ) // parse ord str + { + char* stopstring = NULL; + if ( ord[3] != '[') + CV_ERROR( CV_StsBadArg, "types string is not correct" ); + + ord += 4; // pass "ord[" + do + { + int b1 = (int)strtod( ord, &stopstring ); + if ( *stopstring == 0 || (*stopstring != ',' && *stopstring != ']' && *stopstring != '-') ) + CV_ERROR( CV_StsBadArg, "types string is not correct" ); + ord = stopstring + 1; + if ( (stopstring[0] == ',') || (stopstring[0] == ']')) + { + if ( var_types->data.ptr[b1] == CV_VAR_CATEGORICAL) + CV_ERROR( CV_StsBadArg, "it`s impossible to assign CV_VAR_ORDERED type to categorical variable" ); + var_types->data.ptr[b1] = CV_VAR_ORDERED; + set_var_type_count++; + } + else + { + if ( stopstring[0] == '-') + { + int b2 = (int)strtod( ord, &stopstring); + if ( (*stopstring == 0) || (*stopstring != ',' && *stopstring != ']') ) + CV_ERROR( CV_StsBadArg, "types string is not correct" ); + ord = stopstring + 1; + for (int i = b1; i <= b2; i++) + { + if ( var_types->data.ptr[i] == CV_VAR_CATEGORICAL) + CV_ERROR( CV_StsBadArg, "it`s impossible to assign CV_VAR_ORDERED type to categorical variable" ); + var_types->data.ptr[i] = CV_VAR_ORDERED; + } + set_var_type_count += b2 - b1 + 1; + } + else + CV_ERROR( CV_StsBadArg, "types string is not correct" ); + + } + } + while (*stopstring != ']'); + + if ( stopstring[1] != '\0' && stopstring[1] != ',') + CV_ERROR( CV_StsBadArg, "types string is not correct" ); + } + + if ( cat ) // parse cat str + { + char* stopstring = NULL; + if ( cat[3] != '[') + CV_ERROR( CV_StsBadArg, "types string is not correct" ); + + cat += 4; // pass "cat[" + do + { + int b1 = (int)strtod( cat, &stopstring ); + if ( *stopstring == 0 || (*stopstring != ',' && *stopstring != ']' && *stopstring != '-') ) + CV_ERROR( CV_StsBadArg, "types string is not correct" ); + cat = stopstring + 1; + if ( (stopstring[0] == ',') || (stopstring[0] == ']')) + { + var_types->data.ptr[b1] = CV_VAR_CATEGORICAL; + set_var_type_count++; + } + else + { + if ( stopstring[0] == '-') + { + int b2 = (int)strtod( cat, &stopstring); + if ( (*stopstring == 0) || (*stopstring != ',' && *stopstring != ']') ) + CV_ERROR( CV_StsBadArg, "types string is not correct" ); + cat = stopstring + 1; + for (int i = b1; i <= b2; i++) + var_types->data.ptr[i] = CV_VAR_CATEGORICAL; + set_var_type_count += b2 - b1 + 1; + } + else + CV_ERROR( CV_StsBadArg, "types string is not correct" ); + + } + } + while (*stopstring != ']'); + + if ( stopstring[1] != '\0' && stopstring[1] != ',') + CV_ERROR( CV_StsBadArg, "types string is not correct" ); + } + + if (set_var_type_count != var_count) + CV_ERROR( CV_StsBadArg, "types string is not correct" ); + + __END__; +} + +const CvMat* CvMLData::get_var_types() +{ + CV_FUNCNAME( "CvMLData::get_var_types" ); + __BEGIN__; + + uchar *var_types_out_ptr = 0; + int avcount, vt_size; + if ( !values ) + CV_ERROR( CV_StsInternal, "data is empty" ); + + assert( var_idx_mask ); + + avcount = cvFloor( cvNorm( var_idx_mask, 0, CV_L1 ) ); + vt_size = avcount + (response_idx >= 0); + + if ( avcount == values->cols || (avcount == values->cols-1 && response_idx == values->cols-1) ) + return var_types; + + if ( !var_types_out || ( var_types_out && var_types_out->cols != vt_size ) ) + { + cvReleaseMat( &var_types_out ); + var_types_out = cvCreateMat( 1, vt_size, CV_8UC1 ); + } + + var_types_out_ptr = var_types_out->data.ptr; + for( int i = 0; i < var_types->cols; i++) + { + if (i == response_idx || !var_idx_mask->data.ptr[i]) continue; + *var_types_out_ptr = var_types->data.ptr[i]; + var_types_out_ptr++; + } + if ( response_idx >= 0 ) + *var_types_out_ptr = var_types->data.ptr[response_idx]; + + __END__; + + return var_types_out; +} + +int CvMLData::get_var_type( int var_idx ) const +{ + return var_types->data.ptr[var_idx]; +} + +const CvMat* CvMLData::get_responses() +{ + CV_FUNCNAME( "CvMLData::get_responses_ptr" ); + __BEGIN__; + + int var_count = 0; + + if ( !values ) + CV_ERROR( CV_StsInternal, "data is empty" ); + var_count = values->cols; + + if ( response_idx < 0 || response_idx >= var_count ) + return 0; + if ( !response_out ) + response_out = cvCreateMatHeader( values->rows, 1, CV_32FC1 ); + else + cvInitMatHeader( response_out, values->rows, 1, CV_32FC1); + cvGetCol( values, response_out, response_idx ); + + __END__; + + return response_out; +} + +void CvMLData::set_train_test_split( const CvTrainTestSplit * spl) +{ + CV_FUNCNAME( "CvMLData::set_division" ); + __BEGIN__; + + int sample_count = 0; + + if ( !values ) + CV_ERROR( CV_StsInternal, "data is empty" ); + + sample_count = values->rows; + + float train_sample_portion; + + if (spl->train_sample_part_mode == CV_COUNT) + { + train_sample_count = spl->train_sample_part.count; + if (train_sample_count > sample_count) + CV_ERROR( CV_StsBadArg, "train samples count is not correct" ); + train_sample_count = train_sample_count<=0 ? sample_count : train_sample_count; + } + else // dtype.train_sample_part_mode == CV_PORTION + { + train_sample_portion = spl->train_sample_part.portion; + if ( train_sample_portion > 1) + CV_ERROR( CV_StsBadArg, "train samples count is not correct" ); + train_sample_portion = train_sample_portion <= FLT_EPSILON || + 1 - train_sample_portion <= FLT_EPSILON ? 1 : train_sample_portion; + train_sample_count = std::max(1, cvFloor( train_sample_portion * sample_count )); + } + + if ( train_sample_count == sample_count ) + { + free_train_test_idx(); + return; + } + + if ( train_sample_idx && train_sample_idx->cols != train_sample_count ) + free_train_test_idx(); + + if ( !sample_idx) + { + int test_sample_count = sample_count- train_sample_count; + sample_idx = (int*)cvAlloc( sample_count * sizeof(sample_idx[0]) ); + for (int i = 0; i < sample_count; i++ ) + sample_idx[i] = i; + train_sample_idx = cvCreateMatHeader( 1, train_sample_count, CV_32SC1 ); + *train_sample_idx = cvMat( 1, train_sample_count, CV_32SC1, &sample_idx[0] ); + + CV_Assert(test_sample_count > 0); + test_sample_idx = cvCreateMatHeader( 1, test_sample_count, CV_32SC1 ); + *test_sample_idx = cvMat( 1, test_sample_count, CV_32SC1, &sample_idx[train_sample_count] ); + } + + mix = spl->mix; + if ( mix ) + mix_train_and_test_idx(); + + __END__; +} + +const CvMat* CvMLData::get_train_sample_idx() const +{ + CV_FUNCNAME( "CvMLData::get_train_sample_idx" ); + __BEGIN__; + + if ( !values ) + CV_ERROR( CV_StsInternal, "data is empty" ); + __END__; + + return train_sample_idx; +} + +const CvMat* CvMLData::get_test_sample_idx() const +{ + CV_FUNCNAME( "CvMLData::get_test_sample_idx" ); + __BEGIN__; + + if ( !values ) + CV_ERROR( CV_StsInternal, "data is empty" ); + __END__; + + return test_sample_idx; +} + +void CvMLData::mix_train_and_test_idx() +{ + CV_FUNCNAME( "CvMLData::mix_train_and_test_idx" ); + __BEGIN__; + + if ( !values ) + CV_ERROR( CV_StsInternal, "data is empty" ); + __END__; + + if ( !sample_idx) + return; + + if ( train_sample_count > 0 && train_sample_count < values->rows ) + { + int n = values->rows; + for (int i = 0; i < n; i++) + { + int a = (*rng)(n); + int b = (*rng)(n); + int t; + CV_SWAP( sample_idx[a], sample_idx[b], t ); + } + } +} + +const CvMat* CvMLData::get_var_idx() +{ + CV_FUNCNAME( "CvMLData::get_var_idx" ); + __BEGIN__; + + int avcount = 0; + + if ( !values ) + CV_ERROR( CV_StsInternal, "data is empty" ); + + assert( var_idx_mask ); + + avcount = cvFloor( cvNorm( var_idx_mask, 0, CV_L1 ) ); + int* vidx; + + if ( avcount == values->cols ) + return 0; + + if ( !var_idx_out || ( var_idx_out && var_idx_out->cols != avcount ) ) + { + cvReleaseMat( &var_idx_out ); + var_idx_out = cvCreateMat( 1, avcount, CV_32SC1); + if ( response_idx >=0 ) + var_idx_mask->data.ptr[response_idx] = 0; + } + + vidx = var_idx_out->data.i; + + for(int i = 0; i < var_idx_mask->cols; i++) + if ( var_idx_mask->data.ptr[i] ) + { + *vidx = i; + vidx++; + } + + __END__; + + return var_idx_out; +} + +void CvMLData::chahge_var_idx( int vi, bool state ) +{ + change_var_idx( vi, state ); +} + +void CvMLData::change_var_idx( int vi, bool state ) +{ + CV_FUNCNAME( "CvMLData::change_var_idx" ); + __BEGIN__; + + int var_count = 0; + + if ( !values ) + CV_ERROR( CV_StsInternal, "data is empty" ); + + var_count = values->cols; + + if ( vi < 0 || vi >= var_count) + CV_ERROR( CV_StsBadArg, "variable index is not correct" ); + + assert( var_idx_mask ); + var_idx_mask->data.ptr[vi] = state; + + __END__; +} + +/* End of file. */ diff --git a/opencv-apps/traincascade/old_ml_inner_functions.cpp b/opencv-apps/traincascade/old_ml_inner_functions.cpp new file mode 100644 index 0000000..5858a40 --- /dev/null +++ b/opencv-apps/traincascade/old_ml_inner_functions.cpp @@ -0,0 +1,1688 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#include "old_ml_precomp.hpp" + + +CvStatModel::CvStatModel() +{ + default_model_name = "my_stat_model"; +} + + +CvStatModel::~CvStatModel() +{ + clear(); +} + + +void CvStatModel::clear() +{ +} + + +void CvStatModel::save( const char* filename, const char* name ) const +{ + CvFileStorage* fs = 0; + + CV_FUNCNAME( "CvStatModel::save" ); + + __BEGIN__; + + CV_CALL( fs = cvOpenFileStorage( filename, 0, CV_STORAGE_WRITE )); + if( !fs ) + CV_ERROR( CV_StsError, "Could not open the file storage. Check the path and permissions" ); + + write( fs, name ? name : default_model_name ); + + __END__; + + cvReleaseFileStorage( &fs ); +} + + +void CvStatModel::load( const char* filename, const char* name ) +{ + CvFileStorage* fs = 0; + + CV_FUNCNAME( "CvAlgorithm::load" ); + + __BEGIN__; + + CvFileNode* model_node = 0; + + CV_CALL( fs = cvOpenFileStorage( filename, 0, CV_STORAGE_READ )); + if( !fs ) + EXIT; + + if( name ) + model_node = cvGetFileNodeByName( fs, 0, name ); + else + { + CvFileNode* root = cvGetRootFileNode( fs ); + if( root->data.seq->total > 0 ) + model_node = (CvFileNode*)cvGetSeqElem( root->data.seq, 0 ); + } + + read( fs, model_node ); + + __END__; + + cvReleaseFileStorage( &fs ); +} + + +void CvStatModel::write( CvFileStorage*, const char* ) const +{ + OPENCV_ERROR( CV_StsNotImplemented, "CvStatModel::write", "" ); +} + +void CvStatModel::read( CvFileStorage*, CvFileNode* ) +{ + OPENCV_ERROR( CV_StsNotImplemented, "CvStatModel::read", "" ); +} + +CvMat* icvGenerateRandomClusterCenters ( int seed, const CvMat* data, + int num_of_clusters, CvMat* _centers ) +{ + CvMat* centers = _centers; + + CV_FUNCNAME("icvGenerateRandomClusterCenters"); + __BEGIN__; + + CvRNG rng; + CvMat data_comp, centers_comp; + CvPoint minLoc, maxLoc; // Not used, just for function "cvMinMaxLoc" + double minVal, maxVal; + int i; + int dim = data ? data->cols : 0; + + if( ICV_IS_MAT_OF_TYPE(data, CV_32FC1) ) + { + if( _centers && !ICV_IS_MAT_OF_TYPE (_centers, CV_32FC1) ) + { + CV_ERROR(CV_StsBadArg,""); + } + else if( !_centers ) + CV_CALL(centers = cvCreateMat (num_of_clusters, dim, CV_32FC1)); + } + else if( ICV_IS_MAT_OF_TYPE(data, CV_64FC1) ) + { + if( _centers && !ICV_IS_MAT_OF_TYPE (_centers, CV_64FC1) ) + { + CV_ERROR(CV_StsBadArg,""); + } + else if( !_centers ) + CV_CALL(centers = cvCreateMat (num_of_clusters, dim, CV_64FC1)); + } + else + CV_ERROR (CV_StsBadArg,""); + + if( num_of_clusters < 1 ) + CV_ERROR (CV_StsBadArg,""); + + rng = cvRNG(seed); + for (i = 0; i < dim; i++) + { + CV_CALL(cvGetCol (data, &data_comp, i)); + CV_CALL(cvMinMaxLoc (&data_comp, &minVal, &maxVal, &minLoc, &maxLoc)); + CV_CALL(cvGetCol (centers, ¢ers_comp, i)); + CV_CALL(cvRandArr (&rng, ¢ers_comp, CV_RAND_UNI, cvScalarAll(minVal), cvScalarAll(maxVal))); + } + + __END__; + + if( (cvGetErrStatus () < 0) || (centers != _centers) ) + cvReleaseMat (¢ers); + + return _centers ? _centers : centers; +} // end of icvGenerateRandomClusterCenters + +static int CV_CDECL +icvCmpIntegers( const void* a, const void* b ) +{ + return *(const int*)a - *(const int*)b; +} + + +static int CV_CDECL +icvCmpIntegersPtr( const void* _a, const void* _b ) +{ + int a = **(const int**)_a; + int b = **(const int**)_b; + return (a < b ? -1 : 0)|(a > b); +} + + +static int icvCmpSparseVecElems( const void* a, const void* b ) +{ + return ((CvSparseVecElem32f*)a)->idx - ((CvSparseVecElem32f*)b)->idx; +} + + +CvMat* +cvPreprocessIndexArray( const CvMat* idx_arr, int data_arr_size, bool check_for_duplicates ) +{ + CvMat* idx = 0; + + CV_FUNCNAME( "cvPreprocessIndexArray" ); + + __BEGIN__; + + int i, idx_total, idx_selected = 0, step, type, prev = INT_MIN, is_sorted = 1; + uchar* srcb = 0; + int* srci = 0; + int* dsti; + + if( !CV_IS_MAT(idx_arr) ) + CV_ERROR( CV_StsBadArg, "Invalid index array" ); + + if( idx_arr->rows != 1 && idx_arr->cols != 1 ) + CV_ERROR( CV_StsBadSize, "the index array must be 1-dimensional" ); + + idx_total = idx_arr->rows + idx_arr->cols - 1; + srcb = idx_arr->data.ptr; + srci = idx_arr->data.i; + + type = CV_MAT_TYPE(idx_arr->type); + step = CV_IS_MAT_CONT(idx_arr->type) ? 1 : idx_arr->step/CV_ELEM_SIZE(type); + + switch( type ) + { + case CV_8UC1: + case CV_8SC1: + // idx_arr is array of 1's and 0's - + // i.e. it is a mask of the selected components + if( idx_total != data_arr_size ) + CV_ERROR( CV_StsUnmatchedSizes, + "Component mask should contain as many elements as the total number of input variables" ); + + for( i = 0; i < idx_total; i++ ) + idx_selected += srcb[i*step] != 0; + + if( idx_selected == 0 ) + CV_ERROR( CV_StsOutOfRange, "No components/input_variables is selected!" ); + + break; + case CV_32SC1: + // idx_arr is array of integer indices of selected components + if( idx_total > data_arr_size ) + CV_ERROR( CV_StsOutOfRange, + "index array may not contain more elements than the total number of input variables" ); + idx_selected = idx_total; + // check if sorted already + for( i = 0; i < idx_total; i++ ) + { + int val = srci[i*step]; + if( val >= prev ) + { + is_sorted = 0; + break; + } + prev = val; + } + break; + default: + CV_ERROR( CV_StsUnsupportedFormat, "Unsupported index array data type " + "(it should be 8uC1, 8sC1 or 32sC1)" ); + } + + CV_CALL( idx = cvCreateMat( 1, idx_selected, CV_32SC1 )); + dsti = idx->data.i; + + if( type < CV_32SC1 ) + { + for( i = 0; i < idx_total; i++ ) + if( srcb[i*step] ) + *dsti++ = i; + } + else + { + for( i = 0; i < idx_total; i++ ) + dsti[i] = srci[i*step]; + + if( !is_sorted ) + qsort( dsti, idx_total, sizeof(dsti[0]), icvCmpIntegers ); + + if( dsti[0] < 0 || dsti[idx_total-1] >= data_arr_size ) + CV_ERROR( CV_StsOutOfRange, "the index array elements are out of range" ); + + if( check_for_duplicates ) + { + for( i = 1; i < idx_total; i++ ) + if( dsti[i] <= dsti[i-1] ) + CV_ERROR( CV_StsBadArg, "There are duplicated index array elements" ); + } + } + + __END__; + + if( cvGetErrStatus() < 0 ) + cvReleaseMat( &idx ); + + return idx; +} + + +CvMat* +cvPreprocessVarType( const CvMat* var_type, const CvMat* var_idx, + int var_count, int* response_type ) +{ + CvMat* out_var_type = 0; + CV_FUNCNAME( "cvPreprocessVarType" ); + + if( response_type ) + *response_type = -1; + + __BEGIN__; + + int i, tm_size, tm_step; + //int* map = 0; + const uchar* src; + uchar* dst; + + if( !CV_IS_MAT(var_type) ) + CV_ERROR( var_type ? CV_StsBadArg : CV_StsNullPtr, "Invalid or absent var_type array" ); + + if( var_type->rows != 1 && var_type->cols != 1 ) + CV_ERROR( CV_StsBadSize, "var_type array must be 1-dimensional" ); + + if( !CV_IS_MASK_ARR(var_type)) + CV_ERROR( CV_StsUnsupportedFormat, "type mask must be 8uC1 or 8sC1 array" ); + + tm_size = var_type->rows + var_type->cols - 1; + tm_step = var_type->rows == 1 ? 1 : var_type->step/CV_ELEM_SIZE(var_type->type); + + if( /*tm_size != var_count &&*/ tm_size != var_count + 1 ) + CV_ERROR( CV_StsBadArg, + "type mask must be of + 1 size" ); + + if( response_type && tm_size > var_count ) + *response_type = var_type->data.ptr[var_count*tm_step] != 0; + + if( var_idx ) + { + if( !CV_IS_MAT(var_idx) || CV_MAT_TYPE(var_idx->type) != CV_32SC1 || + (var_idx->rows != 1 && var_idx->cols != 1) || !CV_IS_MAT_CONT(var_idx->type) ) + CV_ERROR( CV_StsBadArg, "var index array should be continuous 1-dimensional integer vector" ); + if( var_idx->rows + var_idx->cols - 1 > var_count ) + CV_ERROR( CV_StsBadSize, "var index array is too large" ); + //map = var_idx->data.i; + var_count = var_idx->rows + var_idx->cols - 1; + } + + CV_CALL( out_var_type = cvCreateMat( 1, var_count, CV_8UC1 )); + src = var_type->data.ptr; + dst = out_var_type->data.ptr; + + for( i = 0; i < var_count; i++ ) + { + //int idx = map ? map[i] : i; + assert( (unsigned)/*idx*/i < (unsigned)tm_size ); + dst[i] = (uchar)(src[/*idx*/i*tm_step] != 0); + } + + __END__; + + return out_var_type; +} + + +CvMat* +cvPreprocessOrderedResponses( const CvMat* responses, const CvMat* sample_idx, int sample_all ) +{ + CvMat* out_responses = 0; + + CV_FUNCNAME( "cvPreprocessOrderedResponses" ); + + __BEGIN__; + + int i, r_type, r_step; + const int* map = 0; + float* dst; + int sample_count = sample_all; + + if( !CV_IS_MAT(responses) ) + CV_ERROR( CV_StsBadArg, "Invalid response array" ); + + if( responses->rows != 1 && responses->cols != 1 ) + CV_ERROR( CV_StsBadSize, "Response array must be 1-dimensional" ); + + if( responses->rows + responses->cols - 1 != sample_count ) + CV_ERROR( CV_StsUnmatchedSizes, + "Response array must contain as many elements as the total number of samples" ); + + r_type = CV_MAT_TYPE(responses->type); + if( r_type != CV_32FC1 && r_type != CV_32SC1 ) + CV_ERROR( CV_StsUnsupportedFormat, "Unsupported response type" ); + + r_step = responses->step ? responses->step / CV_ELEM_SIZE(responses->type) : 1; + + if( r_type == CV_32FC1 && CV_IS_MAT_CONT(responses->type) && !sample_idx ) + { + out_responses = cvCloneMat( responses ); + EXIT; + } + + if( sample_idx ) + { + if( !CV_IS_MAT(sample_idx) || CV_MAT_TYPE(sample_idx->type) != CV_32SC1 || + (sample_idx->rows != 1 && sample_idx->cols != 1) || !CV_IS_MAT_CONT(sample_idx->type) ) + CV_ERROR( CV_StsBadArg, "sample index array should be continuous 1-dimensional integer vector" ); + if( sample_idx->rows + sample_idx->cols - 1 > sample_count ) + CV_ERROR( CV_StsBadSize, "sample index array is too large" ); + map = sample_idx->data.i; + sample_count = sample_idx->rows + sample_idx->cols - 1; + } + + CV_CALL( out_responses = cvCreateMat( 1, sample_count, CV_32FC1 )); + + dst = out_responses->data.fl; + if( r_type == CV_32FC1 ) + { + const float* src = responses->data.fl; + for( i = 0; i < sample_count; i++ ) + { + int idx = map ? map[i] : i; + assert( (unsigned)idx < (unsigned)sample_all ); + dst[i] = src[idx*r_step]; + } + } + else + { + const int* src = responses->data.i; + for( i = 0; i < sample_count; i++ ) + { + int idx = map ? map[i] : i; + assert( (unsigned)idx < (unsigned)sample_all ); + dst[i] = (float)src[idx*r_step]; + } + } + + __END__; + + return out_responses; +} + +CvMat* +cvPreprocessCategoricalResponses( const CvMat* responses, + const CvMat* sample_idx, int sample_all, + CvMat** out_response_map, CvMat** class_counts ) +{ + CvMat* out_responses = 0; + int** response_ptr = 0; + + CV_FUNCNAME( "cvPreprocessCategoricalResponses" ); + + if( out_response_map ) + *out_response_map = 0; + + if( class_counts ) + *class_counts = 0; + + __BEGIN__; + + int i, r_type, r_step; + int cls_count = 1, prev_cls, prev_i; + const int* map = 0; + const int* srci; + const float* srcfl; + int* dst; + int* cls_map; + int* cls_counts = 0; + int sample_count = sample_all; + + if( !CV_IS_MAT(responses) ) + CV_ERROR( CV_StsBadArg, "Invalid response array" ); + + if( responses->rows != 1 && responses->cols != 1 ) + CV_ERROR( CV_StsBadSize, "Response array must be 1-dimensional" ); + + if( responses->rows + responses->cols - 1 != sample_count ) + CV_ERROR( CV_StsUnmatchedSizes, + "Response array must contain as many elements as the total number of samples" ); + + r_type = CV_MAT_TYPE(responses->type); + if( r_type != CV_32FC1 && r_type != CV_32SC1 ) + CV_ERROR( CV_StsUnsupportedFormat, "Unsupported response type" ); + + r_step = responses->rows == 1 ? 1 : responses->step / CV_ELEM_SIZE(responses->type); + + if( sample_idx ) + { + if( !CV_IS_MAT(sample_idx) || CV_MAT_TYPE(sample_idx->type) != CV_32SC1 || + (sample_idx->rows != 1 && sample_idx->cols != 1) || !CV_IS_MAT_CONT(sample_idx->type) ) + CV_ERROR( CV_StsBadArg, "sample index array should be continuous 1-dimensional integer vector" ); + if( sample_idx->rows + sample_idx->cols - 1 > sample_count ) + CV_ERROR( CV_StsBadSize, "sample index array is too large" ); + map = sample_idx->data.i; + sample_count = sample_idx->rows + sample_idx->cols - 1; + } + + CV_CALL( out_responses = cvCreateMat( 1, sample_count, CV_32SC1 )); + + if( !out_response_map ) + CV_ERROR( CV_StsNullPtr, "out_response_map pointer is NULL" ); + + CV_CALL( response_ptr = (int**)cvAlloc( sample_count*sizeof(response_ptr[0]))); + + srci = responses->data.i; + srcfl = responses->data.fl; + dst = out_responses->data.i; + + for( i = 0; i < sample_count; i++ ) + { + int idx = map ? map[i] : i; + assert( (unsigned)idx < (unsigned)sample_all ); + if( r_type == CV_32SC1 ) + dst[i] = srci[idx*r_step]; + else + { + float rf = srcfl[idx*r_step]; + int ri = cvRound(rf); + if( ri != rf ) + { + char buf[100]; + sprintf( buf, "response #%d is not integral", idx ); + CV_ERROR( CV_StsBadArg, buf ); + } + dst[i] = ri; + } + response_ptr[i] = dst + i; + } + + qsort( response_ptr, sample_count, sizeof(int*), icvCmpIntegersPtr ); + + // count the classes + for( i = 1; i < sample_count; i++ ) + cls_count += *response_ptr[i] != *response_ptr[i-1]; + + if( cls_count < 2 ) + CV_ERROR( CV_StsBadArg, "There is only a single class" ); + + CV_CALL( *out_response_map = cvCreateMat( 1, cls_count, CV_32SC1 )); + + if( class_counts ) + { + CV_CALL( *class_counts = cvCreateMat( 1, cls_count, CV_32SC1 )); + cls_counts = (*class_counts)->data.i; + } + + // compact the class indices and build the map + prev_cls = ~*response_ptr[0]; + cls_count = -1; + cls_map = (*out_response_map)->data.i; + + for( i = 0, prev_i = -1; i < sample_count; i++ ) + { + int cur_cls = *response_ptr[i]; + if( cur_cls != prev_cls ) + { + if( cls_counts && cls_count >= 0 ) + cls_counts[cls_count] = i - prev_i; + cls_map[++cls_count] = prev_cls = cur_cls; + prev_i = i; + } + *response_ptr[i] = cls_count; + } + + if( cls_counts ) + cls_counts[cls_count] = i - prev_i; + + __END__; + + cvFree( &response_ptr ); + + return out_responses; +} + + +const float** +cvGetTrainSamples( const CvMat* train_data, int tflag, + const CvMat* var_idx, const CvMat* sample_idx, + int* _var_count, int* _sample_count, + bool always_copy_data ) +{ + float** samples = 0; + + CV_FUNCNAME( "cvGetTrainSamples" ); + + __BEGIN__; + + int i, j, var_count, sample_count, s_step, v_step; + bool copy_data; + const float* data; + const int *s_idx, *v_idx; + + if( !CV_IS_MAT(train_data) ) + CV_ERROR( CV_StsBadArg, "Invalid or NULL training data matrix" ); + + var_count = var_idx ? var_idx->cols + var_idx->rows - 1 : + tflag == CV_ROW_SAMPLE ? train_data->cols : train_data->rows; + sample_count = sample_idx ? sample_idx->cols + sample_idx->rows - 1 : + tflag == CV_ROW_SAMPLE ? train_data->rows : train_data->cols; + + if( _var_count ) + *_var_count = var_count; + + if( _sample_count ) + *_sample_count = sample_count; + + copy_data = tflag != CV_ROW_SAMPLE || var_idx || always_copy_data; + + CV_CALL( samples = (float**)cvAlloc(sample_count*sizeof(samples[0]) + + (copy_data ? 1 : 0)*var_count*sample_count*sizeof(samples[0][0])) ); + data = train_data->data.fl; + s_step = train_data->step / sizeof(samples[0][0]); + v_step = 1; + s_idx = sample_idx ? sample_idx->data.i : 0; + v_idx = var_idx ? var_idx->data.i : 0; + + if( !copy_data ) + { + for( i = 0; i < sample_count; i++ ) + samples[i] = (float*)(data + (s_idx ? s_idx[i] : i)*s_step); + } + else + { + samples[0] = (float*)(samples + sample_count); + if( tflag != CV_ROW_SAMPLE ) + CV_SWAP( s_step, v_step, i ); + + for( i = 0; i < sample_count; i++ ) + { + float* dst = samples[i] = samples[0] + i*var_count; + const float* src = data + (s_idx ? s_idx[i] : i)*s_step; + + if( !v_idx ) + for( j = 0; j < var_count; j++ ) + dst[j] = src[j*v_step]; + else + for( j = 0; j < var_count; j++ ) + dst[j] = src[v_idx[j]*v_step]; + } + } + + __END__; + + return (const float**)samples; +} + + +void +cvCheckTrainData( const CvMat* train_data, int tflag, + const CvMat* missing_mask, + int* var_all, int* sample_all ) +{ + CV_FUNCNAME( "cvCheckTrainData" ); + + if( var_all ) + *var_all = 0; + + if( sample_all ) + *sample_all = 0; + + __BEGIN__; + + // check parameter types and sizes + if( !CV_IS_MAT(train_data) || CV_MAT_TYPE(train_data->type) != CV_32FC1 ) + CV_ERROR( CV_StsBadArg, "train data must be floating-point matrix" ); + + if( missing_mask ) + { + if( !CV_IS_MAT(missing_mask) || !CV_IS_MASK_ARR(missing_mask) || + !CV_ARE_SIZES_EQ(train_data, missing_mask) ) + CV_ERROR( CV_StsBadArg, + "missing value mask must be 8-bit matrix of the same size as training data" ); + } + + if( tflag != CV_ROW_SAMPLE && tflag != CV_COL_SAMPLE ) + CV_ERROR( CV_StsBadArg, + "Unknown training data layout (must be CV_ROW_SAMPLE or CV_COL_SAMPLE)" ); + + if( var_all ) + *var_all = tflag == CV_ROW_SAMPLE ? train_data->cols : train_data->rows; + + if( sample_all ) + *sample_all = tflag == CV_ROW_SAMPLE ? train_data->rows : train_data->cols; + + __END__; +} + + +int +cvPrepareTrainData( const char* /*funcname*/, + const CvMat* train_data, int tflag, + const CvMat* responses, int response_type, + const CvMat* var_idx, + const CvMat* sample_idx, + bool always_copy_data, + const float*** out_train_samples, + int* _sample_count, + int* _var_count, + int* _var_all, + CvMat** out_responses, + CvMat** out_response_map, + CvMat** out_var_idx, + CvMat** out_sample_idx ) +{ + int ok = 0; + CvMat* _var_idx = 0; + CvMat* _sample_idx = 0; + CvMat* _responses = 0; + int sample_all = 0, sample_count = 0, var_all = 0, var_count = 0; + + CV_FUNCNAME( "cvPrepareTrainData" ); + + // step 0. clear all the output pointers to ensure we do not try + // to call free() with uninitialized pointers + if( out_responses ) + *out_responses = 0; + + if( out_response_map ) + *out_response_map = 0; + + if( out_var_idx ) + *out_var_idx = 0; + + if( out_sample_idx ) + *out_sample_idx = 0; + + if( out_train_samples ) + *out_train_samples = 0; + + if( _sample_count ) + *_sample_count = 0; + + if( _var_count ) + *_var_count = 0; + + if( _var_all ) + *_var_all = 0; + + __BEGIN__; + + if( !out_train_samples ) + CV_ERROR( CV_StsBadArg, "output pointer to train samples is NULL" ); + + CV_CALL( cvCheckTrainData( train_data, tflag, 0, &var_all, &sample_all )); + + if( sample_idx ) + CV_CALL( _sample_idx = cvPreprocessIndexArray( sample_idx, sample_all )); + if( var_idx ) + CV_CALL( _var_idx = cvPreprocessIndexArray( var_idx, var_all )); + + if( responses ) + { + if( !out_responses ) + CV_ERROR( CV_StsNullPtr, "output response pointer is NULL" ); + + if( response_type == CV_VAR_NUMERICAL ) + { + CV_CALL( _responses = cvPreprocessOrderedResponses( responses, + _sample_idx, sample_all )); + } + else + { + CV_CALL( _responses = cvPreprocessCategoricalResponses( responses, + _sample_idx, sample_all, out_response_map, 0 )); + } + } + + CV_CALL( *out_train_samples = + cvGetTrainSamples( train_data, tflag, _var_idx, _sample_idx, + &var_count, &sample_count, always_copy_data )); + + ok = 1; + + __END__; + + if( ok ) + { + if( out_responses ) + *out_responses = _responses, _responses = 0; + + if( out_var_idx ) + *out_var_idx = _var_idx, _var_idx = 0; + + if( out_sample_idx ) + *out_sample_idx = _sample_idx, _sample_idx = 0; + + if( _sample_count ) + *_sample_count = sample_count; + + if( _var_count ) + *_var_count = var_count; + + if( _var_all ) + *_var_all = var_all; + } + else + { + if( out_response_map ) + cvReleaseMat( out_response_map ); + cvFree( out_train_samples ); + } + + if( _responses != responses ) + cvReleaseMat( &_responses ); + cvReleaseMat( &_var_idx ); + cvReleaseMat( &_sample_idx ); + + return ok; +} + + +typedef struct CvSampleResponsePair +{ + const float* sample; + const uchar* mask; + int response; + int index; +} +CvSampleResponsePair; + + +static int +CV_CDECL icvCmpSampleResponsePairs( const void* a, const void* b ) +{ + int ra = ((const CvSampleResponsePair*)a)->response; + int rb = ((const CvSampleResponsePair*)b)->response; + int ia = ((const CvSampleResponsePair*)a)->index; + int ib = ((const CvSampleResponsePair*)b)->index; + + return ra < rb ? -1 : ra > rb ? 1 : ia - ib; + //return (ra > rb ? -1 : 0)|(ra < rb); +} + + +void +cvSortSamplesByClasses( const float** samples, const CvMat* classes, + int* class_ranges, const uchar** mask ) +{ + CvSampleResponsePair* pairs = 0; + CV_FUNCNAME( "cvSortSamplesByClasses" ); + + __BEGIN__; + + int i, k = 0, sample_count; + + if( !samples || !classes || !class_ranges ) + CV_ERROR( CV_StsNullPtr, "INTERNAL ERROR: some of the args are NULL pointers" ); + + if( classes->rows != 1 || CV_MAT_TYPE(classes->type) != CV_32SC1 ) + CV_ERROR( CV_StsBadArg, "classes array must be a single row of integers" ); + + sample_count = classes->cols; + CV_CALL( pairs = (CvSampleResponsePair*)cvAlloc( (sample_count+1)*sizeof(pairs[0]))); + + for( i = 0; i < sample_count; i++ ) + { + pairs[i].sample = samples[i]; + pairs[i].mask = (mask) ? (mask[i]) : 0; + pairs[i].response = classes->data.i[i]; + pairs[i].index = i; + assert( classes->data.i[i] >= 0 ); + } + + qsort( pairs, sample_count, sizeof(pairs[0]), icvCmpSampleResponsePairs ); + pairs[sample_count].response = -1; + class_ranges[0] = 0; + + for( i = 0; i < sample_count; i++ ) + { + samples[i] = pairs[i].sample; + if (mask) + mask[i] = pairs[i].mask; + classes->data.i[i] = pairs[i].response; + + if( pairs[i].response != pairs[i+1].response ) + class_ranges[++k] = i+1; + } + + __END__; + + cvFree( &pairs ); +} + + +void +cvPreparePredictData( const CvArr* _sample, int dims_all, + const CvMat* comp_idx, int class_count, + const CvMat* prob, float** _row_sample, + int as_sparse ) +{ + float* row_sample = 0; + int* inverse_comp_idx = 0; + + CV_FUNCNAME( "cvPreparePredictData" ); + + __BEGIN__; + + const CvMat* sample = (const CvMat*)_sample; + float* sample_data; + int sample_step; + int is_sparse = CV_IS_SPARSE_MAT(sample); + int d, sizes[CV_MAX_DIM]; + int i, dims_selected; + int vec_size; + + if( !is_sparse && !CV_IS_MAT(sample) ) + CV_ERROR( !sample ? CV_StsNullPtr : CV_StsBadArg, "The sample is not a valid vector" ); + + if( cvGetElemType( sample ) != CV_32FC1 ) + CV_ERROR( CV_StsUnsupportedFormat, "Input sample must have 32fC1 type" ); + + CV_CALL( d = cvGetDims( sample, sizes )); + + if( !((is_sparse && d == 1) || (!is_sparse && d == 2 && (sample->rows == 1 || sample->cols == 1))) ) + CV_ERROR( CV_StsBadSize, "Input sample must be 1-dimensional vector" ); + + if( d == 1 ) + sizes[1] = 1; + + if( sizes[0] + sizes[1] - 1 != dims_all ) + CV_ERROR( CV_StsUnmatchedSizes, + "The sample size is different from what has been used for training" ); + + if( !_row_sample ) + CV_ERROR( CV_StsNullPtr, "INTERNAL ERROR: The row_sample pointer is NULL" ); + + if( comp_idx && (!CV_IS_MAT(comp_idx) || comp_idx->rows != 1 || + CV_MAT_TYPE(comp_idx->type) != CV_32SC1) ) + CV_ERROR( CV_StsBadArg, "INTERNAL ERROR: invalid comp_idx" ); + + dims_selected = comp_idx ? comp_idx->cols : dims_all; + + if( prob ) + { + if( !CV_IS_MAT(prob) ) + CV_ERROR( CV_StsBadArg, "The output matrix of probabilities is invalid" ); + + if( (prob->rows != 1 && prob->cols != 1) || + (CV_MAT_TYPE(prob->type) != CV_32FC1 && + CV_MAT_TYPE(prob->type) != CV_64FC1) ) + CV_ERROR( CV_StsBadSize, + "The matrix of probabilities must be 1-dimensional vector of 32fC1 type" ); + + if( prob->rows + prob->cols - 1 != class_count ) + CV_ERROR( CV_StsUnmatchedSizes, + "The vector of probabilities must contain as many elements as " + "the number of classes in the training set" ); + } + + vec_size = !as_sparse ? dims_selected*sizeof(row_sample[0]) : + (dims_selected + 1)*sizeof(CvSparseVecElem32f); + + if( CV_IS_MAT(sample) ) + { + sample_data = sample->data.fl; + sample_step = CV_IS_MAT_CONT(sample->type) ? 1 : sample->step/sizeof(row_sample[0]); + + if( !comp_idx && CV_IS_MAT_CONT(sample->type) && !as_sparse ) + *_row_sample = sample_data; + else + { + CV_CALL( row_sample = (float*)cvAlloc( vec_size )); + + if( !comp_idx ) + for( i = 0; i < dims_selected; i++ ) + row_sample[i] = sample_data[sample_step*i]; + else + { + int* comp = comp_idx->data.i; + for( i = 0; i < dims_selected; i++ ) + row_sample[i] = sample_data[sample_step*comp[i]]; + } + + *_row_sample = row_sample; + } + + if( as_sparse ) + { + const float* src = (const float*)row_sample; + CvSparseVecElem32f* dst = (CvSparseVecElem32f*)row_sample; + + dst[dims_selected].idx = -1; + for( i = dims_selected - 1; i >= 0; i-- ) + { + dst[i].idx = i; + dst[i].val = src[i]; + } + } + } + else + { + CvSparseNode* node; + CvSparseMatIterator mat_iterator; + const CvSparseMat* sparse = (const CvSparseMat*)sample; + assert( is_sparse ); + + node = cvInitSparseMatIterator( sparse, &mat_iterator ); + CV_CALL( row_sample = (float*)cvAlloc( vec_size )); + + if( comp_idx ) + { + CV_CALL( inverse_comp_idx = (int*)cvAlloc( dims_all*sizeof(int) )); + memset( inverse_comp_idx, -1, dims_all*sizeof(int) ); + for( i = 0; i < dims_selected; i++ ) + inverse_comp_idx[comp_idx->data.i[i]] = i; + } + + if( !as_sparse ) + { + memset( row_sample, 0, vec_size ); + + for( ; node != 0; node = cvGetNextSparseNode(&mat_iterator) ) + { + int idx = *CV_NODE_IDX( sparse, node ); + if( inverse_comp_idx ) + { + idx = inverse_comp_idx[idx]; + if( idx < 0 ) + continue; + } + row_sample[idx] = *(float*)CV_NODE_VAL( sparse, node ); + } + } + else + { + CvSparseVecElem32f* ptr = (CvSparseVecElem32f*)row_sample; + + for( ; node != 0; node = cvGetNextSparseNode(&mat_iterator) ) + { + int idx = *CV_NODE_IDX( sparse, node ); + if( inverse_comp_idx ) + { + idx = inverse_comp_idx[idx]; + if( idx < 0 ) + continue; + } + ptr->idx = idx; + ptr->val = *(float*)CV_NODE_VAL( sparse, node ); + ptr++; + } + + qsort( row_sample, ptr - (CvSparseVecElem32f*)row_sample, + sizeof(ptr[0]), icvCmpSparseVecElems ); + ptr->idx = -1; + } + + *_row_sample = row_sample; + } + + __END__; + + if( inverse_comp_idx ) + cvFree( &inverse_comp_idx ); + + if( cvGetErrStatus() < 0 && _row_sample ) + { + cvFree( &row_sample ); + *_row_sample = 0; + } +} + + +static void +icvConvertDataToSparse( const uchar* src, int src_step, int src_type, + uchar* dst, int dst_step, int dst_type, + CvSize size, int* idx ) +{ + CV_FUNCNAME( "icvConvertDataToSparse" ); + + __BEGIN__; + + int i, j; + src_type = CV_MAT_TYPE(src_type); + dst_type = CV_MAT_TYPE(dst_type); + + if( CV_MAT_CN(src_type) != 1 || CV_MAT_CN(dst_type) != 1 ) + CV_ERROR( CV_StsUnsupportedFormat, "The function supports only single-channel arrays" ); + + if( src_step == 0 ) + src_step = CV_ELEM_SIZE(src_type); + + if( dst_step == 0 ) + dst_step = CV_ELEM_SIZE(dst_type); + + // if there is no "idx" and if both arrays are continuous, + // do the whole processing (copying or conversion) in a single loop + if( !idx && CV_ELEM_SIZE(src_type)*size.width == src_step && + CV_ELEM_SIZE(dst_type)*size.width == dst_step ) + { + size.width *= size.height; + size.height = 1; + } + + if( src_type == dst_type ) + { + int full_width = CV_ELEM_SIZE(dst_type)*size.width; + + if( full_width == sizeof(int) ) // another common case: copy int's or float's + for( i = 0; i < size.height; i++, src += src_step ) + *(int*)(dst + dst_step*(idx ? idx[i] : i)) = *(int*)src; + else + for( i = 0; i < size.height; i++, src += src_step ) + memcpy( dst + dst_step*(idx ? idx[i] : i), src, full_width ); + } + else if( src_type == CV_32SC1 && (dst_type == CV_32FC1 || dst_type == CV_64FC1) ) + for( i = 0; i < size.height; i++, src += src_step ) + { + uchar* _dst = dst + dst_step*(idx ? idx[i] : i); + if( dst_type == CV_32FC1 ) + for( j = 0; j < size.width; j++ ) + ((float*)_dst)[j] = (float)((int*)src)[j]; + else + for( j = 0; j < size.width; j++ ) + ((double*)_dst)[j] = ((int*)src)[j]; + } + else if( (src_type == CV_32FC1 || src_type == CV_64FC1) && dst_type == CV_32SC1 ) + for( i = 0; i < size.height; i++, src += src_step ) + { + uchar* _dst = dst + dst_step*(idx ? idx[i] : i); + if( src_type == CV_32FC1 ) + for( j = 0; j < size.width; j++ ) + ((int*)_dst)[j] = cvRound(((float*)src)[j]); + else + for( j = 0; j < size.width; j++ ) + ((int*)_dst)[j] = cvRound(((double*)src)[j]); + } + else if( (src_type == CV_32FC1 && dst_type == CV_64FC1) || + (src_type == CV_64FC1 && dst_type == CV_32FC1) ) + for( i = 0; i < size.height; i++, src += src_step ) + { + uchar* _dst = dst + dst_step*(idx ? idx[i] : i); + if( src_type == CV_32FC1 ) + for( j = 0; j < size.width; j++ ) + ((double*)_dst)[j] = ((float*)src)[j]; + else + for( j = 0; j < size.width; j++ ) + ((float*)_dst)[j] = (float)((double*)src)[j]; + } + else + CV_ERROR( CV_StsUnsupportedFormat, "Unsupported combination of input and output vectors" ); + + __END__; +} + + +void +cvWritebackLabels( const CvMat* labels, CvMat* dst_labels, + const CvMat* centers, CvMat* dst_centers, + const CvMat* probs, CvMat* dst_probs, + const CvMat* sample_idx, int samples_all, + const CvMat* comp_idx, int dims_all ) +{ + CV_FUNCNAME( "cvWritebackLabels" ); + + __BEGIN__; + + int samples_selected = samples_all, dims_selected = dims_all; + + if( dst_labels && !CV_IS_MAT(dst_labels) ) + CV_ERROR( CV_StsBadArg, "Array of output labels is not a valid matrix" ); + + if( dst_centers ) + if( !ICV_IS_MAT_OF_TYPE(dst_centers, CV_32FC1) && + !ICV_IS_MAT_OF_TYPE(dst_centers, CV_64FC1) ) + CV_ERROR( CV_StsBadArg, "Array of cluster centers is not a valid matrix" ); + + if( dst_probs && !CV_IS_MAT(dst_probs) ) + CV_ERROR( CV_StsBadArg, "Probability matrix is not valid" ); + + if( sample_idx ) + { + CV_ASSERT( sample_idx->rows == 1 && CV_MAT_TYPE(sample_idx->type) == CV_32SC1 ); + samples_selected = sample_idx->cols; + } + + if( comp_idx ) + { + CV_ASSERT( comp_idx->rows == 1 && CV_MAT_TYPE(comp_idx->type) == CV_32SC1 ); + dims_selected = comp_idx->cols; + } + + if( dst_labels && (!labels || labels->data.ptr != dst_labels->data.ptr) ) + { + if( !labels ) + CV_ERROR( CV_StsNullPtr, "NULL labels" ); + + CV_ASSERT( labels->rows == 1 ); + + if( dst_labels->rows != 1 && dst_labels->cols != 1 ) + CV_ERROR( CV_StsBadSize, "Array of output labels should be 1d vector" ); + + if( dst_labels->rows + dst_labels->cols - 1 != samples_all ) + CV_ERROR( CV_StsUnmatchedSizes, + "Size of vector of output labels is not equal to the total number of input samples" ); + + CV_ASSERT( labels->cols == samples_selected ); + + CV_CALL( icvConvertDataToSparse( labels->data.ptr, labels->step, labels->type, + dst_labels->data.ptr, dst_labels->step, dst_labels->type, + cvSize( 1, samples_selected ), sample_idx ? sample_idx->data.i : 0 )); + } + + if( dst_centers && (!centers || centers->data.ptr != dst_centers->data.ptr) ) + { + int i; + + if( !centers ) + CV_ERROR( CV_StsNullPtr, "NULL centers" ); + + if( centers->rows != dst_centers->rows ) + CV_ERROR( CV_StsUnmatchedSizes, "Invalid number of rows in matrix of output centers" ); + + if( dst_centers->cols != dims_all ) + CV_ERROR( CV_StsUnmatchedSizes, + "Number of columns in matrix of output centers is " + "not equal to the total number of components in the input samples" ); + + CV_ASSERT( centers->cols == dims_selected ); + + for( i = 0; i < centers->rows; i++ ) + CV_CALL( icvConvertDataToSparse( centers->data.ptr + i*centers->step, 0, centers->type, + dst_centers->data.ptr + i*dst_centers->step, 0, dst_centers->type, + cvSize( 1, dims_selected ), comp_idx ? comp_idx->data.i : 0 )); + } + + if( dst_probs && (!probs || probs->data.ptr != dst_probs->data.ptr) ) + { + if( !probs ) + CV_ERROR( CV_StsNullPtr, "NULL probs" ); + + if( probs->cols != dst_probs->cols ) + CV_ERROR( CV_StsUnmatchedSizes, "Invalid number of columns in output probability matrix" ); + + if( dst_probs->rows != samples_all ) + CV_ERROR( CV_StsUnmatchedSizes, + "Number of rows in output probability matrix is " + "not equal to the total number of input samples" ); + + CV_ASSERT( probs->rows == samples_selected ); + + CV_CALL( icvConvertDataToSparse( probs->data.ptr, probs->step, probs->type, + dst_probs->data.ptr, dst_probs->step, dst_probs->type, + cvSize( probs->cols, samples_selected ), + sample_idx ? sample_idx->data.i : 0 )); + } + + __END__; +} + +#if 0 +CV_IMPL void +cvStatModelMultiPredict( const CvStatModel* stat_model, + const CvArr* predict_input, + int flags, CvMat* predict_output, + CvMat* probs, const CvMat* sample_idx ) +{ + CvMemStorage* storage = 0; + CvMat* sample_idx_buffer = 0; + CvSparseMat** sparse_rows = 0; + int samples_selected = 0; + + CV_FUNCNAME( "cvStatModelMultiPredict" ); + + __BEGIN__; + + int i; + int predict_output_step = 1, sample_idx_step = 1; + int type; + int d, sizes[CV_MAX_DIM]; + int tflag = flags == CV_COL_SAMPLE; + int samples_all, dims_all; + int is_sparse = CV_IS_SPARSE_MAT(predict_input); + CvMat predict_input_part; + CvArr* sample = &predict_input_part; + CvMat probs_part; + CvMat* probs1 = probs ? &probs_part : 0; + + if( !CV_IS_STAT_MODEL(stat_model) ) + CV_ERROR( !stat_model ? CV_StsNullPtr : CV_StsBadArg, "Invalid statistical model" ); + + if( !stat_model->predict ) + CV_ERROR( CV_StsNotImplemented, "There is no \"predict\" method" ); + + if( !predict_input || !predict_output ) + CV_ERROR( CV_StsNullPtr, "NULL input or output matrices" ); + + if( !is_sparse && !CV_IS_MAT(predict_input) ) + CV_ERROR( CV_StsBadArg, "predict_input should be a matrix or a sparse matrix" ); + + if( !CV_IS_MAT(predict_output) ) + CV_ERROR( CV_StsBadArg, "predict_output should be a matrix" ); + + type = cvGetElemType( predict_input ); + if( type != CV_32FC1 || + (CV_MAT_TYPE(predict_output->type) != CV_32FC1 && + CV_MAT_TYPE(predict_output->type) != CV_32SC1 )) + CV_ERROR( CV_StsUnsupportedFormat, "The input or output matrix has unsupported format" ); + + CV_CALL( d = cvGetDims( predict_input, sizes )); + if( d > 2 ) + CV_ERROR( CV_StsBadSize, "The input matrix should be 1- or 2-dimensional" ); + + if( !tflag ) + { + samples_all = samples_selected = sizes[0]; + dims_all = sizes[1]; + } + else + { + samples_all = samples_selected = sizes[1]; + dims_all = sizes[0]; + } + + if( sample_idx ) + { + if( !CV_IS_MAT(sample_idx) ) + CV_ERROR( CV_StsBadArg, "Invalid sample_idx matrix" ); + + if( sample_idx->cols != 1 && sample_idx->rows != 1 ) + CV_ERROR( CV_StsBadSize, "sample_idx must be 1-dimensional matrix" ); + + samples_selected = sample_idx->rows + sample_idx->cols - 1; + + if( CV_MAT_TYPE(sample_idx->type) == CV_32SC1 ) + { + if( samples_selected > samples_all ) + CV_ERROR( CV_StsBadSize, "sample_idx is too large vector" ); + } + else if( samples_selected != samples_all ) + CV_ERROR( CV_StsUnmatchedSizes, "sample_idx has incorrect size" ); + + sample_idx_step = sample_idx->step ? + sample_idx->step / CV_ELEM_SIZE(sample_idx->type) : 1; + } + + if( predict_output->rows != 1 && predict_output->cols != 1 ) + CV_ERROR( CV_StsBadSize, "predict_output should be a 1-dimensional matrix" ); + + if( predict_output->rows + predict_output->cols - 1 != samples_all ) + CV_ERROR( CV_StsUnmatchedSizes, "predict_output and predict_input have uncoordinated sizes" ); + + predict_output_step = predict_output->step ? + predict_output->step / CV_ELEM_SIZE(predict_output->type) : 1; + + if( probs ) + { + if( !CV_IS_MAT(probs) ) + CV_ERROR( CV_StsBadArg, "Invalid matrix of probabilities" ); + + if( probs->rows != samples_all ) + CV_ERROR( CV_StsUnmatchedSizes, + "matrix of probabilities must have as many rows as the total number of samples" ); + + if( CV_MAT_TYPE(probs->type) != CV_32FC1 ) + CV_ERROR( CV_StsUnsupportedFormat, "matrix of probabilities must have 32fC1 type" ); + } + + if( is_sparse ) + { + CvSparseNode* node; + CvSparseMatIterator mat_iterator; + CvSparseMat* sparse = (CvSparseMat*)predict_input; + + if( sample_idx && CV_MAT_TYPE(sample_idx->type) == CV_32SC1 ) + { + CV_CALL( sample_idx_buffer = cvCreateMat( 1, samples_all, CV_8UC1 )); + cvZero( sample_idx_buffer ); + for( i = 0; i < samples_selected; i++ ) + sample_idx_buffer->data.ptr[sample_idx->data.i[i*sample_idx_step]] = 1; + samples_selected = samples_all; + sample_idx = sample_idx_buffer; + sample_idx_step = 1; + } + + CV_CALL( sparse_rows = (CvSparseMat**)cvAlloc( samples_selected*sizeof(sparse_rows[0]))); + for( i = 0; i < samples_selected; i++ ) + { + if( sample_idx && sample_idx->data.ptr[i*sample_idx_step] == 0 ) + continue; + CV_CALL( sparse_rows[i] = cvCreateSparseMat( 1, &dims_all, type )); + if( !storage ) + storage = sparse_rows[i]->heap->storage; + else + { + // hack: to decrease memory footprint, make all the sparse matrices + // reside in the same storage + int elem_size = sparse_rows[i]->heap->elem_size; + cvReleaseMemStorage( &sparse_rows[i]->heap->storage ); + sparse_rows[i]->heap = cvCreateSet( 0, sizeof(CvSet), elem_size, storage ); + } + } + + // put each row (or column) of predict_input into separate sparse matrix. + node = cvInitSparseMatIterator( sparse, &mat_iterator ); + for( ; node != 0; node = cvGetNextSparseNode( &mat_iterator )) + { + int* idx = CV_NODE_IDX( sparse, node ); + int idx0 = idx[tflag ^ 1]; + int idx1 = idx[tflag]; + + if( sample_idx && sample_idx->data.ptr[idx0*sample_idx_step] == 0 ) + continue; + + assert( sparse_rows[idx0] != 0 ); + *(float*)cvPtrND( sparse, &idx1, 0, 1, 0 ) = *(float*)CV_NODE_VAL( sparse, node ); + } + } + + for( i = 0; i < samples_selected; i++ ) + { + int idx = i; + float response; + + if( sample_idx ) + { + if( CV_MAT_TYPE(sample_idx->type) == CV_32SC1 ) + { + idx = sample_idx->data.i[i*sample_idx_step]; + if( (unsigned)idx >= (unsigned)samples_all ) + CV_ERROR( CV_StsOutOfRange, "Some of sample_idx elements are out of range" ); + } + else if( CV_MAT_TYPE(sample_idx->type) == CV_8UC1 && + sample_idx->data.ptr[i*sample_idx_step] == 0 ) + continue; + } + + if( !is_sparse ) + { + if( !tflag ) + cvGetRow( predict_input, &predict_input_part, idx ); + else + { + cvGetCol( predict_input, &predict_input_part, idx ); + } + } + else + sample = sparse_rows[idx]; + + if( probs ) + cvGetRow( probs, probs1, idx ); + + CV_CALL( response = stat_model->predict( stat_model, (CvMat*)sample, probs1 )); + + if( CV_MAT_TYPE(predict_output->type) == CV_32FC1 ) + predict_output->data.fl[idx*predict_output_step] = response; + else + { + CV_ASSERT( cvRound(response) == response ); + predict_output->data.i[idx*predict_output_step] = cvRound(response); + } + } + + __END__; + + if( sparse_rows ) + { + int i; + for( i = 0; i < samples_selected; i++ ) + if( sparse_rows[i] ) + { + sparse_rows[i]->heap->storage = 0; + cvReleaseSparseMat( &sparse_rows[i] ); + } + cvFree( &sparse_rows ); + } + + cvReleaseMat( &sample_idx_buffer ); + cvReleaseMemStorage( &storage ); +} +#endif + +// By P. Yarykin - begin - + +void cvCombineResponseMaps (CvMat* _responses, + const CvMat* old_response_map, + CvMat* new_response_map, + CvMat** out_response_map) +{ + int** old_data = NULL; + int** new_data = NULL; + + CV_FUNCNAME ("cvCombineResponseMaps"); + __BEGIN__ + + int i,j; + int old_n, new_n, out_n; + int samples, free_response; + int* first; + int* responses; + int* out_data; + + if( out_response_map ) + *out_response_map = 0; + +// Check input data. + if ((!ICV_IS_MAT_OF_TYPE (_responses, CV_32SC1)) || + (!ICV_IS_MAT_OF_TYPE (old_response_map, CV_32SC1)) || + (!ICV_IS_MAT_OF_TYPE (new_response_map, CV_32SC1))) + { + CV_ERROR (CV_StsBadArg, "Some of input arguments is not the CvMat") + } + +// Prepare sorted responses. + first = new_response_map->data.i; + new_n = new_response_map->cols; + CV_CALL (new_data = (int**)cvAlloc (new_n * sizeof (new_data[0]))); + for (i = 0; i < new_n; i++) + new_data[i] = first + i; + qsort (new_data, new_n, sizeof(int*), icvCmpIntegersPtr); + + first = old_response_map->data.i; + old_n = old_response_map->cols; + CV_CALL (old_data = (int**)cvAlloc (old_n * sizeof (old_data[0]))); + for (i = 0; i < old_n; i++) + old_data[i] = first + i; + qsort (old_data, old_n, sizeof(int*), icvCmpIntegersPtr); + +// Count the number of different responses. + for (i = 0, j = 0, out_n = 0; i < old_n && j < new_n; out_n++) + { + if (*old_data[i] == *new_data[j]) + { + i++; + j++; + } + else if (*old_data[i] < *new_data[j]) + i++; + else + j++; + } + out_n += old_n - i + new_n - j; + +// Create and fill the result response maps. + CV_CALL (*out_response_map = cvCreateMat (1, out_n, CV_32SC1)); + out_data = (*out_response_map)->data.i; + memcpy (out_data, first, old_n * sizeof (int)); + + free_response = old_n; + for (i = 0, j = 0; i < old_n && j < new_n; ) + { + if (*old_data[i] == *new_data[j]) + { + *new_data[j] = (int)(old_data[i] - first); + i++; + j++; + } + else if (*old_data[i] < *new_data[j]) + i++; + else + { + out_data[free_response] = *new_data[j]; + *new_data[j] = free_response++; + j++; + } + } + for (; j < new_n; j++) + { + out_data[free_response] = *new_data[j]; + *new_data[j] = free_response++; + } + CV_ASSERT (free_response == out_n); + +// Change according to out response map. + samples = _responses->cols + _responses->rows - 1; + responses = _responses->data.i; + first = new_response_map->data.i; + for (i = 0; i < samples; i++) + { + responses[i] = first[responses[i]]; + } + + __END__ + + cvFree(&old_data); + cvFree(&new_data); + +} + + +static int icvGetNumberOfCluster( double* prob_vector, int num_of_clusters, float r, + float outlier_thresh, int normalize_probs ) +{ + int max_prob_loc = 0; + + CV_FUNCNAME("icvGetNumberOfCluster"); + __BEGIN__; + + double prob, maxprob, sum; + int i; + + CV_ASSERT(prob_vector); + CV_ASSERT(num_of_clusters >= 0); + + maxprob = prob_vector[0]; + max_prob_loc = 0; + sum = maxprob; + for( i = 1; i < num_of_clusters; i++ ) + { + prob = prob_vector[i]; + sum += prob; + if( prob > maxprob ) + { + max_prob_loc = i; + maxprob = prob; + } + } + if( normalize_probs && fabs(sum - 1.) > FLT_EPSILON ) + { + for( i = 0; i < num_of_clusters; i++ ) + prob_vector[i] /= sum; + } + if( fabs(r - 1.) > FLT_EPSILON && fabs(sum - 1.) < outlier_thresh ) + max_prob_loc = -1; + + __END__; + + return max_prob_loc; + +} // End of icvGetNumberOfCluster + + +void icvFindClusterLabels( const CvMat* probs, float outlier_thresh, float r, + const CvMat* labels ) +{ + CvMat* counts = 0; + + CV_FUNCNAME("icvFindClusterLabels"); + __BEGIN__; + + int nclusters, nsamples; + int i, j; + double* probs_data; + + CV_ASSERT( ICV_IS_MAT_OF_TYPE(probs, CV_64FC1) ); + CV_ASSERT( ICV_IS_MAT_OF_TYPE(labels, CV_32SC1) ); + + nclusters = probs->cols; + nsamples = probs->rows; + CV_ASSERT( nsamples == labels->cols ); + + CV_CALL( counts = cvCreateMat( 1, nclusters + 1, CV_32SC1 ) ); + CV_CALL( cvSetZero( counts )); + for( i = 0; i < nsamples; i++ ) + { + labels->data.i[i] = icvGetNumberOfCluster( probs->data.db + i*probs->cols, + nclusters, r, outlier_thresh, 1 ); + counts->data.i[labels->data.i[i] + 1]++; + } + CV_ASSERT((int)cvSum(counts).val[0] == nsamples); + // Filling empty clusters with the vector, that has the maximal probability + for( j = 0; j < nclusters; j++ ) // outliers are ignored + { + int maxprob_loc = -1; + double maxprob = 0; + + if( counts->data.i[j+1] ) // j-th class is not empty + continue; + // look for the presentative, which is not lonely in it's cluster + // and that has a maximal probability among all these vectors + probs_data = probs->data.db; + for( i = 0; i < nsamples; i++, probs_data++ ) + { + int label = labels->data.i[i]; + double prob; + if( counts->data.i[label+1] == 0 || + (counts->data.i[label+1] <= 1 && label != -1) ) + continue; + prob = *probs_data; + if( prob >= maxprob ) + { + maxprob = prob; + maxprob_loc = i; + } + } + // maxprob_loc == 0 <=> number of vectors less then number of clusters + CV_ASSERT( maxprob_loc >= 0 ); + counts->data.i[labels->data.i[maxprob_loc] + 1]--; + labels->data.i[maxprob_loc] = j; + counts->data.i[j + 1]++; + } + + __END__; + + cvReleaseMat( &counts ); +} // End of icvFindClusterLabels + +/* End of file */ diff --git a/opencv-apps/traincascade/old_ml_precomp.hpp b/opencv-apps/traincascade/old_ml_precomp.hpp new file mode 100644 index 0000000..3394a99 --- /dev/null +++ b/opencv-apps/traincascade/old_ml_precomp.hpp @@ -0,0 +1,376 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#ifndef OPENCV_PRECOMP_H +#define OPENCV_PRECOMP_H + +#include "opencv2/core.hpp" +#include "old_ml.hpp" +#include "opencv2/core/core_c.h" +#include "opencv2/core/utility.hpp" + +#include "opencv2/core/private.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ML_IMPL CV_IMPL +#define __BEGIN__ __CV_BEGIN__ +#define __END__ __CV_END__ +#define EXIT __CV_EXIT__ + +#define CV_MAT_ELEM_FLAG( mat, type, comp, vect, tflag ) \ + (( tflag == CV_ROW_SAMPLE ) \ + ? (CV_MAT_ELEM( mat, type, comp, vect )) \ + : (CV_MAT_ELEM( mat, type, vect, comp ))) + +/* Convert matrix to vector */ +#define ICV_MAT2VEC( mat, vdata, vstep, num ) \ + if( MIN( (mat).rows, (mat).cols ) != 1 ) \ + CV_ERROR( CV_StsBadArg, "" ); \ + (vdata) = ((mat).data.ptr); \ + if( (mat).rows == 1 ) \ + { \ + (vstep) = CV_ELEM_SIZE( (mat).type ); \ + (num) = (mat).cols; \ + } \ + else \ + { \ + (vstep) = (mat).step; \ + (num) = (mat).rows; \ + } + +/* get raw data */ +#define ICV_RAWDATA( mat, flags, rdata, sstep, cstep, m, n ) \ + (rdata) = (mat).data.ptr; \ + if( CV_IS_ROW_SAMPLE( flags ) ) \ + { \ + (sstep) = (mat).step; \ + (cstep) = CV_ELEM_SIZE( (mat).type ); \ + (m) = (mat).rows; \ + (n) = (mat).cols; \ + } \ + else \ + { \ + (cstep) = (mat).step; \ + (sstep) = CV_ELEM_SIZE( (mat).type ); \ + (n) = (mat).rows; \ + (m) = (mat).cols; \ + } + +#define ICV_IS_MAT_OF_TYPE( mat, mat_type) \ + (CV_IS_MAT( mat ) && CV_MAT_TYPE( mat->type ) == (mat_type) && \ + (mat)->cols > 0 && (mat)->rows > 0) + +/* + uchar* data; int sstep, cstep; - trainData->data + uchar* classes; int clstep; int ncl;- trainClasses + uchar* tmask; int tmstep; int ntm; - typeMask + uchar* missed;int msstep, mcstep; -missedMeasurements... + int mm, mn; == m,n == size,dim + uchar* sidx;int sistep; - sampleIdx + uchar* cidx;int cistep; - compIdx + int k, l; == n,m == dim,size (length of cidx, sidx) + int m, n; == size,dim +*/ +#define ICV_DECLARE_TRAIN_ARGS() \ + uchar* data; \ + int sstep, cstep; \ + uchar* classes; \ + int clstep; \ + int ncl; \ + uchar* tmask; \ + int tmstep; \ + int ntm; \ + uchar* missed; \ + int msstep, mcstep; \ + int mm, mn; \ + uchar* sidx; \ + int sistep; \ + uchar* cidx; \ + int cistep; \ + int k, l; \ + int m, n; \ + \ + data = classes = tmask = missed = sidx = cidx = NULL; \ + sstep = cstep = clstep = ncl = tmstep = ntm = msstep = mcstep = mm = mn = 0; \ + sistep = cistep = k = l = m = n = 0; + +#define ICV_TRAIN_DATA_REQUIRED( param, flags ) \ + if( !ICV_IS_MAT_OF_TYPE( (param), CV_32FC1 ) ) \ + { \ + CV_ERROR( CV_StsBadArg, "Invalid " #param " parameter" ); \ + } \ + else \ + { \ + ICV_RAWDATA( *(param), (flags), data, sstep, cstep, m, n ); \ + k = n; \ + l = m; \ + } + +#define ICV_TRAIN_CLASSES_REQUIRED( param ) \ + if( !ICV_IS_MAT_OF_TYPE( (param), CV_32FC1 ) ) \ + { \ + CV_ERROR( CV_StsBadArg, "Invalid " #param " parameter" ); \ + } \ + else \ + { \ + ICV_MAT2VEC( *(param), classes, clstep, ncl ); \ + if( m != ncl ) \ + { \ + CV_ERROR( CV_StsBadArg, "Unmatched sizes" ); \ + } \ + } + +#define ICV_ARG_NULL( param ) \ + if( (param) != NULL ) \ + { \ + CV_ERROR( CV_StsBadArg, #param " parameter must be NULL" ); \ + } + +#define ICV_MISSED_MEASUREMENTS_OPTIONAL( param, flags ) \ + if( param ) \ + { \ + if( !ICV_IS_MAT_OF_TYPE( param, CV_8UC1 ) ) \ + { \ + CV_ERROR( CV_StsBadArg, "Invalid " #param " parameter" ); \ + } \ + else \ + { \ + ICV_RAWDATA( *(param), (flags), missed, msstep, mcstep, mm, mn ); \ + if( mm != m || mn != n ) \ + { \ + CV_ERROR( CV_StsBadArg, "Unmatched sizes" ); \ + } \ + } \ + } + +#define ICV_COMP_IDX_OPTIONAL( param ) \ + if( param ) \ + { \ + if( !ICV_IS_MAT_OF_TYPE( param, CV_32SC1 ) ) \ + { \ + CV_ERROR( CV_StsBadArg, "Invalid " #param " parameter" ); \ + } \ + else \ + { \ + ICV_MAT2VEC( *(param), cidx, cistep, k ); \ + if( k > n ) \ + CV_ERROR( CV_StsBadArg, "Invalid " #param " parameter" ); \ + } \ + } + +#define ICV_SAMPLE_IDX_OPTIONAL( param ) \ + if( param ) \ + { \ + if( !ICV_IS_MAT_OF_TYPE( param, CV_32SC1 ) ) \ + { \ + CV_ERROR( CV_StsBadArg, "Invalid " #param " parameter" ); \ + } \ + else \ + { \ + ICV_MAT2VEC( *sampleIdx, sidx, sistep, l ); \ + if( l > m ) \ + CV_ERROR( CV_StsBadArg, "Invalid " #param " parameter" ); \ + } \ + } + +/****************************************************************************************/ +#define ICV_CONVERT_FLOAT_ARRAY_TO_MATRICE( array, matrice ) \ +{ \ + CvMat a, b; \ + int dims = (matrice)->cols; \ + int nsamples = (matrice)->rows; \ + int type = CV_MAT_TYPE((matrice)->type); \ + int i, offset = dims; \ + \ + CV_ASSERT( type == CV_32FC1 || type == CV_64FC1 ); \ + offset *= ((type == CV_32FC1) ? sizeof(float) : sizeof(double));\ + \ + b = cvMat( 1, dims, CV_32FC1 ); \ + cvGetRow( matrice, &a, 0 ); \ + for( i = 0; i < nsamples; i++, a.data.ptr += offset ) \ + { \ + b.data.fl = (float*)array[i]; \ + CV_CALL( cvConvert( &b, &a ) ); \ + } \ +} + +/****************************************************************************************\ +* Auxiliary functions declarations * +\****************************************************************************************/ + +/* Generates a set of classes centers in quantity that are generated as + uniform random vectors in parallelepiped, where is concentrated. Vectors in + should have horizontal orientation. If != NULL, the function doesn't + allocate any memory and stores generated centers in , returns . + If == NULL, the function allocates memory and creates the matrice. Centers + are supposed to be oriented horizontally. */ +CvMat* icvGenerateRandomClusterCenters( int seed, + const CvMat* data, + int num_of_clusters, + CvMat* centers CV_DEFAULT(0)); + +/* Fills the using by choosing the maximal probability. Outliers are + fixed by and have cluster label (-1). Function also controls that there + weren't "empty" clusters by filling empty clusters with the maximal probability vector. + If probs_sums != NULL, fills it with the sums of probabilities for each sample (it is + useful for normalizing probabilities' matrice of FCM) */ +void icvFindClusterLabels( const CvMat* probs, float outlier_thresh, float r, + const CvMat* labels ); + +typedef struct CvSparseVecElem32f +{ + int idx; + float val; +} +CvSparseVecElem32f; + +/* Prepare training data and related parameters */ +#define CV_TRAIN_STATMODEL_DEFRAGMENT_TRAIN_DATA 1 +#define CV_TRAIN_STATMODEL_SAMPLES_AS_ROWS 2 +#define CV_TRAIN_STATMODEL_SAMPLES_AS_COLUMNS 4 +#define CV_TRAIN_STATMODEL_CATEGORICAL_RESPONSE 8 +#define CV_TRAIN_STATMODEL_ORDERED_RESPONSE 16 +#define CV_TRAIN_STATMODEL_RESPONSES_ON_OUTPUT 32 +#define CV_TRAIN_STATMODEL_ALWAYS_COPY_TRAIN_DATA 64 +#define CV_TRAIN_STATMODEL_SPARSE_AS_SPARSE 128 + +int +cvPrepareTrainData( const char* /*funcname*/, + const CvMat* train_data, int tflag, + const CvMat* responses, int response_type, + const CvMat* var_idx, + const CvMat* sample_idx, + bool always_copy_data, + const float*** out_train_samples, + int* _sample_count, + int* _var_count, + int* _var_all, + CvMat** out_responses, + CvMat** out_response_map, + CvMat** out_var_idx, + CvMat** out_sample_idx=0 ); + +void +cvSortSamplesByClasses( const float** samples, const CvMat* classes, + int* class_ranges, const uchar** mask CV_DEFAULT(0) ); + +void +cvCombineResponseMaps (CvMat* _responses, + const CvMat* old_response_map, + CvMat* new_response_map, + CvMat** out_response_map); + +void +cvPreparePredictData( const CvArr* sample, int dims_all, const CvMat* comp_idx, + int class_count, const CvMat* prob, float** row_sample, + int as_sparse CV_DEFAULT(0) ); + +/* copies clustering [or batch "predict"] results + (labels and/or centers and/or probs) back to the output arrays */ +void +cvWritebackLabels( const CvMat* labels, CvMat* dst_labels, + const CvMat* centers, CvMat* dst_centers, + const CvMat* probs, CvMat* dst_probs, + const CvMat* sample_idx, int samples_all, + const CvMat* comp_idx, int dims_all ); +#define cvWritebackResponses cvWritebackLabels + +#define XML_FIELD_NAME "_name" +CvFileNode* icvFileNodeGetChild(CvFileNode* father, const char* name); +CvFileNode* icvFileNodeGetChildArrayElem(CvFileNode* father, const char* name,int index); +CvFileNode* icvFileNodeGetNext(CvFileNode* n, const char* name); + + +void cvCheckTrainData( const CvMat* train_data, int tflag, + const CvMat* missing_mask, + int* var_all, int* sample_all ); + +CvMat* cvPreprocessIndexArray( const CvMat* idx_arr, int data_arr_size, bool check_for_duplicates=false ); + +CvMat* cvPreprocessVarType( const CvMat* type_mask, const CvMat* var_idx, + int var_all, int* response_type ); + +CvMat* cvPreprocessOrderedResponses( const CvMat* responses, + const CvMat* sample_idx, int sample_all ); + +CvMat* cvPreprocessCategoricalResponses( const CvMat* responses, + const CvMat* sample_idx, int sample_all, + CvMat** out_response_map, CvMat** class_counts=0 ); + +const float** cvGetTrainSamples( const CvMat* train_data, int tflag, + const CvMat* var_idx, const CvMat* sample_idx, + int* _var_count, int* _sample_count, + bool always_copy_data=false ); + +namespace cv +{ + struct DTreeBestSplitFinder + { + DTreeBestSplitFinder(){ splitSize = 0, tree = 0; node = 0; } + DTreeBestSplitFinder( CvDTree* _tree, CvDTreeNode* _node); + DTreeBestSplitFinder( const DTreeBestSplitFinder& finder, Split ); + virtual ~DTreeBestSplitFinder() {} + virtual void operator()(const BlockedRange& range); + void join( DTreeBestSplitFinder& rhs ); + Ptr bestSplit; + Ptr split; + int splitSize; + CvDTree* tree; + CvDTreeNode* node; + }; + + struct ForestTreeBestSplitFinder : DTreeBestSplitFinder + { + ForestTreeBestSplitFinder() : DTreeBestSplitFinder() {} + ForestTreeBestSplitFinder( CvForestTree* _tree, CvDTreeNode* _node ); + ForestTreeBestSplitFinder( const ForestTreeBestSplitFinder& finder, Split ); + virtual void operator()(const BlockedRange& range); + }; +} + +#endif /* __ML_H__ */ diff --git a/opencv-apps/traincascade/old_ml_tree.cpp b/opencv-apps/traincascade/old_ml_tree.cpp new file mode 100644 index 0000000..cfba9ec --- /dev/null +++ b/opencv-apps/traincascade/old_ml_tree.cpp @@ -0,0 +1,4148 @@ +/*M/////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// Intel License Agreement +// +// Copyright (C) 2000, Intel Corporation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of Intel Corporation may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//M*/ + +#include "old_ml_precomp.hpp" +#include + +using namespace cv; + +static const float ord_nan = FLT_MAX*0.5f; +static const int min_block_size = 1 << 16; +static const int block_size_delta = 1 << 10; + +CvDTreeTrainData::CvDTreeTrainData() +{ + var_idx = var_type = cat_count = cat_ofs = cat_map = + priors = priors_mult = counts = direction = split_buf = responses_copy = 0; + buf = 0; + tree_storage = temp_storage = 0; + + clear(); +} + + +CvDTreeTrainData::CvDTreeTrainData( const CvMat* _train_data, int _tflag, + const CvMat* _responses, const CvMat* _var_idx, + const CvMat* _sample_idx, const CvMat* _var_type, + const CvMat* _missing_mask, const CvDTreeParams& _params, + bool _shared, bool _add_labels ) +{ + var_idx = var_type = cat_count = cat_ofs = cat_map = + priors = priors_mult = counts = direction = split_buf = responses_copy = 0; + buf = 0; + + tree_storage = temp_storage = 0; + + set_data( _train_data, _tflag, _responses, _var_idx, _sample_idx, + _var_type, _missing_mask, _params, _shared, _add_labels ); +} + + +CvDTreeTrainData::~CvDTreeTrainData() +{ + clear(); +} + + +bool CvDTreeTrainData::set_params( const CvDTreeParams& _params ) +{ + bool ok = false; + + CV_FUNCNAME( "CvDTreeTrainData::set_params" ); + + __BEGIN__; + + // set parameters + params = _params; + + if( params.max_categories < 2 ) + CV_ERROR( CV_StsOutOfRange, "params.max_categories should be >= 2" ); + params.max_categories = MIN( params.max_categories, 15 ); + + if( params.max_depth < 0 ) + CV_ERROR( CV_StsOutOfRange, "params.max_depth should be >= 0" ); + params.max_depth = MIN( params.max_depth, 25 ); + + params.min_sample_count = MAX(params.min_sample_count,1); + + if( params.cv_folds < 0 ) + CV_ERROR( CV_StsOutOfRange, + "params.cv_folds should be =0 (the tree is not pruned) " + "or n>0 (tree is pruned using n-fold cross-validation)" ); + + if( params.cv_folds == 1 ) + params.cv_folds = 0; + + if( params.regression_accuracy < 0 ) + CV_ERROR( CV_StsOutOfRange, "params.regression_accuracy should be >= 0" ); + + ok = true; + + __END__; + + return ok; +} + +template +class LessThanPtr +{ +public: + bool operator()(T* a, T* b) const { return *a < *b; } +}; + +template +class LessThanIdx +{ +public: + LessThanIdx( const T* _arr ) : arr(_arr) {} + bool operator()(Idx a, Idx b) const { return arr[a] < arr[b]; } + const T* arr; +}; + +class LessThanPairs +{ +public: + bool operator()(const CvPair16u32s& a, const CvPair16u32s& b) const { return *a.i < *b.i; } +}; + +void CvDTreeTrainData::set_data( const CvMat* _train_data, int _tflag, + const CvMat* _responses, const CvMat* _var_idx, const CvMat* _sample_idx, + const CvMat* _var_type, const CvMat* _missing_mask, const CvDTreeParams& _params, + bool _shared, bool _add_labels, bool _update_data ) +{ + CvMat* sample_indices = 0; + CvMat* var_type0 = 0; + CvMat* tmp_map = 0; + int** int_ptr = 0; + CvPair16u32s* pair16u32s_ptr = 0; + CvDTreeTrainData* data = 0; + float *_fdst = 0; + int *_idst = 0; + unsigned short* udst = 0; + int* idst = 0; + + CV_FUNCNAME( "CvDTreeTrainData::set_data" ); + + __BEGIN__; + + int sample_all = 0, r_type, cv_n; + int total_c_count = 0; + int tree_block_size, temp_block_size, max_split_size, nv_size, cv_size = 0; + int ds_step, dv_step, ms_step = 0, mv_step = 0; // {data|mask}{sample|var}_step + int vi, i, size; + char err[100]; + const int *sidx = 0, *vidx = 0; + + uint64 effective_buf_size = 0; + int effective_buf_height = 0, effective_buf_width = 0; + + if( _update_data && data_root ) + { + data = new CvDTreeTrainData( _train_data, _tflag, _responses, _var_idx, + _sample_idx, _var_type, _missing_mask, _params, _shared, _add_labels ); + + // compare new and old train data + if( !(data->var_count == var_count && + cvNorm( data->var_type, var_type, CV_C ) < FLT_EPSILON && + cvNorm( data->cat_count, cat_count, CV_C ) < FLT_EPSILON && + cvNorm( data->cat_map, cat_map, CV_C ) < FLT_EPSILON) ) + CV_ERROR( CV_StsBadArg, + "The new training data must have the same types and the input and output variables " + "and the same categories for categorical variables" ); + + cvReleaseMat( &priors ); + cvReleaseMat( &priors_mult ); + cvReleaseMat( &buf ); + cvReleaseMat( &direction ); + cvReleaseMat( &split_buf ); + cvReleaseMemStorage( &temp_storage ); + + priors = data->priors; data->priors = 0; + priors_mult = data->priors_mult; data->priors_mult = 0; + buf = data->buf; data->buf = 0; + buf_count = data->buf_count; buf_size = data->buf_size; + sample_count = data->sample_count; + + direction = data->direction; data->direction = 0; + split_buf = data->split_buf; data->split_buf = 0; + temp_storage = data->temp_storage; data->temp_storage = 0; + nv_heap = data->nv_heap; cv_heap = data->cv_heap; + + data_root = new_node( 0, sample_count, 0, 0 ); + EXIT; + } + + clear(); + + var_all = 0; + rng = &cv::theRNG(); + + CV_CALL( set_params( _params )); + + // check parameter types and sizes + CV_CALL( cvCheckTrainData( _train_data, _tflag, _missing_mask, &var_all, &sample_all )); + + train_data = _train_data; + responses = _responses; + + if( _tflag == CV_ROW_SAMPLE ) + { + ds_step = _train_data->step/CV_ELEM_SIZE(_train_data->type); + dv_step = 1; + if( _missing_mask ) + ms_step = _missing_mask->step, mv_step = 1; + } + else + { + dv_step = _train_data->step/CV_ELEM_SIZE(_train_data->type); + ds_step = 1; + if( _missing_mask ) + mv_step = _missing_mask->step, ms_step = 1; + } + tflag = _tflag; + + sample_count = sample_all; + var_count = var_all; + + if( _sample_idx ) + { + CV_CALL( sample_indices = cvPreprocessIndexArray( _sample_idx, sample_all )); + sidx = sample_indices->data.i; + sample_count = sample_indices->rows + sample_indices->cols - 1; + } + + if( _var_idx ) + { + CV_CALL( var_idx = cvPreprocessIndexArray( _var_idx, var_all )); + vidx = var_idx->data.i; + var_count = var_idx->rows + var_idx->cols - 1; + } + + is_buf_16u = false; + if ( sample_count < 65536 ) + is_buf_16u = true; + + if( !CV_IS_MAT(_responses) || + (CV_MAT_TYPE(_responses->type) != CV_32SC1 && + CV_MAT_TYPE(_responses->type) != CV_32FC1) || + (_responses->rows != 1 && _responses->cols != 1) || + _responses->rows + _responses->cols - 1 != sample_all ) + CV_ERROR( CV_StsBadArg, "The array of _responses must be an integer or " + "floating-point vector containing as many elements as " + "the total number of samples in the training data matrix" ); + + r_type = CV_VAR_CATEGORICAL; + if( _var_type ) + CV_CALL( var_type0 = cvPreprocessVarType( _var_type, var_idx, var_count, &r_type )); + + CV_CALL( var_type = cvCreateMat( 1, var_count+2, CV_32SC1 )); + + cat_var_count = 0; + ord_var_count = -1; + + is_classifier = r_type == CV_VAR_CATEGORICAL; + + // step 0. calc the number of categorical vars + for( vi = 0; vi < var_count; vi++ ) + { + char vt = var_type0 ? var_type0->data.ptr[vi] : CV_VAR_ORDERED; + var_type->data.i[vi] = vt == CV_VAR_CATEGORICAL ? cat_var_count++ : ord_var_count--; + } + + ord_var_count = ~ord_var_count; + cv_n = params.cv_folds; + // set the two last elements of var_type array to be able + // to locate responses and cross-validation labels using + // the corresponding get_* functions. + var_type->data.i[var_count] = cat_var_count; + var_type->data.i[var_count+1] = cat_var_count+1; + + // in case of single ordered predictor we need dummy cv_labels + // for safe split_node_data() operation + have_labels = cv_n > 0 || (ord_var_count == 1 && cat_var_count == 0) || _add_labels; + + work_var_count = var_count + (is_classifier ? 1 : 0) // for responses class_labels + + (have_labels ? 1 : 0); // for cv_labels + + shared = _shared; + buf_count = shared ? 2 : 1; + + buf_size = -1; // the member buf_size is obsolete + + effective_buf_size = (uint64)(work_var_count + 1)*(uint64)sample_count * buf_count; // this is the total size of "CvMat buf" to be allocated + effective_buf_width = sample_count; + effective_buf_height = work_var_count+1; + + if (effective_buf_width >= effective_buf_height) + effective_buf_height *= buf_count; + else + effective_buf_width *= buf_count; + + if ((uint64)effective_buf_width * (uint64)effective_buf_height != effective_buf_size) + { + CV_Error(CV_StsBadArg, "The memory buffer cannot be allocated since its size exceeds integer fields limit"); + } + + + + if ( is_buf_16u ) + { + CV_CALL( buf = cvCreateMat( effective_buf_height, effective_buf_width, CV_16UC1 )); + CV_CALL( pair16u32s_ptr = (CvPair16u32s*)cvAlloc( sample_count*sizeof(pair16u32s_ptr[0]) )); + } + else + { + CV_CALL( buf = cvCreateMat( effective_buf_height, effective_buf_width, CV_32SC1 )); + CV_CALL( int_ptr = (int**)cvAlloc( sample_count*sizeof(int_ptr[0]) )); + } + + size = is_classifier ? (cat_var_count+1) : cat_var_count; + size = !size ? 1 : size; + CV_CALL( cat_count = cvCreateMat( 1, size, CV_32SC1 )); + CV_CALL( cat_ofs = cvCreateMat( 1, size, CV_32SC1 )); + + size = is_classifier ? (cat_var_count + 1)*params.max_categories : cat_var_count*params.max_categories; + size = !size ? 1 : size; + CV_CALL( cat_map = cvCreateMat( 1, size, CV_32SC1 )); + + // now calculate the maximum size of split, + // create memory storage that will keep nodes and splits of the decision tree + // allocate root node and the buffer for the whole training data + max_split_size = cvAlign(sizeof(CvDTreeSplit) + + (MAX(0,sample_count - 33)/32)*sizeof(int),sizeof(void*)); + tree_block_size = MAX((int)sizeof(CvDTreeNode)*8, max_split_size); + tree_block_size = MAX(tree_block_size + block_size_delta, min_block_size); + CV_CALL( tree_storage = cvCreateMemStorage( tree_block_size )); + CV_CALL( node_heap = cvCreateSet( 0, sizeof(*node_heap), sizeof(CvDTreeNode), tree_storage )); + + nv_size = var_count*sizeof(int); + nv_size = cvAlign(MAX( nv_size, (int)sizeof(CvSetElem) ), sizeof(void*)); + + temp_block_size = nv_size; + + if( cv_n ) + { + if( sample_count < cv_n*MAX(params.min_sample_count,10) ) + CV_ERROR( CV_StsOutOfRange, + "The many folds in cross-validation for such a small dataset" ); + + cv_size = cvAlign( cv_n*(sizeof(int) + sizeof(double)*2), sizeof(double) ); + temp_block_size = MAX(temp_block_size, cv_size); + } + + temp_block_size = MAX( temp_block_size + block_size_delta, min_block_size ); + CV_CALL( temp_storage = cvCreateMemStorage( temp_block_size )); + CV_CALL( nv_heap = cvCreateSet( 0, sizeof(*nv_heap), nv_size, temp_storage )); + if( cv_size ) + CV_CALL( cv_heap = cvCreateSet( 0, sizeof(*cv_heap), cv_size, temp_storage )); + + CV_CALL( data_root = new_node( 0, sample_count, 0, 0 )); + + max_c_count = 1; + + _fdst = 0; + _idst = 0; + if (ord_var_count) + _fdst = (float*)cvAlloc(sample_count*sizeof(_fdst[0])); + if (is_buf_16u && (cat_var_count || is_classifier)) + _idst = (int*)cvAlloc(sample_count*sizeof(_idst[0])); + + // transform the training data to convenient representation + for( vi = 0; vi <= var_count; vi++ ) + { + int ci; + const uchar* mask = 0; + int64 m_step = 0, step; + const int* idata = 0; + const float* fdata = 0; + int num_valid = 0; + + if( vi < var_count ) // analyze i-th input variable + { + int vi0 = vidx ? vidx[vi] : vi; + ci = get_var_type(vi); + step = ds_step; m_step = ms_step; + if( CV_MAT_TYPE(_train_data->type) == CV_32SC1 ) + idata = _train_data->data.i + vi0*dv_step; + else + fdata = _train_data->data.fl + vi0*dv_step; + if( _missing_mask ) + mask = _missing_mask->data.ptr + vi0*mv_step; + } + else // analyze _responses + { + ci = cat_var_count; + step = CV_IS_MAT_CONT(_responses->type) ? + 1 : _responses->step / CV_ELEM_SIZE(_responses->type); + if( CV_MAT_TYPE(_responses->type) == CV_32SC1 ) + idata = _responses->data.i; + else + fdata = _responses->data.fl; + } + + if( (vi < var_count && ci>=0) || + (vi == var_count && is_classifier) ) // process categorical variable or response + { + int c_count, prev_label; + int* c_map; + + if (is_buf_16u) + udst = (unsigned short*)(buf->data.s + (size_t)vi*sample_count); + else + idst = buf->data.i + (size_t)vi*sample_count; + + // copy data + for( i = 0; i < sample_count; i++ ) + { + int val = INT_MAX, si = sidx ? sidx[i] : i; + if( !mask || !mask[(size_t)si*m_step] ) + { + if( idata ) + val = idata[(size_t)si*step]; + else + { + float t = fdata[(size_t)si*step]; + val = cvRound(t); + if( fabs(t - val) > FLT_EPSILON ) + { + sprintf( err, "%d-th value of %d-th (categorical) " + "variable is not an integer", i, vi ); + CV_ERROR( CV_StsBadArg, err ); + } + } + + if( val == INT_MAX ) + { + sprintf( err, "%d-th value of %d-th (categorical) " + "variable is too large", i, vi ); + CV_ERROR( CV_StsBadArg, err ); + } + num_valid++; + } + if (is_buf_16u) + { + _idst[i] = val; + pair16u32s_ptr[i].u = udst + i; + pair16u32s_ptr[i].i = _idst + i; + } + else + { + idst[i] = val; + int_ptr[i] = idst + i; + } + } + + c_count = num_valid > 0; + if (is_buf_16u) + { + std::sort(pair16u32s_ptr, pair16u32s_ptr + sample_count, LessThanPairs()); + // count the categories + for( i = 1; i < num_valid; i++ ) + if (*pair16u32s_ptr[i].i != *pair16u32s_ptr[i-1].i) + c_count ++ ; + } + else + { + std::sort(int_ptr, int_ptr + sample_count, LessThanPtr()); + // count the categories + for( i = 1; i < num_valid; i++ ) + c_count += *int_ptr[i] != *int_ptr[i-1]; + } + + if( vi > 0 ) + max_c_count = MAX( max_c_count, c_count ); + cat_count->data.i[ci] = c_count; + cat_ofs->data.i[ci] = total_c_count; + + // resize cat_map, if need + if( cat_map->cols < total_c_count + c_count ) + { + tmp_map = cat_map; + CV_CALL( cat_map = cvCreateMat( 1, + MAX(cat_map->cols*3/2,total_c_count+c_count), CV_32SC1 )); + for( i = 0; i < total_c_count; i++ ) + cat_map->data.i[i] = tmp_map->data.i[i]; + cvReleaseMat( &tmp_map ); + } + + c_map = cat_map->data.i + total_c_count; + total_c_count += c_count; + + c_count = -1; + if (is_buf_16u) + { + // compact the class indices and build the map + prev_label = ~*pair16u32s_ptr[0].i; + for( i = 0; i < num_valid; i++ ) + { + int cur_label = *pair16u32s_ptr[i].i; + if( cur_label != prev_label ) + c_map[++c_count] = prev_label = cur_label; + *pair16u32s_ptr[i].u = (unsigned short)c_count; + } + // replace labels for missing values with -1 + for( ; i < sample_count; i++ ) + *pair16u32s_ptr[i].u = 65535; + } + else + { + // compact the class indices and build the map + prev_label = ~*int_ptr[0]; + for( i = 0; i < num_valid; i++ ) + { + int cur_label = *int_ptr[i]; + if( cur_label != prev_label ) + c_map[++c_count] = prev_label = cur_label; + *int_ptr[i] = c_count; + } + // replace labels for missing values with -1 + for( ; i < sample_count; i++ ) + *int_ptr[i] = -1; + } + } + else if( ci < 0 ) // process ordered variable + { + if (is_buf_16u) + udst = (unsigned short*)(buf->data.s + (size_t)vi*sample_count); + else + idst = buf->data.i + (size_t)vi*sample_count; + + for( i = 0; i < sample_count; i++ ) + { + float val = ord_nan; + int si = sidx ? sidx[i] : i; + if( !mask || !mask[(size_t)si*m_step] ) + { + if( idata ) + val = (float)idata[(size_t)si*step]; + else + val = fdata[(size_t)si*step]; + + if( fabs(val) >= ord_nan ) + { + sprintf( err, "%d-th value of %d-th (ordered) " + "variable (=%g) is too large", i, vi, val ); + CV_ERROR( CV_StsBadArg, err ); + } + num_valid++; + } + + if (is_buf_16u) + udst[i] = (unsigned short)i; // TODO: memory corruption may be here + else + idst[i] = i; + _fdst[i] = val; + + } + if (is_buf_16u) + std::sort(udst, udst + sample_count, LessThanIdx(_fdst)); + else + std::sort(idst, idst + sample_count, LessThanIdx(_fdst)); + } + + if( vi < var_count ) + data_root->set_num_valid(vi, num_valid); + } + + // set sample labels + if (is_buf_16u) + udst = (unsigned short*)(buf->data.s + (size_t)work_var_count*sample_count); + else + idst = buf->data.i + (size_t)work_var_count*sample_count; + + for (i = 0; i < sample_count; i++) + { + if (udst) + udst[i] = sidx ? (unsigned short)sidx[i] : (unsigned short)i; + else + idst[i] = sidx ? sidx[i] : i; + } + + if( cv_n ) + { + unsigned short* usdst = 0; + int* idst2 = 0; + + if (is_buf_16u) + { + usdst = (unsigned short*)(buf->data.s + (size_t)(get_work_var_count()-1)*sample_count); + for( i = vi = 0; i < sample_count; i++ ) + { + usdst[i] = (unsigned short)vi++; + vi &= vi < cv_n ? -1 : 0; + } + + for( i = 0; i < sample_count; i++ ) + { + int a = (*rng)(sample_count); + int b = (*rng)(sample_count); + unsigned short unsh = (unsigned short)vi; + CV_SWAP( usdst[a], usdst[b], unsh ); + } + } + else + { + idst2 = buf->data.i + (size_t)(get_work_var_count()-1)*sample_count; + for( i = vi = 0; i < sample_count; i++ ) + { + idst2[i] = vi++; + vi &= vi < cv_n ? -1 : 0; + } + + for( i = 0; i < sample_count; i++ ) + { + int a = (*rng)(sample_count); + int b = (*rng)(sample_count); + CV_SWAP( idst2[a], idst2[b], vi ); + } + } + } + + if ( cat_map ) + cat_map->cols = MAX( total_c_count, 1 ); + + max_split_size = cvAlign(sizeof(CvDTreeSplit) + + (MAX(0,max_c_count - 33)/32)*sizeof(int),sizeof(void*)); + CV_CALL( split_heap = cvCreateSet( 0, sizeof(*split_heap), max_split_size, tree_storage )); + + have_priors = is_classifier && params.priors; + if( is_classifier ) + { + int m = get_num_classes(); + double sum = 0; + CV_CALL( priors = cvCreateMat( 1, m, CV_64F )); + for( i = 0; i < m; i++ ) + { + double val = have_priors ? params.priors[i] : 1.; + if( val <= 0 ) + CV_ERROR( CV_StsOutOfRange, "Every class weight should be positive" ); + priors->data.db[i] = val; + sum += val; + } + + // normalize weights + if( have_priors ) + cvScale( priors, priors, 1./sum ); + + CV_CALL( priors_mult = cvCloneMat( priors )); + CV_CALL( counts = cvCreateMat( 1, m, CV_32SC1 )); + } + + + CV_CALL( direction = cvCreateMat( 1, sample_count, CV_8UC1 )); + CV_CALL( split_buf = cvCreateMat( 1, sample_count, CV_32SC1 )); + + __END__; + + if( data ) + delete data; + + if (_fdst) + cvFree( &_fdst ); + if (_idst) + cvFree( &_idst ); + cvFree( &int_ptr ); + cvFree( &pair16u32s_ptr); + cvReleaseMat( &var_type0 ); + cvReleaseMat( &sample_indices ); + cvReleaseMat( &tmp_map ); +} + +void CvDTreeTrainData::do_responses_copy() +{ + responses_copy = cvCreateMat( responses->rows, responses->cols, responses->type ); + cvCopy( responses, responses_copy); + responses = responses_copy; +} + +CvDTreeNode* CvDTreeTrainData::subsample_data( const CvMat* _subsample_idx ) +{ + CvDTreeNode* root = 0; + CvMat* isubsample_idx = 0; + CvMat* subsample_co = 0; + + bool isMakeRootCopy = true; + + CV_FUNCNAME( "CvDTreeTrainData::subsample_data" ); + + __BEGIN__; + + if( !data_root ) + CV_ERROR( CV_StsError, "No training data has been set" ); + + if( _subsample_idx ) + { + CV_CALL( isubsample_idx = cvPreprocessIndexArray( _subsample_idx, sample_count )); + + if( isubsample_idx->cols + isubsample_idx->rows - 1 == sample_count ) + { + const int* sidx = isubsample_idx->data.i; + for( int i = 0; i < sample_count; i++ ) + { + if( sidx[i] != i ) + { + isMakeRootCopy = false; + break; + } + } + } + else + isMakeRootCopy = false; + } + + if( isMakeRootCopy ) + { + // make a copy of the root node + CvDTreeNode temp; + int i; + root = new_node( 0, 1, 0, 0 ); + temp = *root; + *root = *data_root; + root->num_valid = temp.num_valid; + if( root->num_valid ) + { + for( i = 0; i < var_count; i++ ) + root->num_valid[i] = data_root->num_valid[i]; + } + root->cv_Tn = temp.cv_Tn; + root->cv_node_risk = temp.cv_node_risk; + root->cv_node_error = temp.cv_node_error; + } + else + { + int* sidx = isubsample_idx->data.i; + // co - array of count/offset pairs (to handle duplicated values in _subsample_idx) + int* co, cur_ofs = 0; + int vi, i; + int workVarCount = get_work_var_count(); + int count = isubsample_idx->rows + isubsample_idx->cols - 1; + + root = new_node( 0, count, 1, 0 ); + + CV_CALL( subsample_co = cvCreateMat( 1, sample_count*2, CV_32SC1 )); + cvZero( subsample_co ); + co = subsample_co->data.i; + for( i = 0; i < count; i++ ) + co[sidx[i]*2]++; + for( i = 0; i < sample_count; i++ ) + { + if( co[i*2] ) + { + co[i*2+1] = cur_ofs; + cur_ofs += co[i*2]; + } + else + co[i*2+1] = -1; + } + + cv::AutoBuffer inn_buf(sample_count*(2*sizeof(int) + sizeof(float))); + for( vi = 0; vi < workVarCount; vi++ ) + { + int ci = get_var_type(vi); + + if( ci >= 0 || vi >= var_count ) + { + int num_valid = 0; + const int* src = CvDTreeTrainData::get_cat_var_data(data_root, vi, (int*)inn_buf.data()); + + if (is_buf_16u) + { + unsigned short* udst = (unsigned short*)(buf->data.s + root->buf_idx*get_length_subbuf() + + (size_t)vi*sample_count + root->offset); + for( i = 0; i < count; i++ ) + { + int val = src[sidx[i]]; + udst[i] = (unsigned short)val; + num_valid += val >= 0; + } + } + else + { + int* idst = buf->data.i + root->buf_idx*get_length_subbuf() + + (size_t)vi*sample_count + root->offset; + for( i = 0; i < count; i++ ) + { + int val = src[sidx[i]]; + idst[i] = val; + num_valid += val >= 0; + } + } + + if( vi < var_count ) + root->set_num_valid(vi, num_valid); + } + else + { + int *src_idx_buf = (int*)inn_buf.data(); + float *src_val_buf = (float*)(src_idx_buf + sample_count); + int* sample_indices_buf = (int*)(src_val_buf + sample_count); + const int* src_idx = 0; + const float* src_val = 0; + get_ord_var_data( data_root, vi, src_val_buf, src_idx_buf, &src_val, &src_idx, sample_indices_buf ); + int j = 0, idx, count_i; + int num_valid = data_root->get_num_valid(vi); + + if (is_buf_16u) + { + unsigned short* udst_idx = (unsigned short*)(buf->data.s + root->buf_idx*get_length_subbuf() + + (size_t)vi*sample_count + data_root->offset); + for( i = 0; i < num_valid; i++ ) + { + idx = src_idx[i]; + count_i = co[idx*2]; + if( count_i ) + for( cur_ofs = co[idx*2+1]; count_i > 0; count_i--, j++, cur_ofs++ ) + udst_idx[j] = (unsigned short)cur_ofs; + } + + root->set_num_valid(vi, j); + + for( ; i < sample_count; i++ ) + { + idx = src_idx[i]; + count_i = co[idx*2]; + if( count_i ) + for( cur_ofs = co[idx*2+1]; count_i > 0; count_i--, j++, cur_ofs++ ) + udst_idx[j] = (unsigned short)cur_ofs; + } + } + else + { + int* idst_idx = buf->data.i + root->buf_idx*get_length_subbuf() + + (size_t)vi*sample_count + root->offset; + for( i = 0; i < num_valid; i++ ) + { + idx = src_idx[i]; + count_i = co[idx*2]; + if( count_i ) + for( cur_ofs = co[idx*2+1]; count_i > 0; count_i--, j++, cur_ofs++ ) + idst_idx[j] = cur_ofs; + } + + root->set_num_valid(vi, j); + + for( ; i < sample_count; i++ ) + { + idx = src_idx[i]; + count_i = co[idx*2]; + if( count_i ) + for( cur_ofs = co[idx*2+1]; count_i > 0; count_i--, j++, cur_ofs++ ) + idst_idx[j] = cur_ofs; + } + } + } + } + // sample indices subsampling + const int* sample_idx_src = get_sample_indices(data_root, (int*)inn_buf.data()); + if (is_buf_16u) + { + unsigned short* sample_idx_dst = (unsigned short*)(buf->data.s + root->buf_idx*get_length_subbuf() + + (size_t)workVarCount*sample_count + root->offset); + for (i = 0; i < count; i++) + sample_idx_dst[i] = (unsigned short)sample_idx_src[sidx[i]]; + } + else + { + int* sample_idx_dst = buf->data.i + root->buf_idx*get_length_subbuf() + + (size_t)workVarCount*sample_count + root->offset; + for (i = 0; i < count; i++) + sample_idx_dst[i] = sample_idx_src[sidx[i]]; + } + } + + __END__; + + cvReleaseMat( &isubsample_idx ); + cvReleaseMat( &subsample_co ); + + return root; +} + + +void CvDTreeTrainData::get_vectors( const CvMat* _subsample_idx, + float* values, uchar* missing, + float* _responses, bool get_class_idx ) +{ + CvMat* subsample_idx = 0; + CvMat* subsample_co = 0; + + CV_FUNCNAME( "CvDTreeTrainData::get_vectors" ); + + __BEGIN__; + + int i, vi, total = sample_count, count = total, cur_ofs = 0; + int* sidx = 0; + int* co = 0; + + cv::AutoBuffer inn_buf(sample_count*(2*sizeof(int) + sizeof(float))); + if( _subsample_idx ) + { + CV_CALL( subsample_idx = cvPreprocessIndexArray( _subsample_idx, sample_count )); + sidx = subsample_idx->data.i; + CV_CALL( subsample_co = cvCreateMat( 1, sample_count*2, CV_32SC1 )); + co = subsample_co->data.i; + cvZero( subsample_co ); + count = subsample_idx->cols + subsample_idx->rows - 1; + for( i = 0; i < count; i++ ) + co[sidx[i]*2]++; + for( i = 0; i < total; i++ ) + { + int count_i = co[i*2]; + if( count_i ) + { + co[i*2+1] = cur_ofs*var_count; + cur_ofs += count_i; + } + } + } + + if( missing ) + memset( missing, 1, count*var_count ); + + for( vi = 0; vi < var_count; vi++ ) + { + int ci = get_var_type(vi); + if( ci >= 0 ) // categorical + { + float* dst = values + vi; + uchar* m = missing ? missing + vi : 0; + const int* src = get_cat_var_data(data_root, vi, (int*)inn_buf.data()); + + for( i = 0; i < count; i++, dst += var_count ) + { + int idx = sidx ? sidx[i] : i; + int val = src[idx]; + *dst = (float)val; + if( m ) + { + *m = (!is_buf_16u && val < 0) || (is_buf_16u && (val == 65535)); + m += var_count; + } + } + } + else // ordered + { + float* dst = values + vi; + uchar* m = missing ? missing + vi : 0; + int count1 = data_root->get_num_valid(vi); + float *src_val_buf = (float*)inn_buf.data(); + int* src_idx_buf = (int*)(src_val_buf + sample_count); + int* sample_indices_buf = src_idx_buf + sample_count; + const float *src_val = 0; + const int* src_idx = 0; + get_ord_var_data(data_root, vi, src_val_buf, src_idx_buf, &src_val, &src_idx, sample_indices_buf); + + for( i = 0; i < count1; i++ ) + { + int idx = src_idx[i]; + int count_i = 1; + if( co ) + { + count_i = co[idx*2]; + cur_ofs = co[idx*2+1]; + } + else + cur_ofs = idx*var_count; + if( count_i ) + { + float val = src_val[i]; + for( ; count_i > 0; count_i--, cur_ofs += var_count ) + { + dst[cur_ofs] = val; + if( m ) + m[cur_ofs] = 0; + } + } + } + } + } + + // copy responses + if( _responses ) + { + if( is_classifier ) + { + const int* src = get_class_labels(data_root, (int*)inn_buf.data()); + for( i = 0; i < count; i++ ) + { + int idx = sidx ? sidx[i] : i; + int val = get_class_idx ? src[idx] : + cat_map->data.i[cat_ofs->data.i[cat_var_count]+src[idx]]; + _responses[i] = (float)val; + } + } + else + { + float* val_buf = (float*)inn_buf.data(); + int* sample_idx_buf = (int*)(val_buf + sample_count); + const float* _values = get_ord_responses(data_root, val_buf, sample_idx_buf); + for( i = 0; i < count; i++ ) + { + int idx = sidx ? sidx[i] : i; + _responses[i] = _values[idx]; + } + } + } + + __END__; + + cvReleaseMat( &subsample_idx ); + cvReleaseMat( &subsample_co ); +} + + +CvDTreeNode* CvDTreeTrainData::new_node( CvDTreeNode* parent, int count, + int storage_idx, int offset ) +{ + CvDTreeNode* node = (CvDTreeNode*)cvSetNew( node_heap ); + + node->sample_count = count; + node->depth = parent ? parent->depth + 1 : 0; + node->parent = parent; + node->left = node->right = 0; + node->split = 0; + node->value = 0; + node->class_idx = 0; + node->maxlr = 0.; + + node->buf_idx = storage_idx; + node->offset = offset; + if( nv_heap ) + node->num_valid = (int*)cvSetNew( nv_heap ); + else + node->num_valid = 0; + node->alpha = node->node_risk = node->tree_risk = node->tree_error = 0.; + node->complexity = 0; + + if( params.cv_folds > 0 && cv_heap ) + { + int cv_n = params.cv_folds; + node->Tn = INT_MAX; + node->cv_Tn = (int*)cvSetNew( cv_heap ); + node->cv_node_risk = (double*)cvAlignPtr(node->cv_Tn + cv_n, sizeof(double)); + node->cv_node_error = node->cv_node_risk + cv_n; + } + else + { + node->Tn = 0; + node->cv_Tn = 0; + node->cv_node_risk = 0; + node->cv_node_error = 0; + } + + return node; +} + + +CvDTreeSplit* CvDTreeTrainData::new_split_ord( int vi, float cmp_val, + int split_point, int inversed, float quality ) +{ + CvDTreeSplit* split = (CvDTreeSplit*)cvSetNew( split_heap ); + split->var_idx = vi; + split->condensed_idx = INT_MIN; + split->ord.c = cmp_val; + split->ord.split_point = split_point; + split->inversed = inversed; + split->quality = quality; + split->next = 0; + + return split; +} + + +CvDTreeSplit* CvDTreeTrainData::new_split_cat( int vi, float quality ) +{ + CvDTreeSplit* split = (CvDTreeSplit*)cvSetNew( split_heap ); + int i, n = (max_c_count + 31)/32; + + split->var_idx = vi; + split->condensed_idx = INT_MIN; + split->inversed = 0; + split->quality = quality; + for( i = 0; i < n; i++ ) + split->subset[i] = 0; + split->next = 0; + + return split; +} + + +void CvDTreeTrainData::free_node( CvDTreeNode* node ) +{ + CvDTreeSplit* split = node->split; + free_node_data( node ); + while( split ) + { + CvDTreeSplit* next = split->next; + cvSetRemoveByPtr( split_heap, split ); + split = next; + } + node->split = 0; + cvSetRemoveByPtr( node_heap, node ); +} + + +void CvDTreeTrainData::free_node_data( CvDTreeNode* node ) +{ + if( node->num_valid ) + { + cvSetRemoveByPtr( nv_heap, node->num_valid ); + node->num_valid = 0; + } + // do not free cv_* fields, as all the cross-validation related data is released at once. +} + + +void CvDTreeTrainData::free_train_data() +{ + cvReleaseMat( &counts ); + cvReleaseMat( &buf ); + cvReleaseMat( &direction ); + cvReleaseMat( &split_buf ); + cvReleaseMemStorage( &temp_storage ); + cvReleaseMat( &responses_copy ); + cv_heap = nv_heap = 0; +} + + +void CvDTreeTrainData::clear() +{ + free_train_data(); + + cvReleaseMemStorage( &tree_storage ); + + cvReleaseMat( &var_idx ); + cvReleaseMat( &var_type ); + cvReleaseMat( &cat_count ); + cvReleaseMat( &cat_ofs ); + cvReleaseMat( &cat_map ); + cvReleaseMat( &priors ); + cvReleaseMat( &priors_mult ); + + node_heap = split_heap = 0; + + sample_count = var_all = var_count = max_c_count = ord_var_count = cat_var_count = 0; + have_labels = have_priors = is_classifier = false; + + buf_count = buf_size = 0; + shared = false; + + data_root = 0; + + rng = &cv::theRNG(); +} + + +int CvDTreeTrainData::get_num_classes() const +{ + return is_classifier ? cat_count->data.i[cat_var_count] : 0; +} + + +int CvDTreeTrainData::get_var_type(int vi) const +{ + return var_type->data.i[vi]; +} + +void CvDTreeTrainData::get_ord_var_data( CvDTreeNode* n, int vi, float* ord_values_buf, int* sorted_indices_buf, + const float** ord_values, const int** sorted_indices, int* sample_indices_buf ) +{ + int vidx = var_idx ? var_idx->data.i[vi] : vi; + int node_sample_count = n->sample_count; + int td_step = train_data->step/CV_ELEM_SIZE(train_data->type); + + const int* sample_indices = get_sample_indices(n, sample_indices_buf); + + if( !is_buf_16u ) + *sorted_indices = buf->data.i + n->buf_idx*get_length_subbuf() + + (size_t)vi*sample_count + n->offset; + else { + const unsigned short* short_indices = (const unsigned short*)(buf->data.s + n->buf_idx*get_length_subbuf() + + (size_t)vi*sample_count + n->offset ); + for( int i = 0; i < node_sample_count; i++ ) + sorted_indices_buf[i] = short_indices[i]; + *sorted_indices = sorted_indices_buf; + } + + if( tflag == CV_ROW_SAMPLE ) + { + for( int i = 0; i < node_sample_count && + ((((*sorted_indices)[i] >= 0) && !is_buf_16u) || (((*sorted_indices)[i] != 65535) && is_buf_16u)); i++ ) + { + int idx = (*sorted_indices)[i]; + idx = sample_indices[idx]; + ord_values_buf[i] = *(train_data->data.fl + idx * td_step + vidx); + } + } + else + for( int i = 0; i < node_sample_count && + ((((*sorted_indices)[i] >= 0) && !is_buf_16u) || (((*sorted_indices)[i] != 65535) && is_buf_16u)); i++ ) + { + int idx = (*sorted_indices)[i]; + idx = sample_indices[idx]; + ord_values_buf[i] = *(train_data->data.fl + vidx* td_step + idx); + } + + *ord_values = ord_values_buf; +} + + +const int* CvDTreeTrainData::get_class_labels( CvDTreeNode* n, int* labels_buf ) +{ + if (is_classifier) + return get_cat_var_data( n, var_count, labels_buf); + return 0; +} + +const int* CvDTreeTrainData::get_sample_indices( CvDTreeNode* n, int* indices_buf ) +{ + return get_cat_var_data( n, get_work_var_count(), indices_buf ); +} + +const float* CvDTreeTrainData::get_ord_responses( CvDTreeNode* n, float* values_buf, int*sample_indices_buf ) +{ + int _sample_count = n->sample_count; + int r_step = CV_IS_MAT_CONT(responses->type) ? 1 : responses->step/CV_ELEM_SIZE(responses->type); + const int* indices = get_sample_indices(n, sample_indices_buf); + + for( int i = 0; i < _sample_count && + (((indices[i] >= 0) && !is_buf_16u) || ((indices[i] != 65535) && is_buf_16u)); i++ ) + { + int idx = indices[i]; + values_buf[i] = *(responses->data.fl + idx * r_step); + } + + return values_buf; +} + + +const int* CvDTreeTrainData::get_cv_labels( CvDTreeNode* n, int* labels_buf ) +{ + if (have_labels) + return get_cat_var_data( n, get_work_var_count()- 1, labels_buf); + return 0; +} + + +const int* CvDTreeTrainData::get_cat_var_data( CvDTreeNode* n, int vi, int* cat_values_buf) +{ + const int* cat_values = 0; + if( !is_buf_16u ) + cat_values = buf->data.i + n->buf_idx*get_length_subbuf() + + (size_t)vi*sample_count + n->offset; + else { + const unsigned short* short_values = (const unsigned short*)(buf->data.s + n->buf_idx*get_length_subbuf() + + (size_t)vi*sample_count + n->offset); + for( int i = 0; i < n->sample_count; i++ ) + cat_values_buf[i] = short_values[i]; + cat_values = cat_values_buf; + } + return cat_values; +} + + +int CvDTreeTrainData::get_child_buf_idx( CvDTreeNode* n ) +{ + int idx = n->buf_idx + 1; + if( idx >= buf_count ) + idx = shared ? 1 : 0; + return idx; +} + + +void CvDTreeTrainData::write_params( CvFileStorage* fs ) const +{ + CV_FUNCNAME( "CvDTreeTrainData::write_params" ); + + __BEGIN__; + + int vi, vcount = var_count; + + cvWriteInt( fs, "is_classifier", is_classifier ? 1 : 0 ); + cvWriteInt( fs, "var_all", var_all ); + cvWriteInt( fs, "var_count", var_count ); + cvWriteInt( fs, "ord_var_count", ord_var_count ); + cvWriteInt( fs, "cat_var_count", cat_var_count ); + + cvStartWriteStruct( fs, "training_params", CV_NODE_MAP ); + cvWriteInt( fs, "use_surrogates", params.use_surrogates ? 1 : 0 ); + + if( is_classifier ) + { + cvWriteInt( fs, "max_categories", params.max_categories ); + } + else + { + cvWriteReal( fs, "regression_accuracy", params.regression_accuracy ); + } + + cvWriteInt( fs, "max_depth", params.max_depth ); + cvWriteInt( fs, "min_sample_count", params.min_sample_count ); + cvWriteInt( fs, "cross_validation_folds", params.cv_folds ); + + if( params.cv_folds > 1 ) + { + cvWriteInt( fs, "use_1se_rule", params.use_1se_rule ? 1 : 0 ); + cvWriteInt( fs, "truncate_pruned_tree", params.truncate_pruned_tree ? 1 : 0 ); + } + + if( priors ) + cvWrite( fs, "priors", priors ); + + cvEndWriteStruct( fs ); + + if( var_idx ) + cvWrite( fs, "var_idx", var_idx ); + + cvStartWriteStruct( fs, "var_type", CV_NODE_SEQ+CV_NODE_FLOW ); + + for( vi = 0; vi < vcount; vi++ ) + cvWriteInt( fs, 0, var_type->data.i[vi] >= 0 ); + + cvEndWriteStruct( fs ); + + if( cat_count && (cat_var_count > 0 || is_classifier) ) + { + CV_ASSERT( cat_count != 0 ); + cvWrite( fs, "cat_count", cat_count ); + cvWrite( fs, "cat_map", cat_map ); + } + + __END__; +} + + +void CvDTreeTrainData::read_params( CvFileStorage* fs, CvFileNode* node ) +{ + CV_FUNCNAME( "CvDTreeTrainData::read_params" ); + + __BEGIN__; + + CvFileNode *tparams_node, *vartype_node; + CvSeqReader reader; + int vi, max_split_size, tree_block_size; + + is_classifier = (cvReadIntByName( fs, node, "is_classifier" ) != 0); + var_all = cvReadIntByName( fs, node, "var_all" ); + var_count = cvReadIntByName( fs, node, "var_count", var_all ); + cat_var_count = cvReadIntByName( fs, node, "cat_var_count" ); + ord_var_count = cvReadIntByName( fs, node, "ord_var_count" ); + + tparams_node = cvGetFileNodeByName( fs, node, "training_params" ); + + if( tparams_node ) // training parameters are not necessary + { + params.use_surrogates = cvReadIntByName( fs, tparams_node, "use_surrogates", 1 ) != 0; + + if( is_classifier ) + { + params.max_categories = cvReadIntByName( fs, tparams_node, "max_categories" ); + } + else + { + params.regression_accuracy = + (float)cvReadRealByName( fs, tparams_node, "regression_accuracy" ); + } + + params.max_depth = cvReadIntByName( fs, tparams_node, "max_depth" ); + params.min_sample_count = cvReadIntByName( fs, tparams_node, "min_sample_count" ); + params.cv_folds = cvReadIntByName( fs, tparams_node, "cross_validation_folds" ); + + if( params.cv_folds > 1 ) + { + params.use_1se_rule = cvReadIntByName( fs, tparams_node, "use_1se_rule" ) != 0; + params.truncate_pruned_tree = + cvReadIntByName( fs, tparams_node, "truncate_pruned_tree" ) != 0; + } + + priors = (CvMat*)cvReadByName( fs, tparams_node, "priors" ); + if( priors ) + { + if( !CV_IS_MAT(priors) ) + CV_ERROR( CV_StsParseError, "priors must stored as a matrix" ); + priors_mult = cvCloneMat( priors ); + } + } + + CV_CALL( var_idx = (CvMat*)cvReadByName( fs, node, "var_idx" )); + if( var_idx ) + { + if( !CV_IS_MAT(var_idx) || + (var_idx->cols != 1 && var_idx->rows != 1) || + var_idx->cols + var_idx->rows - 1 != var_count || + CV_MAT_TYPE(var_idx->type) != CV_32SC1 ) + CV_ERROR( CV_StsParseError, + "var_idx (if exist) must be valid 1d integer vector containing elements" ); + + for( vi = 0; vi < var_count; vi++ ) + if( (unsigned)var_idx->data.i[vi] >= (unsigned)var_all ) + CV_ERROR( CV_StsOutOfRange, "some of var_idx elements are out of range" ); + } + + ////// read var type + CV_CALL( var_type = cvCreateMat( 1, var_count + 2, CV_32SC1 )); + + cat_var_count = 0; + ord_var_count = -1; + vartype_node = cvGetFileNodeByName( fs, node, "var_type" ); + + if( vartype_node && CV_NODE_TYPE(vartype_node->tag) == CV_NODE_INT && var_count == 1 ) + var_type->data.i[0] = vartype_node->data.i ? cat_var_count++ : ord_var_count--; + else + { + if( !vartype_node || CV_NODE_TYPE(vartype_node->tag) != CV_NODE_SEQ || + vartype_node->data.seq->total != var_count ) + CV_ERROR( CV_StsParseError, "var_type must exist and be a sequence of 0's and 1's" ); + + cvStartReadSeq( vartype_node->data.seq, &reader ); + + for( vi = 0; vi < var_count; vi++ ) + { + CvFileNode* n = (CvFileNode*)reader.ptr; + if( CV_NODE_TYPE(n->tag) != CV_NODE_INT || (n->data.i & ~1) ) + CV_ERROR( CV_StsParseError, "var_type must exist and be a sequence of 0's and 1's" ); + var_type->data.i[vi] = n->data.i ? cat_var_count++ : ord_var_count--; + CV_NEXT_SEQ_ELEM( reader.seq->elem_size, reader ); + } + } + var_type->data.i[var_count] = cat_var_count; + + ord_var_count = ~ord_var_count; + ////// + + if( cat_var_count > 0 || is_classifier ) + { + int ccount, total_c_count = 0; + CV_CALL( cat_count = (CvMat*)cvReadByName( fs, node, "cat_count" )); + CV_CALL( cat_map = (CvMat*)cvReadByName( fs, node, "cat_map" )); + + if( !CV_IS_MAT(cat_count) || !CV_IS_MAT(cat_map) || + (cat_count->cols != 1 && cat_count->rows != 1) || + CV_MAT_TYPE(cat_count->type) != CV_32SC1 || + cat_count->cols + cat_count->rows - 1 != cat_var_count + is_classifier || + (cat_map->cols != 1 && cat_map->rows != 1) || + CV_MAT_TYPE(cat_map->type) != CV_32SC1 ) + CV_ERROR( CV_StsParseError, + "Both cat_count and cat_map must exist and be valid 1d integer vectors of an appropriate size" ); + + ccount = cat_var_count + is_classifier; + + CV_CALL( cat_ofs = cvCreateMat( 1, ccount + 1, CV_32SC1 )); + cat_ofs->data.i[0] = 0; + max_c_count = 1; + + for( vi = 0; vi < ccount; vi++ ) + { + int val = cat_count->data.i[vi]; + if( val <= 0 ) + CV_ERROR( CV_StsOutOfRange, "some of cat_count elements are out of range" ); + max_c_count = MAX( max_c_count, val ); + cat_ofs->data.i[vi+1] = total_c_count += val; + } + + if( cat_map->cols + cat_map->rows - 1 != total_c_count ) + CV_ERROR( CV_StsBadSize, + "cat_map vector length is not equal to the total number of categories in all categorical vars" ); + } + + max_split_size = cvAlign(sizeof(CvDTreeSplit) + + (MAX(0,max_c_count - 33)/32)*sizeof(int),sizeof(void*)); + + tree_block_size = MAX((int)sizeof(CvDTreeNode)*8, max_split_size); + tree_block_size = MAX(tree_block_size + block_size_delta, min_block_size); + CV_CALL( tree_storage = cvCreateMemStorage( tree_block_size )); + CV_CALL( node_heap = cvCreateSet( 0, sizeof(node_heap[0]), + sizeof(CvDTreeNode), tree_storage )); + CV_CALL( split_heap = cvCreateSet( 0, sizeof(split_heap[0]), + max_split_size, tree_storage )); + + __END__; +} + +/////////////////////// Decision Tree ///////////////////////// +CvDTreeParams::CvDTreeParams() : max_categories(10), max_depth(INT_MAX), min_sample_count(10), + cv_folds(10), use_surrogates(true), use_1se_rule(true), + truncate_pruned_tree(true), regression_accuracy(0.01f), priors(0) +{} + +CvDTreeParams::CvDTreeParams( int _max_depth, int _min_sample_count, + float _regression_accuracy, bool _use_surrogates, + int _max_categories, int _cv_folds, + bool _use_1se_rule, bool _truncate_pruned_tree, + const float* _priors ) : + max_categories(_max_categories), max_depth(_max_depth), + min_sample_count(_min_sample_count), cv_folds (_cv_folds), + use_surrogates(_use_surrogates), use_1se_rule(_use_1se_rule), + truncate_pruned_tree(_truncate_pruned_tree), + regression_accuracy(_regression_accuracy), + priors(_priors) +{} + +CvDTree::CvDTree() +{ + data = 0; + var_importance = 0; + default_model_name = "my_tree"; + + clear(); +} + + +void CvDTree::clear() +{ + cvReleaseMat( &var_importance ); + if( data ) + { + if( !data->shared ) + delete data; + else + free_tree(); + data = 0; + } + root = 0; + pruned_tree_idx = -1; +} + + +CvDTree::~CvDTree() +{ + clear(); +} + + +const CvDTreeNode* CvDTree::get_root() const +{ + return root; +} + + +int CvDTree::get_pruned_tree_idx() const +{ + return pruned_tree_idx; +} + + +CvDTreeTrainData* CvDTree::get_data() +{ + return data; +} + + +bool CvDTree::train( const CvMat* _train_data, int _tflag, + const CvMat* _responses, const CvMat* _var_idx, + const CvMat* _sample_idx, const CvMat* _var_type, + const CvMat* _missing_mask, CvDTreeParams _params ) +{ + bool result = false; + + CV_FUNCNAME( "CvDTree::train" ); + + __BEGIN__; + + clear(); + data = new CvDTreeTrainData( _train_data, _tflag, _responses, + _var_idx, _sample_idx, _var_type, + _missing_mask, _params, false ); + CV_CALL( result = do_train(0) ); + + __END__; + + return result; +} + +bool CvDTree::train( const Mat& _train_data, int _tflag, + const Mat& _responses, const Mat& _var_idx, + const Mat& _sample_idx, const Mat& _var_type, + const Mat& _missing_mask, CvDTreeParams _params ) +{ + train_data_hdr = cvMat(_train_data); + train_data_mat = _train_data; + responses_hdr = cvMat(_responses); + responses_mat = _responses; + + CvMat vidx=cvMat(_var_idx), sidx=cvMat(_sample_idx), vtype=cvMat(_var_type), mmask=cvMat(_missing_mask); + + return train(&train_data_hdr, _tflag, &responses_hdr, vidx.data.ptr ? &vidx : 0, sidx.data.ptr ? &sidx : 0, + vtype.data.ptr ? &vtype : 0, mmask.data.ptr ? &mmask : 0, _params); +} + + +bool CvDTree::train( CvMLData* _data, CvDTreeParams _params ) +{ + bool result = false; + + CV_FUNCNAME( "CvDTree::train" ); + + __BEGIN__; + + const CvMat* values = _data->get_values(); + const CvMat* response = _data->get_responses(); + const CvMat* missing = _data->get_missing(); + const CvMat* var_types = _data->get_var_types(); + const CvMat* train_sidx = _data->get_train_sample_idx(); + const CvMat* var_idx = _data->get_var_idx(); + + CV_CALL( result = train( values, CV_ROW_SAMPLE, response, var_idx, + train_sidx, var_types, missing, _params ) ); + + __END__; + + return result; +} + +bool CvDTree::train( CvDTreeTrainData* _data, const CvMat* _subsample_idx ) +{ + bool result = false; + + CV_FUNCNAME( "CvDTree::train" ); + + __BEGIN__; + + clear(); + data = _data; + data->shared = true; + CV_CALL( result = do_train(_subsample_idx)); + + __END__; + + return result; +} + + +bool CvDTree::do_train( const CvMat* _subsample_idx ) +{ + bool result = false; + + CV_FUNCNAME( "CvDTree::do_train" ); + + __BEGIN__; + + root = data->subsample_data( _subsample_idx ); + + CV_CALL( try_split_node(root)); + + if( root->split ) + { + CV_Assert( root->left ); + CV_Assert( root->right ); + + if( data->params.cv_folds > 0 ) + CV_CALL( prune_cv() ); + + if( !data->shared ) + data->free_train_data(); + + result = true; + } + + __END__; + + return result; +} + + +void CvDTree::try_split_node( CvDTreeNode* node ) +{ + CvDTreeSplit* best_split = 0; + int i, n = node->sample_count, vi; + bool can_split = true; + double quality_scale; + + calc_node_value( node ); + + if( node->sample_count <= data->params.min_sample_count || + node->depth >= data->params.max_depth ) + can_split = false; + + if( can_split && data->is_classifier ) + { + // check if we have a "pure" node, + // we assume that cls_count is filled by calc_node_value() + int* cls_count = data->counts->data.i; + int nz = 0, m = data->get_num_classes(); + for( i = 0; i < m; i++ ) + nz += cls_count[i] != 0; + if( nz == 1 ) // there is only one class + can_split = false; + } + else if( can_split ) + { + if( sqrt(node->node_risk)/n < data->params.regression_accuracy ) + can_split = false; + } + + if( can_split ) + { + best_split = find_best_split(node); + // TODO: check the split quality ... + node->split = best_split; + } + if( !can_split || !best_split ) + { + data->free_node_data(node); + return; + } + + quality_scale = calc_node_dir( node ); + if( data->params.use_surrogates ) + { + // find all the surrogate splits + // and sort them by their similarity to the primary one + for( vi = 0; vi < data->var_count; vi++ ) + { + CvDTreeSplit* split; + int ci = data->get_var_type(vi); + + if( vi == best_split->var_idx ) + continue; + + if( ci >= 0 ) + split = find_surrogate_split_cat( node, vi ); + else + split = find_surrogate_split_ord( node, vi ); + + if( split ) + { + // insert the split + CvDTreeSplit* prev_split = node->split; + split->quality = (float)(split->quality*quality_scale); + + while( prev_split->next && + prev_split->next->quality > split->quality ) + prev_split = prev_split->next; + split->next = prev_split->next; + prev_split->next = split; + } + } + } + split_node_data( node ); + try_split_node( node->left ); + try_split_node( node->right ); +} + + +// calculate direction (left(-1),right(1),missing(0)) +// for each sample using the best split +// the function returns scale coefficients for surrogate split quality factors. +// the scale is applied to normalize surrogate split quality relatively to the +// best (primary) split quality. That is, if a surrogate split is absolutely +// identical to the primary split, its quality will be set to the maximum value = +// quality of the primary split; otherwise, it will be lower. +// besides, the function compute node->maxlr, +// minimum possible quality (w/o considering the above mentioned scale) +// for a surrogate split. Surrogate splits with quality less than node->maxlr +// are not discarded. +double CvDTree::calc_node_dir( CvDTreeNode* node ) +{ + char* dir = (char*)data->direction->data.ptr; + int i, n = node->sample_count, vi = node->split->var_idx; + double L, R; + + assert( !node->split->inversed ); + + if( data->get_var_type(vi) >= 0 ) // split on categorical var + { + cv::AutoBuffer inn_buf(n*(!data->have_priors ? 1 : 2)); + int* labels_buf = inn_buf.data(); + const int* labels = data->get_cat_var_data( node, vi, labels_buf ); + const int* subset = node->split->subset; + if( !data->have_priors ) + { + int sum = 0, sum_abs = 0; + + for( i = 0; i < n; i++ ) + { + int idx = labels[i]; + int d = ( ((idx >= 0)&&(!data->is_buf_16u)) || ((idx != 65535)&&(data->is_buf_16u)) ) ? + CV_DTREE_CAT_DIR(idx,subset) : 0; + sum += d; sum_abs += d & 1; + dir[i] = (char)d; + } + + R = (sum_abs + sum) >> 1; + L = (sum_abs - sum) >> 1; + } + else + { + const double* priors = data->priors_mult->data.db; + double sum = 0, sum_abs = 0; + int* responses_buf = labels_buf + n; + const int* responses = data->get_class_labels(node, responses_buf); + + for( i = 0; i < n; i++ ) + { + int idx = labels[i]; + double w = priors[responses[i]]; + int d = idx >= 0 ? CV_DTREE_CAT_DIR(idx,subset) : 0; + sum += d*w; sum_abs += (d & 1)*w; + dir[i] = (char)d; + } + + R = (sum_abs + sum) * 0.5; + L = (sum_abs - sum) * 0.5; + } + } + else // split on ordered var + { + int split_point = node->split->ord.split_point; + int n1 = node->get_num_valid(vi); + cv::AutoBuffer inn_buf(n*(sizeof(int)*(data->have_priors ? 3 : 2) + sizeof(float))); + float* val_buf = (float*)inn_buf.data(); + int* sorted_buf = (int*)(val_buf + n); + int* sample_idx_buf = sorted_buf + n; + const float* val = 0; + const int* sorted = 0; + data->get_ord_var_data( node, vi, val_buf, sorted_buf, &val, &sorted, sample_idx_buf); + + assert( 0 <= split_point && split_point < n1-1 ); + + if( !data->have_priors ) + { + for( i = 0; i <= split_point; i++ ) + dir[sorted[i]] = (char)-1; + for( ; i < n1; i++ ) + dir[sorted[i]] = (char)1; + for( ; i < n; i++ ) + dir[sorted[i]] = (char)0; + + L = split_point-1; + R = n1 - split_point + 1; + } + else + { + const double* priors = data->priors_mult->data.db; + int* responses_buf = sample_idx_buf + n; + const int* responses = data->get_class_labels(node, responses_buf); + L = R = 0; + + for( i = 0; i <= split_point; i++ ) + { + int idx = sorted[i]; + double w = priors[responses[idx]]; + dir[idx] = (char)-1; + L += w; + } + + for( ; i < n1; i++ ) + { + int idx = sorted[i]; + double w = priors[responses[idx]]; + dir[idx] = (char)1; + R += w; + } + + for( ; i < n; i++ ) + dir[sorted[i]] = (char)0; + } + } + node->maxlr = MAX( L, R ); + return node->split->quality/(L + R); +} + + +namespace cv +{ + +void DefaultDeleter::operator ()(CvDTreeSplit* obj) const { fastFree(obj); } + +DTreeBestSplitFinder::DTreeBestSplitFinder( CvDTree* _tree, CvDTreeNode* _node) +{ + tree = _tree; + node = _node; + splitSize = tree->get_data()->split_heap->elem_size; + + bestSplit.reset((CvDTreeSplit*)fastMalloc(splitSize)); + memset(bestSplit.get(), 0, splitSize); + bestSplit->quality = -1; + bestSplit->condensed_idx = INT_MIN; + split.reset((CvDTreeSplit*)fastMalloc(splitSize)); + memset(split.get(), 0, splitSize); + //haveSplit = false; +} + +DTreeBestSplitFinder::DTreeBestSplitFinder( const DTreeBestSplitFinder& finder, Split ) +{ + tree = finder.tree; + node = finder.node; + splitSize = tree->get_data()->split_heap->elem_size; + + bestSplit.reset((CvDTreeSplit*)fastMalloc(splitSize)); + memcpy(bestSplit.get(), finder.bestSplit.get(), splitSize); + split.reset((CvDTreeSplit*)fastMalloc(splitSize)); + memset(split.get(), 0, splitSize); +} + +void DTreeBestSplitFinder::operator()(const BlockedRange& range) +{ + int vi, vi1 = range.begin(), vi2 = range.end(); + int n = node->sample_count; + CvDTreeTrainData* data = tree->get_data(); + AutoBuffer inn_buf(2*n*(sizeof(int) + sizeof(float))); + + for( vi = vi1; vi < vi2; vi++ ) + { + CvDTreeSplit *res; + int ci = data->get_var_type(vi); + if( node->get_num_valid(vi) <= 1 ) + continue; + + if( data->is_classifier ) + { + if( ci >= 0 ) + res = tree->find_split_cat_class( node, vi, bestSplit->quality, split, inn_buf.data() ); + else + res = tree->find_split_ord_class( node, vi, bestSplit->quality, split, inn_buf.data() ); + } + else + { + if( ci >= 0 ) + res = tree->find_split_cat_reg( node, vi, bestSplit->quality, split, inn_buf.data() ); + else + res = tree->find_split_ord_reg( node, vi, bestSplit->quality, split, inn_buf.data() ); + } + + if( res && bestSplit->quality < split->quality ) + memcpy( bestSplit.get(), split.get(), splitSize ); + } +} + +void DTreeBestSplitFinder::join( DTreeBestSplitFinder& rhs ) +{ + if( bestSplit->quality < rhs.bestSplit->quality ) + memcpy( bestSplit.get(), rhs.bestSplit.get(), splitSize ); +} +} + + +CvDTreeSplit* CvDTree::find_best_split( CvDTreeNode* node ) +{ + DTreeBestSplitFinder finder( this, node ); + + cv::parallel_reduce(cv::BlockedRange(0, data->var_count), finder); + + CvDTreeSplit *bestSplit = 0; + if( finder.bestSplit->quality > 0 ) + { + bestSplit = data->new_split_cat( 0, -1.0f ); + memcpy( bestSplit, finder.bestSplit, finder.splitSize ); + } + + return bestSplit; +} + +CvDTreeSplit* CvDTree::find_split_ord_class( CvDTreeNode* node, int vi, + float init_quality, CvDTreeSplit* _split, uchar* _ext_buf ) +{ + const float epsilon = FLT_EPSILON*2; + int n = node->sample_count; + int n1 = node->get_num_valid(vi); + int m = data->get_num_classes(); + + int base_size = 2*m*sizeof(int); + cv::AutoBuffer inn_buf(base_size); + if( !_ext_buf ) + inn_buf.allocate(base_size + n*(3*sizeof(int)+sizeof(float))); + uchar* base_buf = inn_buf.data(); + uchar* ext_buf = _ext_buf ? _ext_buf : base_buf + base_size; + float* values_buf = (float*)ext_buf; + int* sorted_indices_buf = (int*)(values_buf + n); + int* sample_indices_buf = sorted_indices_buf + n; + const float* values = 0; + const int* sorted_indices = 0; + data->get_ord_var_data( node, vi, values_buf, sorted_indices_buf, &values, + &sorted_indices, sample_indices_buf ); + int* responses_buf = sample_indices_buf + n; + const int* responses = data->get_class_labels( node, responses_buf ); + + const int* rc0 = data->counts->data.i; + int* lc = (int*)base_buf; + int* rc = lc + m; + int i, best_i = -1; + double lsum2 = 0, rsum2 = 0, best_val = init_quality; + const double* priors = data->have_priors ? data->priors_mult->data.db : 0; + + // init arrays of class instance counters on both sides of the split + for( i = 0; i < m; i++ ) + { + lc[i] = 0; + rc[i] = rc0[i]; + } + + // compensate for missing values + for( i = n1; i < n; i++ ) + { + rc[responses[sorted_indices[i]]]--; + } + + if( !priors ) + { + int L = 0, R = n1; + + for( i = 0; i < m; i++ ) + rsum2 += (double)rc[i]*rc[i]; + + for( i = 0; i < n1 - 1; i++ ) + { + int idx = responses[sorted_indices[i]]; + int lv, rv; + L++; R--; + lv = lc[idx]; rv = rc[idx]; + lsum2 += lv*2 + 1; + rsum2 -= rv*2 - 1; + lc[idx] = lv + 1; rc[idx] = rv - 1; + + if( values[i] + epsilon < values[i+1] ) + { + double val = (lsum2*R + rsum2*L)/((double)L*R); + if( best_val < val ) + { + best_val = val; + best_i = i; + } + } + } + } + else + { + double L = 0, R = 0; + for( i = 0; i < m; i++ ) + { + double wv = rc[i]*priors[i]; + R += wv; + rsum2 += wv*wv; + } + + for( i = 0; i < n1 - 1; i++ ) + { + int idx = responses[sorted_indices[i]]; + int lv, rv; + double p = priors[idx], p2 = p*p; + L += p; R -= p; + lv = lc[idx]; rv = rc[idx]; + lsum2 += p2*(lv*2 + 1); + rsum2 -= p2*(rv*2 - 1); + lc[idx] = lv + 1; rc[idx] = rv - 1; + + if( values[i] + epsilon < values[i+1] ) + { + double val = (lsum2*R + rsum2*L)/((double)L*R); + if( best_val < val ) + { + best_val = val; + best_i = i; + } + } + } + } + + CvDTreeSplit* split = 0; + if( best_i >= 0 ) + { + split = _split ? _split : data->new_split_ord( 0, 0.0f, 0, 0, 0.0f ); + split->var_idx = vi; + split->ord.c = (values[best_i] + values[best_i+1])*0.5f; + split->ord.split_point = best_i; + split->inversed = 0; + split->quality = (float)best_val; + } + return split; +} + + +void CvDTree::cluster_categories( const int* vectors, int n, int m, + int* csums, int k, int* labels ) +{ + // TODO: consider adding priors (class weights) and sample weights to the clustering algorithm + int iters = 0, max_iters = 100; + int i, j, idx; + cv::AutoBuffer buf(n + k); + double *v_weights = buf.data(), *c_weights = buf.data() + n; + bool modified = true; + RNG* r = data->rng; + + // assign labels randomly + for( i = 0; i < n; i++ ) + { + int sum = 0; + const int* v = vectors + i*m; + labels[i] = i < k ? i : r->uniform(0, k); + + // compute weight of each vector + for( j = 0; j < m; j++ ) + sum += v[j]; + v_weights[i] = sum ? 1./sum : 0.; + } + + for( i = 0; i < n; i++ ) + { + int i1 = (*r)(n); + int i2 = (*r)(n); + CV_SWAP( labels[i1], labels[i2], j ); + } + + for( iters = 0; iters <= max_iters; iters++ ) + { + // calculate csums + for( i = 0; i < k; i++ ) + { + for( j = 0; j < m; j++ ) + csums[i*m + j] = 0; + } + + for( i = 0; i < n; i++ ) + { + const int* v = vectors + i*m; + int* s = csums + labels[i]*m; + for( j = 0; j < m; j++ ) + s[j] += v[j]; + } + + // exit the loop here, when we have up-to-date csums + if( iters == max_iters || !modified ) + break; + + modified = false; + + // calculate weight of each cluster + for( i = 0; i < k; i++ ) + { + const int* s = csums + i*m; + int sum = 0; + for( j = 0; j < m; j++ ) + sum += s[j]; + c_weights[i] = sum ? 1./sum : 0; + } + + // now for each vector determine the closest cluster + for( i = 0; i < n; i++ ) + { + const int* v = vectors + i*m; + double alpha = v_weights[i]; + double min_dist2 = DBL_MAX; + int min_idx = -1; + + for( idx = 0; idx < k; idx++ ) + { + const int* s = csums + idx*m; + double dist2 = 0., beta = c_weights[idx]; + for( j = 0; j < m; j++ ) + { + double t = v[j]*alpha - s[j]*beta; + dist2 += t*t; + } + if( min_dist2 > dist2 ) + { + min_dist2 = dist2; + min_idx = idx; + } + } + + if( min_idx != labels[i] ) + modified = true; + labels[i] = min_idx; + } + } +} + + +CvDTreeSplit* CvDTree::find_split_cat_class( CvDTreeNode* node, int vi, float init_quality, + CvDTreeSplit* _split, uchar* _ext_buf ) +{ + int ci = data->get_var_type(vi); + int n = node->sample_count; + int m = data->get_num_classes(); + int _mi = data->cat_count->data.i[ci], mi = _mi; + + int base_size = m*(3 + mi)*sizeof(int) + (mi+1)*sizeof(double); + if( m > 2 && mi > data->params.max_categories ) + base_size += (m*std::min(data->params.max_categories, n) + mi)*sizeof(int); + else + base_size += mi*sizeof(int*); + cv::AutoBuffer inn_buf(base_size); + if( !_ext_buf ) + inn_buf.allocate(base_size + 2*n*sizeof(int)); + uchar* base_buf = inn_buf.data(); + uchar* ext_buf = _ext_buf ? _ext_buf : base_buf + base_size; + + int* lc = (int*)base_buf; + int* rc = lc + m; + int* _cjk = rc + m*2, *cjk = _cjk; + double* c_weights = (double*)alignPtr(cjk + m*mi, sizeof(double)); + + int* labels_buf = (int*)ext_buf; + const int* labels = data->get_cat_var_data(node, vi, labels_buf); + int* responses_buf = labels_buf + n; + const int* responses = data->get_class_labels(node, responses_buf); + + int* cluster_labels = 0; + int** int_ptr = 0; + int i, j, k, idx; + double L = 0, R = 0; + double best_val = init_quality; + int prevcode = 0, best_subset = -1, subset_i, subset_n, subtract = 0; + const double* priors = data->priors_mult->data.db; + + // init array of counters: + // c_{jk} - number of samples that have vi-th input variable = j and response = k. + for( j = -1; j < mi; j++ ) + for( k = 0; k < m; k++ ) + cjk[j*m + k] = 0; + + for( i = 0; i < n; i++ ) + { + j = ( labels[i] == 65535 && data->is_buf_16u) ? -1 : labels[i]; + k = responses[i]; + cjk[j*m + k]++; + } + + if( m > 2 ) + { + if( mi > data->params.max_categories ) + { + mi = MIN(data->params.max_categories, n); + cjk = (int*)(c_weights + _mi); + cluster_labels = cjk + m*mi; + cluster_categories( _cjk, _mi, m, cjk, mi, cluster_labels ); + } + subset_i = 1; + subset_n = 1 << mi; + } + else + { + assert( m == 2 ); + int_ptr = (int**)(c_weights + _mi); + for( j = 0; j < mi; j++ ) + int_ptr[j] = cjk + j*2 + 1; + std::sort(int_ptr, int_ptr + mi, LessThanPtr()); + subset_i = 0; + subset_n = mi; + } + + for( k = 0; k < m; k++ ) + { + int sum = 0; + for( j = 0; j < mi; j++ ) + sum += cjk[j*m + k]; + rc[k] = sum; + lc[k] = 0; + } + + for( j = 0; j < mi; j++ ) + { + double sum = 0; + for( k = 0; k < m; k++ ) + sum += cjk[j*m + k]*priors[k]; + c_weights[j] = sum; + R += c_weights[j]; + } + + for( ; subset_i < subset_n; subset_i++ ) + { + double weight; + int* crow; + double lsum2 = 0, rsum2 = 0; + + if( m == 2 ) + idx = (int)(int_ptr[subset_i] - cjk)/2; + else + { + int graycode = (subset_i>>1)^subset_i; + int diff = graycode ^ prevcode; + + // determine index of the changed bit. + Cv32suf u; + idx = diff >= (1 << 16) ? 16 : 0; + u.f = (float)(((diff >> 16) | diff) & 65535); + idx += (u.i >> 23) - 127; + subtract = graycode < prevcode; + prevcode = graycode; + } + + crow = cjk + idx*m; + weight = c_weights[idx]; + if( weight < FLT_EPSILON ) + continue; + + if( !subtract ) + { + for( k = 0; k < m; k++ ) + { + int t = crow[k]; + int lval = lc[k] + t; + int rval = rc[k] - t; + double p = priors[k], p2 = p*p; + lsum2 += p2*lval*lval; + rsum2 += p2*rval*rval; + lc[k] = lval; rc[k] = rval; + } + L += weight; + R -= weight; + } + else + { + for( k = 0; k < m; k++ ) + { + int t = crow[k]; + int lval = lc[k] - t; + int rval = rc[k] + t; + double p = priors[k], p2 = p*p; + lsum2 += p2*lval*lval; + rsum2 += p2*rval*rval; + lc[k] = lval; rc[k] = rval; + } + L -= weight; + R += weight; + } + + if( L > FLT_EPSILON && R > FLT_EPSILON ) + { + double val = (lsum2*R + rsum2*L)/((double)L*R); + if( best_val < val ) + { + best_val = val; + best_subset = subset_i; + } + } + } + + CvDTreeSplit* split = 0; + if( best_subset >= 0 ) + { + split = _split ? _split : data->new_split_cat( 0, -1.0f ); + split->var_idx = vi; + split->quality = (float)best_val; + memset( split->subset, 0, (data->max_c_count + 31)/32 * sizeof(int)); + if( m == 2 ) + { + for( i = 0; i <= best_subset; i++ ) + { + idx = (int)(int_ptr[i] - cjk) >> 1; + split->subset[idx >> 5] |= 1 << (idx & 31); + } + } + else + { + for( i = 0; i < _mi; i++ ) + { + idx = cluster_labels ? cluster_labels[i] : i; + if( best_subset & (1 << idx) ) + split->subset[i >> 5] |= 1 << (i & 31); + } + } + } + return split; +} + + +CvDTreeSplit* CvDTree::find_split_ord_reg( CvDTreeNode* node, int vi, float init_quality, CvDTreeSplit* _split, uchar* _ext_buf ) +{ + const float epsilon = FLT_EPSILON*2; + int n = node->sample_count; + int n1 = node->get_num_valid(vi); + + cv::AutoBuffer inn_buf; + if( !_ext_buf ) + inn_buf.allocate(2*n*(sizeof(int) + sizeof(float))); + uchar* ext_buf = _ext_buf ? _ext_buf : inn_buf.data(); + float* values_buf = (float*)ext_buf; + int* sorted_indices_buf = (int*)(values_buf + n); + int* sample_indices_buf = sorted_indices_buf + n; + const float* values = 0; + const int* sorted_indices = 0; + data->get_ord_var_data( node, vi, values_buf, sorted_indices_buf, &values, &sorted_indices, sample_indices_buf ); + float* responses_buf = (float*)(sample_indices_buf + n); + const float* responses = data->get_ord_responses( node, responses_buf, sample_indices_buf ); + + int i, best_i = -1; + double best_val = init_quality, lsum = 0, rsum = node->value*n; + int L = 0, R = n1; + + // compensate for missing values + for( i = n1; i < n; i++ ) + rsum -= responses[sorted_indices[i]]; + + // find the optimal split + for( i = 0; i < n1 - 1; i++ ) + { + float t = responses[sorted_indices[i]]; + L++; R--; + lsum += t; + rsum -= t; + + if( values[i] + epsilon < values[i+1] ) + { + double val = (lsum*lsum*R + rsum*rsum*L)/((double)L*R); + if( best_val < val ) + { + best_val = val; + best_i = i; + } + } + } + + CvDTreeSplit* split = 0; + if( best_i >= 0 ) + { + split = _split ? _split : data->new_split_ord( 0, 0.0f, 0, 0, 0.0f ); + split->var_idx = vi; + split->ord.c = (values[best_i] + values[best_i+1])*0.5f; + split->ord.split_point = best_i; + split->inversed = 0; + split->quality = (float)best_val; + } + return split; +} + +CvDTreeSplit* CvDTree::find_split_cat_reg( CvDTreeNode* node, int vi, float init_quality, CvDTreeSplit* _split, uchar* _ext_buf ) +{ + int ci = data->get_var_type(vi); + int n = node->sample_count; + int mi = data->cat_count->data.i[ci]; + + int base_size = (mi+2)*sizeof(double) + (mi+1)*(sizeof(int) + sizeof(double*)); + cv::AutoBuffer inn_buf(base_size); + if( !_ext_buf ) + inn_buf.allocate(base_size + n*(2*sizeof(int) + sizeof(float))); + uchar* base_buf = inn_buf.data(); + uchar* ext_buf = _ext_buf ? _ext_buf : base_buf + base_size; + int* labels_buf = (int*)ext_buf; + const int* labels = data->get_cat_var_data(node, vi, labels_buf); + float* responses_buf = (float*)(labels_buf + n); + int* sample_indices_buf = (int*)(responses_buf + n); + const float* responses = data->get_ord_responses(node, responses_buf, sample_indices_buf); + + double* sum = (double*)cv::alignPtr(base_buf,sizeof(double)) + 1; + int* counts = (int*)(sum + mi) + 1; + double** sum_ptr = (double**)(counts + mi); + int i, L = 0, R = 0; + double best_val = init_quality, lsum = 0, rsum = 0; + int best_subset = -1, subset_i; + + for( i = -1; i < mi; i++ ) + sum[i] = counts[i] = 0; + + // calculate sum response and weight of each category of the input var + for( i = 0; i < n; i++ ) + { + int idx = ( (labels[i] == 65535) && data->is_buf_16u ) ? -1 : labels[i]; + double s = sum[idx] + responses[i]; + int nc = counts[idx] + 1; + sum[idx] = s; + counts[idx] = nc; + } + + // calculate average response in each category + for( i = 0; i < mi; i++ ) + { + R += counts[i]; + rsum += sum[i]; + sum[i] /= MAX(counts[i],1); + sum_ptr[i] = sum + i; + } + + std::sort(sum_ptr, sum_ptr + mi, LessThanPtr()); + + // revert back to unnormalized sums + // (there should be a very little loss of accuracy) + for( i = 0; i < mi; i++ ) + sum[i] *= counts[i]; + + for( subset_i = 0; subset_i < mi-1; subset_i++ ) + { + int idx = (int)(sum_ptr[subset_i] - sum); + int ni = counts[idx]; + + if( ni ) + { + double s = sum[idx]; + lsum += s; L += ni; + rsum -= s; R -= ni; + + if( L && R ) + { + double val = (lsum*lsum*R + rsum*rsum*L)/((double)L*R); + if( best_val < val ) + { + best_val = val; + best_subset = subset_i; + } + } + } + } + + CvDTreeSplit* split = 0; + if( best_subset >= 0 ) + { + split = _split ? _split : data->new_split_cat( 0, -1.0f); + split->var_idx = vi; + split->quality = (float)best_val; + memset( split->subset, 0, (data->max_c_count + 31)/32 * sizeof(int)); + for( i = 0; i <= best_subset; i++ ) + { + int idx = (int)(sum_ptr[i] - sum); + split->subset[idx >> 5] |= 1 << (idx & 31); + } + } + return split; +} + +CvDTreeSplit* CvDTree::find_surrogate_split_ord( CvDTreeNode* node, int vi, uchar* _ext_buf ) +{ + const float epsilon = FLT_EPSILON*2; + const char* dir = (char*)data->direction->data.ptr; + int n = node->sample_count, n1 = node->get_num_valid(vi); + cv::AutoBuffer inn_buf; + if( !_ext_buf ) + inn_buf.allocate( n*(sizeof(int)*(data->have_priors ? 3 : 2) + sizeof(float)) ); + uchar* ext_buf = _ext_buf ? _ext_buf : inn_buf.data(); + float* values_buf = (float*)ext_buf; + int* sorted_indices_buf = (int*)(values_buf + n); + int* sample_indices_buf = sorted_indices_buf + n; + const float* values = 0; + const int* sorted_indices = 0; + data->get_ord_var_data( node, vi, values_buf, sorted_indices_buf, &values, &sorted_indices, sample_indices_buf ); + // LL - number of samples that both the primary and the surrogate splits send to the left + // LR - ... primary split sends to the left and the surrogate split sends to the right + // RL - ... primary split sends to the right and the surrogate split sends to the left + // RR - ... both send to the right + int i, best_i = -1, best_inversed = 0; + double best_val; + + if( !data->have_priors ) + { + int LL = 0, RL = 0, LR, RR; + int worst_val = cvFloor(node->maxlr), _best_val = worst_val; + int sum = 0, sum_abs = 0; + + for( i = 0; i < n1; i++ ) + { + int d = dir[sorted_indices[i]]; + sum += d; sum_abs += d & 1; + } + + // sum_abs = R + L; sum = R - L + RR = (sum_abs + sum) >> 1; + LR = (sum_abs - sum) >> 1; + + // initially all the samples are sent to the right by the surrogate split, + // LR of them are sent to the left by primary split, and RR - to the right. + // now iteratively compute LL, LR, RL and RR for every possible surrogate split value. + for( i = 0; i < n1 - 1; i++ ) + { + int d = dir[sorted_indices[i]]; + + if( d < 0 ) + { + LL++; LR--; + if( LL + RR > _best_val && values[i] + epsilon < values[i+1] ) + { + best_val = LL + RR; + best_i = i; best_inversed = 0; + } + } + else if( d > 0 ) + { + RL++; RR--; + if( RL + LR > _best_val && values[i] + epsilon < values[i+1] ) + { + best_val = RL + LR; + best_i = i; best_inversed = 1; + } + } + } + best_val = _best_val; + } + else + { + double LL = 0, RL = 0, LR, RR; + double worst_val = node->maxlr; + double sum = 0, sum_abs = 0; + const double* priors = data->priors_mult->data.db; + int* responses_buf = sample_indices_buf + n; + const int* responses = data->get_class_labels(node, responses_buf); + best_val = worst_val; + + for( i = 0; i < n1; i++ ) + { + int idx = sorted_indices[i]; + double w = priors[responses[idx]]; + int d = dir[idx]; + sum += d*w; sum_abs += (d & 1)*w; + } + + // sum_abs = R + L; sum = R - L + RR = (sum_abs + sum)*0.5; + LR = (sum_abs - sum)*0.5; + + // initially all the samples are sent to the right by the surrogate split, + // LR of them are sent to the left by primary split, and RR - to the right. + // now iteratively compute LL, LR, RL and RR for every possible surrogate split value. + for( i = 0; i < n1 - 1; i++ ) + { + int idx = sorted_indices[i]; + double w = priors[responses[idx]]; + int d = dir[idx]; + + if( d < 0 ) + { + LL += w; LR -= w; + if( LL + RR > best_val && values[i] + epsilon < values[i+1] ) + { + best_val = LL + RR; + best_i = i; best_inversed = 0; + } + } + else if( d > 0 ) + { + RL += w; RR -= w; + if( RL + LR > best_val && values[i] + epsilon < values[i+1] ) + { + best_val = RL + LR; + best_i = i; best_inversed = 1; + } + } + } + } + return best_i >= 0 && best_val > node->maxlr ? data->new_split_ord( vi, + (values[best_i] + values[best_i+1])*0.5f, best_i, best_inversed, (float)best_val ) : 0; +} + + +CvDTreeSplit* CvDTree::find_surrogate_split_cat( CvDTreeNode* node, int vi, uchar* _ext_buf ) +{ + const char* dir = (char*)data->direction->data.ptr; + int n = node->sample_count; + int i, mi = data->cat_count->data.i[data->get_var_type(vi)], l_win = 0; + + int base_size = (2*(mi+1)+1)*sizeof(double) + (!data->have_priors ? 2*(mi+1)*sizeof(int) : 0); + cv::AutoBuffer inn_buf(base_size); + if( !_ext_buf ) + inn_buf.allocate(base_size + n*(sizeof(int) + (data->have_priors ? sizeof(int) : 0))); + uchar* base_buf = inn_buf.data(); + uchar* ext_buf = _ext_buf ? _ext_buf : base_buf + base_size; + + int* labels_buf = (int*)ext_buf; + const int* labels = data->get_cat_var_data(node, vi, labels_buf); + // LL - number of samples that both the primary and the surrogate splits send to the left + // LR - ... primary split sends to the left and the surrogate split sends to the right + // RL - ... primary split sends to the right and the surrogate split sends to the left + // RR - ... both send to the right + CvDTreeSplit* split = data->new_split_cat( vi, 0 ); + double best_val = 0; + double* lc = (double*)cv::alignPtr(base_buf,sizeof(double)) + 1; + double* rc = lc + mi + 1; + + for( i = -1; i < mi; i++ ) + lc[i] = rc[i] = 0; + + // for each category calculate the weight of samples + // sent to the left (lc) and to the right (rc) by the primary split + if( !data->have_priors ) + { + int* _lc = (int*)rc + 1; + int* _rc = _lc + mi + 1; + + for( i = -1; i < mi; i++ ) + _lc[i] = _rc[i] = 0; + + for( i = 0; i < n; i++ ) + { + int idx = ( (labels[i] == 65535) && (data->is_buf_16u) ) ? -1 : labels[i]; + int d = dir[i]; + int sum = _lc[idx] + d; + int sum_abs = _rc[idx] + (d & 1); + _lc[idx] = sum; _rc[idx] = sum_abs; + } + + for( i = 0; i < mi; i++ ) + { + int sum = _lc[i]; + int sum_abs = _rc[i]; + lc[i] = (sum_abs - sum) >> 1; + rc[i] = (sum_abs + sum) >> 1; + } + } + else + { + const double* priors = data->priors_mult->data.db; + int* responses_buf = labels_buf + n; + const int* responses = data->get_class_labels(node, responses_buf); + + for( i = 0; i < n; i++ ) + { + int idx = ( (labels[i] == 65535) && (data->is_buf_16u) ) ? -1 : labels[i]; + double w = priors[responses[i]]; + int d = dir[i]; + double sum = lc[idx] + d*w; + double sum_abs = rc[idx] + (d & 1)*w; + lc[idx] = sum; rc[idx] = sum_abs; + } + + for( i = 0; i < mi; i++ ) + { + double sum = lc[i]; + double sum_abs = rc[i]; + lc[i] = (sum_abs - sum) * 0.5; + rc[i] = (sum_abs + sum) * 0.5; + } + } + + // 2. now form the split. + // in each category send all the samples to the same direction as majority + for( i = 0; i < mi; i++ ) + { + double lval = lc[i], rval = rc[i]; + if( lval > rval ) + { + split->subset[i >> 5] |= 1 << (i & 31); + best_val += lval; + l_win++; + } + else + best_val += rval; + } + + split->quality = (float)best_val; + if( split->quality <= node->maxlr || l_win == 0 || l_win == mi ) + cvSetRemoveByPtr( data->split_heap, split ), split = 0; + + return split; +} + + +void CvDTree::calc_node_value( CvDTreeNode* node ) +{ + int i, j, k, n = node->sample_count, cv_n = data->params.cv_folds; + int m = data->get_num_classes(); + + int base_size = data->is_classifier ? m*cv_n*sizeof(int) : 2*cv_n*sizeof(double)+cv_n*sizeof(int); + int ext_size = n*(sizeof(int) + (data->is_classifier ? sizeof(int) : sizeof(int)+sizeof(float))); + cv::AutoBuffer inn_buf(base_size + ext_size); + uchar* base_buf = inn_buf.data(); + uchar* ext_buf = base_buf + base_size; + + int* cv_labels_buf = (int*)ext_buf; + const int* cv_labels = data->get_cv_labels(node, cv_labels_buf); + + if( data->is_classifier ) + { + // in case of classification tree: + // * node value is the label of the class that has the largest weight in the node. + // * node risk is the weighted number of misclassified samples, + // * j-th cross-validation fold value and risk are calculated as above, + // but using the samples with cv_labels(*)!=j. + // * j-th cross-validation fold error is calculated as the weighted number of + // misclassified samples with cv_labels(*)==j. + + // compute the number of instances of each class + int* cls_count = data->counts->data.i; + int* responses_buf = cv_labels_buf + n; + const int* responses = data->get_class_labels(node, responses_buf); + int* cv_cls_count = (int*)base_buf; + double max_val = -1, total_weight = 0; + int max_k = -1; + double* priors = data->priors_mult->data.db; + + for( k = 0; k < m; k++ ) + cls_count[k] = 0; + + if( cv_n == 0 ) + { + for( i = 0; i < n; i++ ) + cls_count[responses[i]]++; + } + else + { + for( j = 0; j < cv_n; j++ ) + for( k = 0; k < m; k++ ) + cv_cls_count[j*m + k] = 0; + + for( i = 0; i < n; i++ ) + { + j = cv_labels[i]; k = responses[i]; + cv_cls_count[j*m + k]++; + } + + for( j = 0; j < cv_n; j++ ) + for( k = 0; k < m; k++ ) + cls_count[k] += cv_cls_count[j*m + k]; + } + + if( data->have_priors && node->parent == 0 ) + { + // compute priors_mult from priors, take the sample ratio into account. + double sum = 0; + for( k = 0; k < m; k++ ) + { + int n_k = cls_count[k]; + priors[k] = data->priors->data.db[k]*(n_k ? 1./n_k : 0.); + sum += priors[k]; + } + sum = 1./sum; + for( k = 0; k < m; k++ ) + priors[k] *= sum; + } + + for( k = 0; k < m; k++ ) + { + double val = cls_count[k]*priors[k]; + total_weight += val; + if( max_val < val ) + { + max_val = val; + max_k = k; + } + } + + node->class_idx = max_k; + node->value = data->cat_map->data.i[ + data->cat_ofs->data.i[data->cat_var_count] + max_k]; + node->node_risk = total_weight - max_val; + + for( j = 0; j < cv_n; j++ ) + { + double sum_k = 0, sum = 0, max_val_k = 0; + max_val = -1; max_k = -1; + + for( k = 0; k < m; k++ ) + { + double w = priors[k]; + double val_k = cv_cls_count[j*m + k]*w; + double val = cls_count[k]*w - val_k; + sum_k += val_k; + sum += val; + if( max_val < val ) + { + max_val = val; + max_val_k = val_k; + max_k = k; + } + } + + node->cv_Tn[j] = INT_MAX; + node->cv_node_risk[j] = sum - max_val; + node->cv_node_error[j] = sum_k - max_val_k; + } + } + else + { + // in case of regression tree: + // * node value is 1/n*sum_i(Y_i), where Y_i is i-th response, + // n is the number of samples in the node. + // * node risk is the sum of squared errors: sum_i((Y_i - )^2) + // * j-th cross-validation fold value and risk are calculated as above, + // but using the samples with cv_labels(*)!=j. + // * j-th cross-validation fold error is calculated + // using samples with cv_labels(*)==j as the test subset: + // error_j = sum_(i,cv_labels(i)==j)((Y_i - )^2), + // where node_value_j is the node value calculated + // as described in the previous bullet, and summation is done + // over the samples with cv_labels(*)==j. + + double sum = 0, sum2 = 0; + float* values_buf = (float*)(cv_labels_buf + n); + int* sample_indices_buf = (int*)(values_buf + n); + const float* values = data->get_ord_responses(node, values_buf, sample_indices_buf); + double *cv_sum = 0, *cv_sum2 = 0; + int* cv_count = 0; + + if( cv_n == 0 ) + { + for( i = 0; i < n; i++ ) + { + double t = values[i]; + sum += t; + sum2 += t*t; + } + } + else + { + cv_sum = (double*)base_buf; + cv_sum2 = cv_sum + cv_n; + cv_count = (int*)(cv_sum2 + cv_n); + + for( j = 0; j < cv_n; j++ ) + { + cv_sum[j] = cv_sum2[j] = 0.; + cv_count[j] = 0; + } + + for( i = 0; i < n; i++ ) + { + j = cv_labels[i]; + double t = values[i]; + double s = cv_sum[j] + t; + double s2 = cv_sum2[j] + t*t; + int nc = cv_count[j] + 1; + cv_sum[j] = s; + cv_sum2[j] = s2; + cv_count[j] = nc; + } + + for( j = 0; j < cv_n; j++ ) + { + sum += cv_sum[j]; + sum2 += cv_sum2[j]; + } + } + + node->node_risk = sum2 - (sum/n)*sum; + node->value = sum/n; + + for( j = 0; j < cv_n; j++ ) + { + double s = cv_sum[j], si = sum - s; + double s2 = cv_sum2[j], s2i = sum2 - s2; + int c = cv_count[j], ci = n - c; + double r = si/MAX(ci,1); + node->cv_node_risk[j] = s2i - r*r*ci; + node->cv_node_error[j] = s2 - 2*r*s + c*r*r; + node->cv_Tn[j] = INT_MAX; + } + } +} + + +void CvDTree::complete_node_dir( CvDTreeNode* node ) +{ + int vi, i, n = node->sample_count, nl, nr, d0 = 0, d1 = -1; + int nz = n - node->get_num_valid(node->split->var_idx); + char* dir = (char*)data->direction->data.ptr; + + // try to complete direction using surrogate splits + if( nz && data->params.use_surrogates ) + { + cv::AutoBuffer inn_buf(n*(2*sizeof(int)+sizeof(float))); + CvDTreeSplit* split = node->split->next; + for( ; split != 0 && nz; split = split->next ) + { + int inversed_mask = split->inversed ? -1 : 0; + vi = split->var_idx; + + if( data->get_var_type(vi) >= 0 ) // split on categorical var + { + int* labels_buf = (int*)inn_buf.data(); + const int* labels = data->get_cat_var_data(node, vi, labels_buf); + const int* subset = split->subset; + + for( i = 0; i < n; i++ ) + { + int idx = labels[i]; + if( !dir[i] && ( ((idx >= 0)&&(!data->is_buf_16u)) || ((idx != 65535)&&(data->is_buf_16u)) )) + + { + int d = CV_DTREE_CAT_DIR(idx,subset); + dir[i] = (char)((d ^ inversed_mask) - inversed_mask); + if( --nz ) + break; + } + } + } + else // split on ordered var + { + float* values_buf = (float*)inn_buf.data(); + int* sorted_indices_buf = (int*)(values_buf + n); + int* sample_indices_buf = sorted_indices_buf + n; + const float* values = 0; + const int* sorted_indices = 0; + data->get_ord_var_data( node, vi, values_buf, sorted_indices_buf, &values, &sorted_indices, sample_indices_buf ); + int split_point = split->ord.split_point; + int n1 = node->get_num_valid(vi); + + assert( 0 <= split_point && split_point < n-1 ); + + for( i = 0; i < n1; i++ ) + { + int idx = sorted_indices[i]; + if( !dir[idx] ) + { + int d = i <= split_point ? -1 : 1; + dir[idx] = (char)((d ^ inversed_mask) - inversed_mask); + if( --nz ) + break; + } + } + } + } + } + + // find the default direction for the rest + if( nz ) + { + for( i = nr = 0; i < n; i++ ) + nr += dir[i] > 0; + nl = n - nr - nz; + d0 = nl > nr ? -1 : nr > nl; + } + + // make sure that every sample is directed either to the left or to the right + for( i = 0; i < n; i++ ) + { + int d = dir[i]; + if( !d ) + { + d = d0; + if( !d ) + d = d1, d1 = -d1; + } + d = d > 0; + dir[i] = (char)d; // remap (-1,1) to (0,1) + } +} + + +void CvDTree::split_node_data( CvDTreeNode* node ) +{ + int vi, i, n = node->sample_count, nl, nr, scount = data->sample_count; + char* dir = (char*)data->direction->data.ptr; + CvDTreeNode *left = 0, *right = 0; + int* new_idx = data->split_buf->data.i; + int new_buf_idx = data->get_child_buf_idx( node ); + int work_var_count = data->get_work_var_count(); + CvMat* buf = data->buf; + size_t length_buf_row = data->get_length_subbuf(); + cv::AutoBuffer inn_buf(n*(3*sizeof(int) + sizeof(float))); + int* temp_buf = (int*)inn_buf.data(); + + complete_node_dir(node); + + for( i = nl = nr = 0; i < n; i++ ) + { + int d = dir[i]; + // initialize new indices for splitting ordered variables + new_idx[i] = (nl & (d-1)) | (nr & -d); // d ? ri : li + nr += d; + nl += d^1; + } + + bool split_input_data; + node->left = left = data->new_node( node, nl, new_buf_idx, node->offset ); + node->right = right = data->new_node( node, nr, new_buf_idx, node->offset + nl ); + + split_input_data = node->depth + 1 < data->params.max_depth && + (node->left->sample_count > data->params.min_sample_count || + node->right->sample_count > data->params.min_sample_count); + + // split ordered variables, keep both halves sorted. + for( vi = 0; vi < data->var_count; vi++ ) + { + int ci = data->get_var_type(vi); + + if( ci >= 0 || !split_input_data ) + continue; + + int n1 = node->get_num_valid(vi); + float* src_val_buf = (float*)(uchar*)(temp_buf + n); + int* src_sorted_idx_buf = (int*)(src_val_buf + n); + int* src_sample_idx_buf = src_sorted_idx_buf + n; + const float* src_val = 0; + const int* src_sorted_idx = 0; + data->get_ord_var_data(node, vi, src_val_buf, src_sorted_idx_buf, &src_val, &src_sorted_idx, src_sample_idx_buf); + + for(i = 0; i < n; i++) + temp_buf[i] = src_sorted_idx[i]; + + if (data->is_buf_16u) + { + unsigned short *ldst, *rdst, *ldst0, *rdst0; + //unsigned short tl, tr; + ldst0 = ldst = (unsigned short*)(buf->data.s + left->buf_idx*length_buf_row + + vi*scount + left->offset); + rdst0 = rdst = (unsigned short*)(ldst + nl); + + // split sorted + for( i = 0; i < n1; i++ ) + { + int idx = temp_buf[i]; + int d = dir[idx]; + idx = new_idx[idx]; + if (d) + { + *rdst = (unsigned short)idx; + rdst++; + } + else + { + *ldst = (unsigned short)idx; + ldst++; + } + } + + left->set_num_valid(vi, (int)(ldst - ldst0)); + right->set_num_valid(vi, (int)(rdst - rdst0)); + + // split missing + for( ; i < n; i++ ) + { + int idx = temp_buf[i]; + int d = dir[idx]; + idx = new_idx[idx]; + if (d) + { + *rdst = (unsigned short)idx; + rdst++; + } + else + { + *ldst = (unsigned short)idx; + ldst++; + } + } + } + else + { + int *ldst0, *ldst, *rdst0, *rdst; + ldst0 = ldst = buf->data.i + left->buf_idx*length_buf_row + + vi*scount + left->offset; + rdst0 = rdst = buf->data.i + right->buf_idx*length_buf_row + + vi*scount + right->offset; + + // split sorted + for( i = 0; i < n1; i++ ) + { + int idx = temp_buf[i]; + int d = dir[idx]; + idx = new_idx[idx]; + if (d) + { + *rdst = idx; + rdst++; + } + else + { + *ldst = idx; + ldst++; + } + } + + left->set_num_valid(vi, (int)(ldst - ldst0)); + right->set_num_valid(vi, (int)(rdst - rdst0)); + + // split missing + for( ; i < n; i++ ) + { + int idx = temp_buf[i]; + int d = dir[idx]; + idx = new_idx[idx]; + if (d) + { + *rdst = idx; + rdst++; + } + else + { + *ldst = idx; + ldst++; + } + } + } + } + + // split categorical vars, responses and cv_labels using new_idx relocation table + for( vi = 0; vi < work_var_count; vi++ ) + { + int ci = data->get_var_type(vi); + int n1 = node->get_num_valid(vi), nr1 = 0; + + if( ci < 0 || (vi < data->var_count && !split_input_data) ) + continue; + + int *src_lbls_buf = temp_buf + n; + const int* src_lbls = data->get_cat_var_data(node, vi, src_lbls_buf); + + for(i = 0; i < n; i++) + temp_buf[i] = src_lbls[i]; + + if (data->is_buf_16u) + { + unsigned short *ldst = (unsigned short *)(buf->data.s + left->buf_idx*length_buf_row + + vi*scount + left->offset); + unsigned short *rdst = (unsigned short *)(buf->data.s + right->buf_idx*length_buf_row + + vi*scount + right->offset); + + for( i = 0; i < n; i++ ) + { + int d = dir[i]; + int idx = temp_buf[i]; + if (d) + { + *rdst = (unsigned short)idx; + rdst++; + nr1 += (idx != 65535 )&d; + } + else + { + *ldst = (unsigned short)idx; + ldst++; + } + } + + if( vi < data->var_count ) + { + left->set_num_valid(vi, n1 - nr1); + right->set_num_valid(vi, nr1); + } + } + else + { + int *ldst = buf->data.i + left->buf_idx*length_buf_row + + vi*scount + left->offset; + int *rdst = buf->data.i + right->buf_idx*length_buf_row + + vi*scount + right->offset; + + for( i = 0; i < n; i++ ) + { + int d = dir[i]; + int idx = temp_buf[i]; + if (d) + { + *rdst = idx; + rdst++; + nr1 += (idx >= 0)&d; + } + else + { + *ldst = idx; + ldst++; + } + + } + + if( vi < data->var_count ) + { + left->set_num_valid(vi, n1 - nr1); + right->set_num_valid(vi, nr1); + } + } + } + + + // split sample indices + int *sample_idx_src_buf = temp_buf + n; + const int* sample_idx_src = data->get_sample_indices(node, sample_idx_src_buf); + + for(i = 0; i < n; i++) + temp_buf[i] = sample_idx_src[i]; + + int pos = data->get_work_var_count(); + if (data->is_buf_16u) + { + unsigned short* ldst = (unsigned short*)(buf->data.s + left->buf_idx*length_buf_row + + pos*scount + left->offset); + unsigned short* rdst = (unsigned short*)(buf->data.s + right->buf_idx*length_buf_row + + pos*scount + right->offset); + for (i = 0; i < n; i++) + { + int d = dir[i]; + unsigned short idx = (unsigned short)temp_buf[i]; + if (d) + { + *rdst = idx; + rdst++; + } + else + { + *ldst = idx; + ldst++; + } + } + } + else + { + int* ldst = buf->data.i + left->buf_idx*length_buf_row + + pos*scount + left->offset; + int* rdst = buf->data.i + right->buf_idx*length_buf_row + + pos*scount + right->offset; + for (i = 0; i < n; i++) + { + int d = dir[i]; + int idx = temp_buf[i]; + if (d) + { + *rdst = idx; + rdst++; + } + else + { + *ldst = idx; + ldst++; + } + } + } + + // deallocate the parent node data that is not needed anymore + data->free_node_data(node); +} + +float CvDTree::calc_error( CvMLData* _data, int type, std::vector *resp ) +{ + float err = 0; + const CvMat* values = _data->get_values(); + const CvMat* response = _data->get_responses(); + const CvMat* missing = _data->get_missing(); + const CvMat* sample_idx = (type == CV_TEST_ERROR) ? _data->get_test_sample_idx() : _data->get_train_sample_idx(); + const CvMat* var_types = _data->get_var_types(); + int* sidx = sample_idx ? sample_idx->data.i : 0; + int r_step = CV_IS_MAT_CONT(response->type) ? + 1 : response->step / CV_ELEM_SIZE(response->type); + bool is_classifier = var_types->data.ptr[var_types->cols-1] == CV_VAR_CATEGORICAL; + int sample_count = sample_idx ? sample_idx->cols : 0; + sample_count = (type == CV_TRAIN_ERROR && sample_count == 0) ? values->rows : sample_count; + float* pred_resp = 0; + if( resp && (sample_count > 0) ) + { + resp->resize( sample_count ); + pred_resp = &((*resp)[0]); + } + + if ( is_classifier ) + { + for( int i = 0; i < sample_count; i++ ) + { + CvMat sample, miss; + int si = sidx ? sidx[i] : i; + cvGetRow( values, &sample, si ); + if( missing ) + cvGetRow( missing, &miss, si ); + float r = (float)predict( &sample, missing ? &miss : 0 )->value; + if( pred_resp ) + pred_resp[i] = r; + int d = fabs((double)r - response->data.fl[(size_t)si*r_step]) <= FLT_EPSILON ? 0 : 1; + err += d; + } + err = sample_count ? err / (float)sample_count * 100 : -FLT_MAX; + } + else + { + for( int i = 0; i < sample_count; i++ ) + { + CvMat sample, miss; + int si = sidx ? sidx[i] : i; + cvGetRow( values, &sample, si ); + if( missing ) + cvGetRow( missing, &miss, si ); + float r = (float)predict( &sample, missing ? &miss : 0 )->value; + if( pred_resp ) + pred_resp[i] = r; + float d = r - response->data.fl[(size_t)si*r_step]; + err += d*d; + } + err = sample_count ? err / (float)sample_count : -FLT_MAX; + } + return err; +} + +void CvDTree::prune_cv() +{ + CvMat* ab = 0; + CvMat* temp = 0; + CvMat* err_jk = 0; + + // 1. build tree sequence for each cv fold, calculate error_{Tj,beta_k}. + // 2. choose the best tree index (if need, apply 1SE rule). + // 3. store the best index and cut the branches. + + CV_FUNCNAME( "CvDTree::prune_cv" ); + + __BEGIN__; + + int ti, j, tree_count = 0, cv_n = data->params.cv_folds, n = root->sample_count; + // currently, 1SE for regression is not implemented + bool use_1se = data->params.use_1se_rule != 0 && data->is_classifier; + double* err; + double min_err = 0, min_err_se = 0; + int min_idx = -1; + + CV_CALL( ab = cvCreateMat( 1, 256, CV_64F )); + + // build the main tree sequence, calculate alpha's + for(;;tree_count++) + { + double min_alpha = update_tree_rnc(tree_count, -1); + if( cut_tree(tree_count, -1, min_alpha) ) + break; + + if( ab->cols <= tree_count ) + { + CV_CALL( temp = cvCreateMat( 1, ab->cols*3/2, CV_64F )); + for( ti = 0; ti < ab->cols; ti++ ) + temp->data.db[ti] = ab->data.db[ti]; + cvReleaseMat( &ab ); + ab = temp; + temp = 0; + } + + ab->data.db[tree_count] = min_alpha; + } + + ab->data.db[0] = 0.; + + if( tree_count > 0 ) + { + for( ti = 1; ti < tree_count-1; ti++ ) + ab->data.db[ti] = sqrt(ab->data.db[ti]*ab->data.db[ti+1]); + ab->data.db[tree_count-1] = DBL_MAX*0.5; + + CV_CALL( err_jk = cvCreateMat( cv_n, tree_count, CV_64F )); + err = err_jk->data.db; + + for( j = 0; j < cv_n; j++ ) + { + int tj = 0, tk = 0; + for( ; tk < tree_count; tj++ ) + { + double min_alpha = update_tree_rnc(tj, j); + if( cut_tree(tj, j, min_alpha) ) + min_alpha = DBL_MAX; + + for( ; tk < tree_count; tk++ ) + { + if( ab->data.db[tk] > min_alpha ) + break; + err[j*tree_count + tk] = root->tree_error; + } + } + } + + for( ti = 0; ti < tree_count; ti++ ) + { + double sum_err = 0; + for( j = 0; j < cv_n; j++ ) + sum_err += err[j*tree_count + ti]; + if( ti == 0 || sum_err < min_err ) + { + min_err = sum_err; + min_idx = ti; + if( use_1se ) + min_err_se = sqrt( sum_err*(n - sum_err) ); + } + else if( sum_err < min_err + min_err_se ) + min_idx = ti; + } + } + + pruned_tree_idx = min_idx; + free_prune_data(data->params.truncate_pruned_tree != 0); + + __END__; + + cvReleaseMat( &err_jk ); + cvReleaseMat( &ab ); + cvReleaseMat( &temp ); +} + + +double CvDTree::update_tree_rnc( int T, int fold ) +{ + CvDTreeNode* node = root; + double min_alpha = DBL_MAX; + + for(;;) + { + CvDTreeNode* parent; + for(;;) + { + int t = fold >= 0 ? node->cv_Tn[fold] : node->Tn; + if( t <= T || !node->left ) + { + node->complexity = 1; + node->tree_risk = node->node_risk; + node->tree_error = 0.; + if( fold >= 0 ) + { + node->tree_risk = node->cv_node_risk[fold]; + node->tree_error = node->cv_node_error[fold]; + } + break; + } + node = node->left; + } + + for( parent = node->parent; parent && parent->right == node; + node = parent, parent = parent->parent ) + { + parent->complexity += node->complexity; + parent->tree_risk += node->tree_risk; + parent->tree_error += node->tree_error; + + parent->alpha = ((fold >= 0 ? parent->cv_node_risk[fold] : parent->node_risk) + - parent->tree_risk)/(parent->complexity - 1); + min_alpha = MIN( min_alpha, parent->alpha ); + } + + if( !parent ) + break; + + parent->complexity = node->complexity; + parent->tree_risk = node->tree_risk; + parent->tree_error = node->tree_error; + node = parent->right; + } + + return min_alpha; +} + + +int CvDTree::cut_tree( int T, int fold, double min_alpha ) +{ + CvDTreeNode* node = root; + if( !node->left ) + return 1; + + for(;;) + { + CvDTreeNode* parent; + for(;;) + { + int t = fold >= 0 ? node->cv_Tn[fold] : node->Tn; + if( t <= T || !node->left ) + break; + if( node->alpha <= min_alpha + FLT_EPSILON ) + { + if( fold >= 0 ) + node->cv_Tn[fold] = T; + else + node->Tn = T; + if( node == root ) + return 1; + break; + } + node = node->left; + } + + for( parent = node->parent; parent && parent->right == node; + node = parent, parent = parent->parent ) + ; + + if( !parent ) + break; + + node = parent->right; + } + + return 0; +} + + +void CvDTree::free_prune_data(bool _cut_tree) +{ + CvDTreeNode* node = root; + + for(;;) + { + CvDTreeNode* parent; + for(;;) + { + // do not call cvSetRemoveByPtr( cv_heap, node->cv_Tn ) + // as we will clear the whole cross-validation heap at the end + node->cv_Tn = 0; + node->cv_node_error = node->cv_node_risk = 0; + if( !node->left ) + break; + node = node->left; + } + + for( parent = node->parent; parent && parent->right == node; + node = parent, parent = parent->parent ) + { + if( _cut_tree && parent->Tn <= pruned_tree_idx ) + { + data->free_node( parent->left ); + data->free_node( parent->right ); + parent->left = parent->right = 0; + } + } + + if( !parent ) + break; + + node = parent->right; + } + + if( data->cv_heap ) + cvClearSet( data->cv_heap ); +} + + +void CvDTree::free_tree() +{ + if( root && data && data->shared ) + { + pruned_tree_idx = INT_MIN; + free_prune_data(true); + data->free_node(root); + root = 0; + } +} + +CvDTreeNode* CvDTree::predict( const CvMat* _sample, + const CvMat* _missing, bool preprocessed_input ) const +{ + cv::AutoBuffer catbuf; + + int i, mstep = 0; + const uchar* m = 0; + CvDTreeNode* node = root; + + if( !node ) + CV_Error( CV_StsError, "The tree has not been trained yet" ); + + if( !CV_IS_MAT(_sample) || CV_MAT_TYPE(_sample->type) != CV_32FC1 || + (_sample->cols != 1 && _sample->rows != 1) || + (_sample->cols + _sample->rows - 1 != data->var_all && !preprocessed_input) || + (_sample->cols + _sample->rows - 1 != data->var_count && preprocessed_input) ) + CV_Error( CV_StsBadArg, + "the input sample must be 1d floating-point vector with the same " + "number of elements as the total number of variables used for training" ); + + const float* sample = _sample->data.fl; + int step = CV_IS_MAT_CONT(_sample->type) ? 1 : _sample->step/sizeof(sample[0]); + + if( data->cat_count && !preprocessed_input ) // cache for categorical variables + { + int n = data->cat_count->cols; + catbuf.allocate(n); + for( i = 0; i < n; i++ ) + catbuf[i] = -1; + } + + if( _missing ) + { + if( !CV_IS_MAT(_missing) || !CV_IS_MASK_ARR(_missing) || + !CV_ARE_SIZES_EQ(_missing, _sample) ) + CV_Error( CV_StsBadArg, + "the missing data mask must be 8-bit vector of the same size as input sample" ); + m = _missing->data.ptr; + mstep = CV_IS_MAT_CONT(_missing->type) ? 1 : _missing->step/sizeof(m[0]); + } + + const int* vtype = data->var_type->data.i; + const int* vidx = data->var_idx && !preprocessed_input ? data->var_idx->data.i : 0; + const int* cmap = data->cat_map ? data->cat_map->data.i : 0; + const int* cofs = data->cat_ofs ? data->cat_ofs->data.i : 0; + + while( node->Tn > pruned_tree_idx && node->left ) + { + CvDTreeSplit* split = node->split; + int dir = 0; + for( ; !dir && split != 0; split = split->next ) + { + int vi = split->var_idx; + int ci = vtype[vi]; + i = vidx ? vidx[vi] : vi; + float val = sample[(size_t)i*step]; + if( m && m[(size_t)i*mstep] ) + continue; + if( ci < 0 ) // ordered + dir = val <= split->ord.c ? -1 : 1; + else // categorical + { + int c; + if( preprocessed_input ) + c = cvRound(val); + else + { + c = catbuf[ci]; + if( c < 0 ) + { + int a = c = cofs[ci]; + int b = (ci+1 >= data->cat_ofs->cols) ? data->cat_map->cols : cofs[ci+1]; + + int ival = cvRound(val); + if( ival != val ) + CV_Error( CV_StsBadArg, + "one of input categorical variable is not an integer" ); + + int sh = 0; + while( a < b ) + { + sh++; + c = (a + b) >> 1; + if( ival < cmap[c] ) + b = c; + else if( ival > cmap[c] ) + a = c+1; + else + break; + } + + if( c < 0 || ival != cmap[c] ) + continue; + + catbuf[ci] = c -= cofs[ci]; + } + } + c = ( (c == 65535) && data->is_buf_16u ) ? -1 : c; + dir = CV_DTREE_CAT_DIR(c, split->subset); + } + + if( split->inversed ) + dir = -dir; + } + + if( !dir ) + { + double diff = node->right->sample_count - node->left->sample_count; + dir = diff < 0 ? -1 : 1; + } + node = dir < 0 ? node->left : node->right; + } + + return node; +} + + +CvDTreeNode* CvDTree::predict( const Mat& _sample, const Mat& _missing, bool preprocessed_input ) const +{ + CvMat sample = cvMat(_sample), mmask = cvMat(_missing); + return predict(&sample, mmask.data.ptr ? &mmask : 0, preprocessed_input); +} + + +const CvMat* CvDTree::get_var_importance() +{ + if( !var_importance ) + { + CvDTreeNode* node = root; + double* importance; + if( !node ) + return 0; + var_importance = cvCreateMat( 1, data->var_count, CV_64F ); + cvZero( var_importance ); + importance = var_importance->data.db; + + for(;;) + { + CvDTreeNode* parent; + for( ;; node = node->left ) + { + CvDTreeSplit* split = node->split; + + if( !node->left || node->Tn <= pruned_tree_idx ) + break; + + for( ; split != 0; split = split->next ) + importance[split->var_idx] += split->quality; + } + + for( parent = node->parent; parent && parent->right == node; + node = parent, parent = parent->parent ) + ; + + if( !parent ) + break; + + node = parent->right; + } + + cvNormalize( var_importance, var_importance, 1., 0, CV_L1 ); + } + + return var_importance; +} + + +void CvDTree::write_split( CvFileStorage* fs, CvDTreeSplit* split ) const +{ + int ci; + + cvStartWriteStruct( fs, 0, CV_NODE_MAP + CV_NODE_FLOW ); + cvWriteInt( fs, "var", split->var_idx ); + cvWriteReal( fs, "quality", split->quality ); + + ci = data->get_var_type(split->var_idx); + if( ci >= 0 ) // split on a categorical var + { + int i, n = data->cat_count->data.i[ci], to_right = 0, default_dir; + for( i = 0; i < n; i++ ) + to_right += CV_DTREE_CAT_DIR(i,split->subset) > 0; + + // ad-hoc rule when to use inverse categorical split notation + // to achieve more compact and clear representation + default_dir = to_right <= 1 || to_right <= MIN(3, n/2) || to_right <= n/3 ? -1 : 1; + + cvStartWriteStruct( fs, default_dir*(split->inversed ? -1 : 1) > 0 ? + "in" : "not_in", CV_NODE_SEQ+CV_NODE_FLOW ); + + for( i = 0; i < n; i++ ) + { + int dir = CV_DTREE_CAT_DIR(i,split->subset); + if( dir*default_dir < 0 ) + cvWriteInt( fs, 0, i ); + } + cvEndWriteStruct( fs ); + } + else + cvWriteReal( fs, !split->inversed ? "le" : "gt", split->ord.c ); + + cvEndWriteStruct( fs ); +} + + +void CvDTree::write_node( CvFileStorage* fs, CvDTreeNode* node ) const +{ + CvDTreeSplit* split; + + cvStartWriteStruct( fs, 0, CV_NODE_MAP ); + + cvWriteInt( fs, "depth", node->depth ); + cvWriteInt( fs, "sample_count", node->sample_count ); + cvWriteReal( fs, "value", node->value ); + + if( data->is_classifier ) + cvWriteInt( fs, "norm_class_idx", node->class_idx ); + + cvWriteInt( fs, "Tn", node->Tn ); + cvWriteInt( fs, "complexity", node->complexity ); + cvWriteReal( fs, "alpha", node->alpha ); + cvWriteReal( fs, "node_risk", node->node_risk ); + cvWriteReal( fs, "tree_risk", node->tree_risk ); + cvWriteReal( fs, "tree_error", node->tree_error ); + + if( node->left ) + { + cvStartWriteStruct( fs, "splits", CV_NODE_SEQ ); + + for( split = node->split; split != 0; split = split->next ) + write_split( fs, split ); + + cvEndWriteStruct( fs ); + } + + cvEndWriteStruct( fs ); +} + + +void CvDTree::write_tree_nodes( CvFileStorage* fs ) const +{ + //CV_FUNCNAME( "CvDTree::write_tree_nodes" ); + + __BEGIN__; + + CvDTreeNode* node = root; + + // traverse the tree and save all the nodes in depth-first order + for(;;) + { + CvDTreeNode* parent; + for(;;) + { + write_node( fs, node ); + if( !node->left ) + break; + node = node->left; + } + + for( parent = node->parent; parent && parent->right == node; + node = parent, parent = parent->parent ) + ; + + if( !parent ) + break; + + node = parent->right; + } + + __END__; +} + + +void CvDTree::write( CvFileStorage* fs, const char* name ) const +{ + //CV_FUNCNAME( "CvDTree::write" ); + + __BEGIN__; + + cvStartWriteStruct( fs, name, CV_NODE_MAP, CV_TYPE_NAME_ML_TREE ); + + //get_var_importance(); + data->write_params( fs ); + //if( var_importance ) + //cvWrite( fs, "var_importance", var_importance ); + write( fs ); + + cvEndWriteStruct( fs ); + + __END__; +} + + +void CvDTree::write( CvFileStorage* fs ) const +{ + //CV_FUNCNAME( "CvDTree::write" ); + + __BEGIN__; + + cvWriteInt( fs, "best_tree_idx", pruned_tree_idx ); + + cvStartWriteStruct( fs, "nodes", CV_NODE_SEQ ); + write_tree_nodes( fs ); + cvEndWriteStruct( fs ); + + __END__; +} + + +CvDTreeSplit* CvDTree::read_split( CvFileStorage* fs, CvFileNode* fnode ) +{ + CvDTreeSplit* split = 0; + + CV_FUNCNAME( "CvDTree::read_split" ); + + __BEGIN__; + + int vi, ci; + + if( !fnode || CV_NODE_TYPE(fnode->tag) != CV_NODE_MAP ) + CV_ERROR( CV_StsParseError, "some of the splits are not stored properly" ); + + vi = cvReadIntByName( fs, fnode, "var", -1 ); + if( (unsigned)vi >= (unsigned)data->var_count ) + CV_ERROR( CV_StsOutOfRange, "Split variable index is out of range" ); + + ci = data->get_var_type(vi); + if( ci >= 0 ) // split on categorical var + { + int i, n = data->cat_count->data.i[ci], inversed = 0, val; + CvSeqReader reader; + CvFileNode* inseq; + split = data->new_split_cat( vi, 0 ); + inseq = cvGetFileNodeByName( fs, fnode, "in" ); + if( !inseq ) + { + inseq = cvGetFileNodeByName( fs, fnode, "not_in" ); + inversed = 1; + } + if( !inseq || + (CV_NODE_TYPE(inseq->tag) != CV_NODE_SEQ && CV_NODE_TYPE(inseq->tag) != CV_NODE_INT)) + CV_ERROR( CV_StsParseError, + "Either 'in' or 'not_in' tags should be inside a categorical split data" ); + + if( CV_NODE_TYPE(inseq->tag) == CV_NODE_INT ) + { + val = inseq->data.i; + if( (unsigned)val >= (unsigned)n ) + CV_ERROR( CV_StsOutOfRange, "some of in/not_in elements are out of range" ); + + split->subset[val >> 5] |= 1 << (val & 31); + } + else + { + cvStartReadSeq( inseq->data.seq, &reader ); + + for( i = 0; i < reader.seq->total; i++ ) + { + CvFileNode* inode = (CvFileNode*)reader.ptr; + val = inode->data.i; + if( CV_NODE_TYPE(inode->tag) != CV_NODE_INT || (unsigned)val >= (unsigned)n ) + CV_ERROR( CV_StsOutOfRange, "some of in/not_in elements are out of range" ); + + split->subset[val >> 5] |= 1 << (val & 31); + CV_NEXT_SEQ_ELEM( reader.seq->elem_size, reader ); + } + } + + // for categorical splits we do not use inversed splits, + // instead we inverse the variable set in the split + if( inversed ) + for( i = 0; i < (n + 31) >> 5; i++ ) + split->subset[i] ^= -1; + } + else + { + CvFileNode* cmp_node; + split = data->new_split_ord( vi, 0, 0, 0, 0 ); + + cmp_node = cvGetFileNodeByName( fs, fnode, "le" ); + if( !cmp_node ) + { + cmp_node = cvGetFileNodeByName( fs, fnode, "gt" ); + split->inversed = 1; + } + + split->ord.c = (float)cvReadReal( cmp_node ); + } + + split->quality = (float)cvReadRealByName( fs, fnode, "quality" ); + + __END__; + + return split; +} + + +CvDTreeNode* CvDTree::read_node( CvFileStorage* fs, CvFileNode* fnode, CvDTreeNode* parent ) +{ + CvDTreeNode* node = 0; + + CV_FUNCNAME( "CvDTree::read_node" ); + + __BEGIN__; + + CvFileNode* splits; + int i, depth; + + if( !fnode || CV_NODE_TYPE(fnode->tag) != CV_NODE_MAP ) + CV_ERROR( CV_StsParseError, "some of the tree elements are not stored properly" ); + + CV_CALL( node = data->new_node( parent, 0, 0, 0 )); + depth = cvReadIntByName( fs, fnode, "depth", -1 ); + if( depth != node->depth ) + CV_ERROR( CV_StsParseError, "incorrect node depth" ); + + node->sample_count = cvReadIntByName( fs, fnode, "sample_count" ); + node->value = cvReadRealByName( fs, fnode, "value" ); + if( data->is_classifier ) + node->class_idx = cvReadIntByName( fs, fnode, "norm_class_idx" ); + + node->Tn = cvReadIntByName( fs, fnode, "Tn" ); + node->complexity = cvReadIntByName( fs, fnode, "complexity" ); + node->alpha = cvReadRealByName( fs, fnode, "alpha" ); + node->node_risk = cvReadRealByName( fs, fnode, "node_risk" ); + node->tree_risk = cvReadRealByName( fs, fnode, "tree_risk" ); + node->tree_error = cvReadRealByName( fs, fnode, "tree_error" ); + + splits = cvGetFileNodeByName( fs, fnode, "splits" ); + if( splits ) + { + CvSeqReader reader; + CvDTreeSplit* last_split = 0; + + if( CV_NODE_TYPE(splits->tag) != CV_NODE_SEQ ) + CV_ERROR( CV_StsParseError, "splits tag must stored as a sequence" ); + + cvStartReadSeq( splits->data.seq, &reader ); + for( i = 0; i < reader.seq->total; i++ ) + { + CvDTreeSplit* split; + CV_CALL( split = read_split( fs, (CvFileNode*)reader.ptr )); + if( !last_split ) + node->split = last_split = split; + else + last_split = last_split->next = split; + + CV_NEXT_SEQ_ELEM( reader.seq->elem_size, reader ); + } + } + + __END__; + + return node; +} + + +void CvDTree::read_tree_nodes( CvFileStorage* fs, CvFileNode* fnode ) +{ + CV_FUNCNAME( "CvDTree::read_tree_nodes" ); + + __BEGIN__; + + CvSeqReader reader; + CvDTreeNode _root; + CvDTreeNode* parent = &_root; + int i; + parent->left = parent->right = parent->parent = 0; + + cvStartReadSeq( fnode->data.seq, &reader ); + + for( i = 0; i < reader.seq->total; i++ ) + { + CvDTreeNode* node; + + CV_CALL( node = read_node( fs, (CvFileNode*)reader.ptr, parent != &_root ? parent : 0 )); + if( !parent->left ) + parent->left = node; + else + parent->right = node; + if( node->split ) + parent = node; + else + { + while( parent && parent->right ) + parent = parent->parent; + } + + CV_NEXT_SEQ_ELEM( reader.seq->elem_size, reader ); + } + + root = _root.left; + + __END__; +} + + +void CvDTree::read( CvFileStorage* fs, CvFileNode* fnode ) +{ + CvDTreeTrainData* _data = new CvDTreeTrainData(); + _data->read_params( fs, fnode ); + + read( fs, fnode, _data ); + get_var_importance(); +} + + +// a special entry point for reading weak decision trees from the tree ensembles +void CvDTree::read( CvFileStorage* fs, CvFileNode* node, CvDTreeTrainData* _data ) +{ + CV_FUNCNAME( "CvDTree::read" ); + + __BEGIN__; + + CvFileNode* tree_nodes; + + clear(); + data = _data; + + tree_nodes = cvGetFileNodeByName( fs, node, "nodes" ); + if( !tree_nodes || CV_NODE_TYPE(tree_nodes->tag) != CV_NODE_SEQ ) + CV_ERROR( CV_StsParseError, "nodes tag is missing" ); + + pruned_tree_idx = cvReadIntByName( fs, node, "best_tree_idx", -1 ); + read_tree_nodes( fs, tree_nodes ); + + __END__; +} + +Mat CvDTree::getVarImportance() +{ + return cvarrToMat(get_var_importance()); +} + +/* End of file. */ diff --git a/opencv-apps/traincascade/traincascade.cpp b/opencv-apps/traincascade/traincascade.cpp new file mode 100644 index 0000000..0efce23 --- /dev/null +++ b/opencv-apps/traincascade/traincascade.cpp @@ -0,0 +1,129 @@ +#include "opencv2/core.hpp" +#include "cascadeclassifier.h" + +using namespace std; +using namespace cv; + +/* +traincascade.cpp is the source file of the program used for cascade training. +User has to provide training input in form of positive and negative training images, +and other data related to training in form of command line argument. +*/ +int main( int argc, char* argv[] ) +{ + CvCascadeClassifier classifier; + string cascadeDirName, vecName, bgName; + int numPos = 2000; + int numNeg = 1000; + int numStages = 20; + int numThreads = getNumThreads(); + int precalcValBufSize = 1024, + precalcIdxBufSize = 1024; + bool baseFormatSave = false; + double acceptanceRatioBreakValue = -1.0; + + CvCascadeParams cascadeParams; + CvCascadeBoostParams stageParams; + Ptr featureParams[] = { makePtr(), + makePtr(), + makePtr() + }; + int fc = sizeof(featureParams)/sizeof(featureParams[0]); + if( argc == 1 ) + { + cout << "Usage: " << argv[0] << endl; + cout << " -data " << endl; + cout << " -vec " << endl; + cout << " -bg " << endl; + cout << " [-numPos ]" << endl; + cout << " [-numNeg ]" << endl; + cout << " [-numStages ]" << endl; + cout << " [-precalcValBufSize ]" << endl; + cout << " [-precalcIdxBufSize ]" << endl; + cout << " [-baseFormatSave]" << endl; + cout << " [-numThreads ]" << endl; + cout << " [-acceptanceRatioBreakValue = " << acceptanceRatioBreakValue << ">]" << endl; + cascadeParams.printDefaults(); + stageParams.printDefaults(); + for( int fi = 0; fi < fc; fi++ ) + featureParams[fi]->printDefaults(); + return 0; + } + + for( int i = 1; i < argc; i++ ) + { + bool set = false; + if( !strcmp( argv[i], "-data" ) ) + { + cascadeDirName = argv[++i]; + } + else if( !strcmp( argv[i], "-vec" ) ) + { + vecName = argv[++i]; + } + else if( !strcmp( argv[i], "-bg" ) ) + { + bgName = argv[++i]; + } + else if( !strcmp( argv[i], "-numPos" ) ) + { + numPos = atoi( argv[++i] ); + } + else if( !strcmp( argv[i], "-numNeg" ) ) + { + numNeg = atoi( argv[++i] ); + } + else if( !strcmp( argv[i], "-numStages" ) ) + { + numStages = atoi( argv[++i] ); + } + else if( !strcmp( argv[i], "-precalcValBufSize" ) ) + { + precalcValBufSize = atoi( argv[++i] ); + } + else if( !strcmp( argv[i], "-precalcIdxBufSize" ) ) + { + precalcIdxBufSize = atoi( argv[++i] ); + } + else if( !strcmp( argv[i], "-baseFormatSave" ) ) + { + baseFormatSave = true; + } + else if( !strcmp( argv[i], "-numThreads" ) ) + { + numThreads = atoi(argv[++i]); + } + else if( !strcmp( argv[i], "-acceptanceRatioBreakValue" ) ) + { + acceptanceRatioBreakValue = atof(argv[++i]); + } + else if ( cascadeParams.scanAttr( argv[i], argv[i+1] ) ) { i++; } + else if ( stageParams.scanAttr( argv[i], argv[i+1] ) ) { i++; } + else if ( !set ) + { + for( int fi = 0; fi < fc; fi++ ) + { + set = featureParams[fi]->scanAttr(argv[i], argv[i+1]); + if ( !set ) + { + i++; + break; + } + } + } + } + + setNumThreads( numThreads ); + classifier.train( cascadeDirName, + vecName, + bgName, + numPos, numNeg, + precalcValBufSize, precalcIdxBufSize, + numStages, + cascadeParams, + *featureParams[cascadeParams.featureType], + stageParams, + baseFormatSave, + acceptanceRatioBreakValue ); + return 0; +} diff --git a/opencv-apps/traincascade/traincascade_features.h b/opencv-apps/traincascade/traincascade_features.h new file mode 100644 index 0000000..c8f024b --- /dev/null +++ b/opencv-apps/traincascade/traincascade_features.h @@ -0,0 +1,101 @@ +#ifndef _OPENCV_FEATURES_H_ +#define _OPENCV_FEATURES_H_ + +#include "imagestorage.h" +#include + +#define FEATURES "features" + +#define CV_SUM_OFFSETS( p0, p1, p2, p3, rect, step ) \ + /* (x, y) */ \ + (p0) = (rect).x + (step) * (rect).y; \ + /* (x + w, y) */ \ + (p1) = (rect).x + (rect).width + (step) * (rect).y; \ + /* (x + w, y) */ \ + (p2) = (rect).x + (step) * ((rect).y + (rect).height); \ + /* (x + w, y + h) */ \ + (p3) = (rect).x + (rect).width + (step) * ((rect).y + (rect).height); + +#define CV_TILTED_OFFSETS( p0, p1, p2, p3, rect, step ) \ + /* (x, y) */ \ + (p0) = (rect).x + (step) * (rect).y; \ + /* (x - h, y + h) */ \ + (p1) = (rect).x - (rect).height + (step) * ((rect).y + (rect).height);\ + /* (x + w, y + w) */ \ + (p2) = (rect).x + (rect).width + (step) * ((rect).y + (rect).width); \ + /* (x + w - h, y + w + h) */ \ + (p3) = (rect).x + (rect).width - (rect).height \ + + (step) * ((rect).y + (rect).width + (rect).height); + +float calcNormFactor( const cv::Mat& sum, const cv::Mat& sqSum ); + +template +void _writeFeatures( const std::vector features, cv::FileStorage &fs, const cv::Mat& featureMap ) +{ + fs << FEATURES << "["; + const cv::Mat_& featureMap_ = (const cv::Mat_&)featureMap; + for ( int fi = 0; fi < featureMap.cols; fi++ ) + if ( featureMap_(0, fi) >= 0 ) + { + fs << "{"; + features[fi].write( fs ); + fs << "}"; + } + fs << "]"; +} + +class CvParams +{ +public: + CvParams(); + virtual ~CvParams() {} + // from|to file + virtual void write( cv::FileStorage &fs ) const = 0; + virtual bool read( const cv::FileNode &node ) = 0; + // from|to screen + virtual void printDefaults() const; + virtual void printAttrs() const; + virtual bool scanAttr( const std::string prmName, const std::string val ); + std::string name; +}; + +class CvFeatureParams : public CvParams +{ +public: + enum { HAAR = 0, LBP = 1, HOG = 2 }; + CvFeatureParams(); + virtual void init( const CvFeatureParams& fp ); + virtual void write( cv::FileStorage &fs ) const; + virtual bool read( const cv::FileNode &node ); + static cv::Ptr create( int featureType ); + int maxCatCount; // 0 in case of numerical features + int featSize; // 1 in case of simple features (HAAR, LBP) and N_BINS(9)*N_CELLS(4) in case of Dalal's HOG features +}; + +class CvFeatureEvaluator +{ +public: + virtual ~CvFeatureEvaluator() {} + virtual void init(const CvFeatureParams *_featureParams, + int _maxSampleCount, cv::Size _winSize ); + virtual void setImage(const cv::Mat& img, uchar clsLabel, int idx); + virtual void writeFeatures( cv::FileStorage &fs, const cv::Mat& featureMap ) const = 0; + virtual float operator()(int featureIdx, int sampleIdx) const = 0; + static cv::Ptr create(int type); + + int getNumFeatures() const { return numFeatures; } + int getMaxCatCount() const { return featureParams->maxCatCount; } + int getFeatureSize() const { return featureParams->featSize; } + const cv::Mat& getCls() const { return cls; } + float getCls(int si) const { return cls.at(si, 0); } +protected: + virtual void generateFeatures() = 0; + + int npos, nneg; + int numFeatures; + cv::Size winSize; + CvFeatureParams *featureParams; + cv::Mat cls; +}; + +#endif diff --git a/opencv-apps/version/CMakeLists.txt b/opencv-apps/version/CMakeLists.txt new file mode 100644 index 0000000..89e739b --- /dev/null +++ b/opencv-apps/version/CMakeLists.txt @@ -0,0 +1,5 @@ +ocv_add_application(opencv_version MODULES opencv_core SRCS opencv_version.cpp) +if(WIN32) + ocv_add_application(opencv_version_win32 MODULES opencv_core SRCS opencv_version.cpp) + target_compile_definitions(opencv_version_win32 PRIVATE "OPENCV_WIN32_API=1") +endif() diff --git a/opencv-apps/version/opencv_version.cpp b/opencv-apps/version/opencv_version.cpp new file mode 100644 index 0000000..8034f69 --- /dev/null +++ b/opencv-apps/version/opencv_version.cpp @@ -0,0 +1,84 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include + +#include +#include + +#include + +#ifdef OPENCV_WIN32_API +#define WIN32_LEAN_AND_MEAN +#include +#endif + +static void dumpHWFeatures(bool showAll = false) +{ + std::cout << "OpenCV's HW features list:" << std::endl; + int count = 0; + for (int i = 0; i < CV_HARDWARE_MAX_FEATURE; i++) + { + cv::String name = cv::getHardwareFeatureName(i); + if (name.empty()) + continue; + bool enabled = cv::checkHardwareSupport(i); + if (enabled) + count++; + if (enabled || showAll) + { + printf(" ID=%3d (%s) -> %s\n", i, name.c_str(), enabled ? "ON" : "N/A"); + } + } + std::cout << "Total available: " << count << std::endl; +} + +int main(int argc, const char** argv) +{ + CV_TRACE_FUNCTION(); + CV_TRACE_ARG(argc); + CV_TRACE_ARG_VALUE(argv0, "argv0", argv[0]); + CV_TRACE_ARG_VALUE(argv1, "argv1", argv[1]); + +#ifndef OPENCV_WIN32_API + cv::CommandLineParser parser(argc, argv, + "{ help h usage ? | | show this help message }" + "{ verbose v | | show build configuration log }" + "{ opencl | | show information about OpenCL (available platforms/devices, default selected device) }" + "{ hw | | show detected HW features (see cv::checkHardwareSupport() function). Use --hw=0 to show available features only }" + ); + + if (parser.has("help")) + { + parser.printMessage(); + return 0; + } + + if (parser.has("verbose")) + { + std::cout << cv::getBuildInformation().c_str() << std::endl; + } + else + { + std::cout << CV_VERSION << std::endl; + } + + if (parser.has("opencl")) + { + cv::dumpOpenCLInformation(); + } + + if (parser.has("hw")) + { + dumpHWFeatures(parser.get("hw")); + } +#else + std::cout << cv::getBuildInformation().c_str() << std::endl; + cv::dumpOpenCLInformation(); + dumpHWFeatures(); + MessageBoxA(NULL, "Check console window output", "OpenCV(" CV_VERSION ")", MB_ICONINFORMATION | MB_OK); +#endif + + return 0; +} diff --git a/opencv-apps/visualisation/CMakeLists.txt b/opencv-apps/visualisation/CMakeLists.txt new file mode 100644 index 0000000..eaddf77 --- /dev/null +++ b/opencv-apps/visualisation/CMakeLists.txt @@ -0,0 +1,3 @@ +ocv_add_application(opencv_visualisation + MODULES opencv_core opencv_highgui opencv_imgproc opencv_videoio opencv_imgcodecs + SRCS opencv_visualisation.cpp) diff --git a/opencv-apps/visualisation/opencv_visualisation.cpp b/opencv-apps/visualisation/opencv_visualisation.cpp new file mode 100644 index 0000000..be540ef --- /dev/null +++ b/opencv-apps/visualisation/opencv_visualisation.cpp @@ -0,0 +1,364 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2000-2008, Intel Corporation, all rights reserved. +// Copyright (C) 2009, Willow Garage Inc., all rights reserved. +// Copyright (C) 2013, OpenCV Foundation, all rights reserved. +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +//////////////////////////////////////////////////////////////////////////////////////// + +/***************************************************************************************************** + +Software for visualising cascade classifier models trained by OpenCV and to get a better +understanding of the used features. + +USAGE: +./opencv_visualisation --model= --image= --data=